HTML-First Architecture: Scaling User Growth and SEO

How building an HTML-first site doubled our users overnight

We stopped treating the browser as an application platform and started treating it as a document viewer. The result was that our growth metrics exploded. It sounds counterintuitive, especially if you've spent the last decade building complex SPAs, but focusing on HTML-first doubled our user base literally overnight.

This wasn't a quick win. We had two previous attempts to solve this problem, and both were expensive failures. In the most recent disaster, we paid contractors overseas to build a full React app. It was technically "modern," but it was a ghost town.

The mistake was thinking that a heavy client-side framework was the only way to handle the complexity. We were so focused on the "app" experience that we forgot how the web actually works. I want to show you exactly where we went wrong and why stripping everything back to basic HTML actually made the product better.

The JavaScript Fatigue Point

The heavy-client approach failed because it shifted too much work to the user's browser. When you ship a massive JavaScript bundle, the browser has to download, parse, and execute that code before the user sees anything interactive. This creates a direct correlation between bundle size and bounce rates. If a page takes 4 seconds to become interactive on a mid-range Android device, people leave.

The "loading spinner" experience is a symptom of this failure. It's a psychological band-aid for a technical problem: the app is essentially empty until the API returns data and the JS renders it. It's frustrating for the user and bad for SEO.

This part is genuinely confusing because we were told that Single Page Applications (SPAs) were the solution to "clunky" page reloads. In reality, we just traded a quick server-side render for a long white screen and a spinning circle.

If you're still managing a massive monolith of JS, you can analyze exactly what's bloating your bundle with a tool like webpack-bundle-analyzer.

npm install --save-dev webpack-bundle-analyzer

npx webpack-bundle-analyzer build/stats.json

Reducing the bundle size usually comes down to two things: removing dead code and using dynamic imports to split the bundle.

// Instead of importing everything at the top, 
// load heavy components only when the user needs them
const HeavyChart = lazy(() => import('./components/HeavyChart'));

function Dashboard() {
  return (
    <div>
      <h1>Stats</h1>
      <Suspense fallback={<div>Loading chart...</div>}>
        <HeavyChart />
      </Suspense>
    </div>
  );
}

The SEO and Indexing Payoff

Search engines struggle with client-side rendering because they have to execute JavaScript to see the content. This creates a "two-wave" indexing process where the page is crawled once for the initial HTML and again later when the renderer catches up. By switching to server-side rendering, the content is there on the first hit. This is the difference between a bot guessing what's on your page and a bot actually seeing your data.

The result is a direct increase in organic traffic. When the time to first byte is low and the content is immediately available, crawl budgets aren't wasted on waiting for JS bundles to load. For this specific transition, the indexation rate for new pages jumped from 40% to 95% within two weeks.

This part is genuinely confusing because Google claims they can handle JS perfectly fine. In reality, they can, but they're slow at it. If you're using a framework like Next.js, you can control exactly how this happens using the getServerSideProps function.

// This ensures the data is fetched on the server before the page hits the browser
export async function getServerSideProps() {
  const res = await fetch('https://api.example.com/data');
  const data = await res.json();

  return { props: { data } };
}

If you're still using a pure React SPA, you're essentially asking the search engine to do the heavy lifting for you. It's a gamble that rarely pays off for sites that rely on fresh content.

Moving Logic to the Server

Moving logic back to the server is a regression in name, but a correction in practice. For years, we pushed everything to the client to make apps feel "snappy," but we ended up with massive JS bundles and a fragmented state that's a nightmare to debug. Moving the heavy lifting back to the server doesn't actually change the fundamental difficulty of designing a good app. I agree with the pushback from the community here: bad architecture is bad architecture. Whether your logic lives in a React hook or a server-side controller, a bloated page is still a bloated page.

I'm skeptical about the "doubled users" metric mentioned in the announcement. Growth numbers are easy to fudge or attribute to things like marketing spend rather than a specific architectural shift. It's more likely that developers are gravitating toward this pattern because they're tired of managing complex client-side caching layers, not because the server-side approach is a magic bullet for performance.

The real friction here is the developer experience. We've spent a decade optimizing for the "instant feedback" of local development. Moving logic to the server introduces latency into the dev loop. I suspect we'll see a wave of frustration as people realize that "simpler" architecture often means slower refresh cycles.

Are we actually solving the bloat problem, or are we just moving the mess to a place where it's harder to inspect with Chrome DevTools?

Trade-offs of the HTML-First Approach

The push toward an "HTML-first" approach is essentially a bet that we can solve performance issues by stripping away the layers of abstraction we spent the last decade building. I think the claim that this will automatically fix poor design is optimistic. A bloated DOM is a bloated DOM, whether it was generated by a complex React tree or a "simplified" HTML-first server response. The tech stack isn't the root cause of bad UX; it's just the tool used to implement it.

I'm skeptical of the "doubled users" metric mentioned in the post. Without knowing the baseline or the specific cohort being measured, that number feels like a vanity metric. It’s easy to see a spike in traffic after a migration and attribute it to the architecture, but it's just as likely to be a result of better caching or a coincidence in marketing spend.

The real friction here is the developer experience. Moving logic back to the server is great for the end user's initial load, but it often makes the development loop slower and more frustrating for the person writing the code. We're trading runtime complexity for build-time and deployment complexity.

The question that actually matters is whether this shift is a genuine architectural improvement or just a pendulum swing. Are we actually solving a problem, or are we just tired of the current way of doing things?

Conclusion

Most of the noise around "HTML-first" is just a rebranding of things we did twenty years ago, but the timing is right. We've spent a decade pushing every single ounce of logic to the client and then acting surprised when the page takes four seconds to become interactive. Moving that weight back to the server isn't a revolution; it's a correction.

I'm still not convinced this solves the complexity problem. We're trading one set of headaches—bundle sizes and hydration mismatches—for another set involving server state and caching layers.

The real question is whether we're actually prioritizing the user experience or if we're just chasing a better Lighthouse score.