← All writing
Craft · · 8 min

Five pages and a contact form

Rebuilding the browser, badly. The tax nobody itemizes, and the one question that decides it.

JavaScript Performance

Somebody asked me a while back what they should build their studio site in, and I caught myself about to answer with a framework. Not a question back, not “what’s actually on it,” just straight to a name. Which bugged me for the rest of the day, because the site is five pages and one of them is a contact form.

At some point the default inverted and I don’t think most of us noticed it happening. It used to be that you had a page and you added some JavaScript to it. Now you have a JavaScript application and you add some pages to it. Those are not the same starting position and they don’t lead to the same place, and for the vast majority of what’s on the web, which is brochures, the second one is a very strange thing to be doing.

Count the interactions. Go on, count them.

The site in question has a nav, a few pages of copy, a grid of work, a form that POSTs somewhere, and a mobile menu that opens. That’s the interaction budget. That is the entire list.

For that, a lot of people would reach for a client-side framework, which means shipping a runtime whose whole purpose is efficiently re-rendering state that changes a lot in response to the user. There’s no state. Nothing changes. The content is the same for every visitor and it was the same yesterday. You’ve brought a diffing algorithm to a document.

And to be clear I’m not anti-React, I write it, it’s very good at the thing it’s good at. But we’ve made it the answer to a question nobody asked, and the tell is that the interesting decisions on a brochure site are typography, copy, image weight and whether the contact form actually goes anywhere, none of which your framework has an opinion about.

You end up rebuilding the browser, badly

This is the part that gets me. The moment you go SPA on a content site you sign up to reimplement, in userland, a pile of things the browser already does correctly and for free.

Routing, obviously. But also scroll restoration, which everybody gets wrong on the back button at least once. Focus management, because a route change that doesn’t move focus leaves screen reader users parked wherever they were. Route announcements, same reason. The loading state that the browser used to give you for nothing. ctrl-click and middle-click and right-click-open-in-new-tab, which work fine right up until somebody uses a div with an onClick instead of an a with an href, which happens constantly. Meta tags and canonicals and OpenGraph, which now need a library. Error states for when a chunk fails to load, which is a failure mode that only exists because you made it exist.

You’re not choosing between React and no framework. You’re choosing between React and the browser, and the browser has had 30 years and a lot of very smart people working on this exact problem.

You're not choosing between React and no framework. You're choosing between React and the browser.

The old trump card here was that client-side nav just feels faster, and honestly for a long stretch that was true and it mattered. But that gap has been closing from the other direction. Prefetching on hover is a couple of lines, and it buys you most of what the router was for. A small, cached, server-rendered site on a decent host is already at the point where the navigation is not what anybody’s complaining about.

The tax nobody itemizes

The bundle is the obvious cost and it’s the least of it, but it’s still real: the runtime alone is somewhere around 40-odd KB gzipped before you’ve written a line of your own code, and it’s a floor, not a total. That’s on a client’s phone, on transit wifi, on a device three generations behind whatever you’re testing on. Your machine is a bad instrument for measuring this because your machine is great.

The bigger tax is that you’ve attached a build pipeline to a document. Come back in 18 months to change a phone number and there’s a decent chance npm install doesn’t complete anymore, or it does and the dev server won’t boot, or the Node version the toolchain wants no longer resembles the one you have. I’ve had this happen on my own projects, where I was the only developer and the only user and I still couldn’t get back in without an afternoon of yak shaving. Meanwhile a PHP file from 2011 still runs, and a folder of HTML runs forever because it isn’t running anything.

There’s a maintenance-inheritance angle too, and it’s the one I’d actually lead with if I were pitching a client. The person who edits this site in three years is probably not you. Handing them a static site generator or a boring server-rendered app means handing them files they can read. Handing them a component tree, a bundler config and a lockfile means the next quote they get for “change the homepage headline” is four figures.

I know the counter

The strongest version of the pushback isn’t performance, it’s ergonomics, and it’s fair. Components are a genuinely good idea. Nobody wants to go back to copy-pasting a header into 11 files, and “just write HTML” as advice ignores that the reason we reached for all this was real.

I’d just say two things. First, components aren’t the framework’s, they’re everybody’s now. Eleventy and Jekyll and Hugo have includes and layouts. Astro has actual components with scoped styles and ships zero JS by default, with islands for the two bits that need it. Laravel has Blade, Symfony has Twig, and if you want a piece of a page to be properly reactive, Livewire and htmx and Alpine all exist and all fit in the space between “a static file” and “a single-page application.” The middle of that spectrum got really good while everybody was arguing about the ends.

Second, “but I already know React” is a real cost and I’m not going to pretend otherwise. It’s just usually smaller than it feels, on a five page site, versus the cost of everything above. And it’s worth being honest that a chunk of the reason we default to it isn’t technical at all. It’s that it’s what create-next-app gives you, it’s what the tutorials teach, it’s what’s in the job posting, and nobody has ever been criticized in a code review for picking React. That’s an incentive structure, not an engineering argument, and I say that as someone it has absolutely worked on.

The question that actually decides it

Where does the interesting state live.

If it lives on the server or gets baked at build time, and the user’s job is mostly to read it, ship HTML. Blog, portfolio, marketing site, docs, restaurant, church, small business, most of the actual web. Server-render it or compile it, add the 20 lines of vanilla JS for the menu toggle and the theme switch, and be done.

If it lives in the client and changes constantly in response to what the user does, if there’s a canvas or an editor or a drag-and-drop or a dashboard with seven filters, if losing state on navigation would be a real loss, then yeah, use the tool built for that. Figma should not be an MPA. That’s not a close call and nobody’s arguing it.

Most things aren’t Figma. That’s the whole point. We took the tooling that was built for the hardest 5% of the web and made it the starting position for the other 95%, and then spent a decade building meta-frameworks to claw back the server rendering we’d given away in the first place. Which, when you say it out loud, is a slightly funny thing to have collectively done.

Anyway, this site is Jekyll, it’s a few kilobytes of CSS, and the only JavaScript on it flips a data-theme attribute. It’ll still build in five years and I won’t have thought about it once in between, which is all I want from it.

I mostly build things where the state does live on the server, so of course this is where I landed. If your work skews the other way your defaults should probably skew with it. Just, you know. Count the interactions first.

Read similar posts
7 min

IntersectionObserver

For about eight years the standard way to find out whether something was on screen was to ask, repeatedly, on every scroll event, in a way that forced the browser to stop and recalculate layout each time.

8 min

Service workers on a brochure site

Five pages, a contact form, updated roughly twice a year, and I spent an evening giving it an offline experience. I've been trying since to work out honestly whether that was worth doing or just fun.