You’ve probably been in a meeting where someone says “we need better SEO” and the immediate response is “let’s use Next.js.” And they’re not wrong — but why they’re not wrong is more interesting than the recommendation itself. The SEO benefits of meta-frameworks aren’t a feature someone bolted on. They’re an architectural consequence of a decision made decades ago: let the server do the talking.
I’ve spent most of my time in this space working with TanStack Start , but the patterns I’m about to walk through apply equally to Next.js, SvelteKit, Nuxt, Astro, and pretty much every meta-framework out there. They’re all solving the same fundamental problem — just with different trade-offs.
The <div id="root"></div> Era#
For about five years (roughly 2015–2020), the default way to build a web app was to ship an empty HTML shell and let JavaScript do everything:
<!-- What Google's crawler receives from a typical SPA -->
<!DOCTYPE html>
<html>
<head>
<title>My App</title>
</head>
<body>
<div id="root"></div>
<!-- nothing here -->
<script src="/bundle.js"></script>
<!-- 200KB+ before anything appears -->
</body>
</html>
The browser downloads the bundle, executes it, makes API calls for data, then renders the UI. Users stare at a white screen for 1–3 seconds. Crawlers receive a page with no content, no meta description, no structured data — just an empty div and a prayer that Googlebot will bother executing your JavaScript later.
And that “later” is doing a lot of heavy lifting. Google runs a two-phase indexing pipeline: first it indexes the raw HTML (which, for an SPA, is nothing), then it queues the page for JavaScript rendering in a headless Chromium instance. That queue can be days behind the crawl. Your content sits in limbo while your competitors’ server-rendered pages get indexed immediately.
Social media previews are even worse — Twitter, LinkedIn, and Facebook never execute JavaScript when generating link cards. A CSR page shared on LinkedIn is a blank card with your domain name. Not exactly compelling.
Engine vs. Car#
Here’s the thing that clicked for me when I started working with TanStack Start: a meta-framework isn’t just “React but with routing.” It’s a runtime that controls where and when your code executes. React is the engine. TanStack Start (or Next.js, or SvelteKit) is the car — it decides whether to run your component on the server, at build time, on the edge, or in the browser.
That control is the whole game. Every SEO and performance benefit flows from one architectural decision: generate HTML on the server (or at build time) before the user’s browser ever gets involved.
Three Flavors of “Let the Server Handle It”#
Meta-frameworks offer multiple rendering strategies, and the real power is mixing them per route. Here’s how I think about them:
Static Site Generation (SSG) builds HTML at deploy time. This blog runs on Hugo — pure SSG. Every page is a pre-built HTML file sitting on a CDN. No server, no database, no cold starts. Fastest possible load times, and crawlers get complete content on every request. The trade-off? You rebuild and redeploy to update content.
Server-Side Rendering (SSR) generates HTML per request. When a user hits /dashboard, the server fetches their data, renders the component tree to HTML, and sends a fully-formed page. In TanStack Start, this happens in the route loader:
// TanStack Start — the loader runs on the server for every request
export const Route = createFileRoute("/dashboard")({
loader: async ({ context }) => {
// This runs server-side. Data is serialized and sent with the HTML.
return { data: await getDashboardData(context.userId) };
},
component: Dashboard,
});
The browser receives complete HTML with all the content, meta tags, and structured data baked in. The crawler is happy. The user sees content immediately. JavaScript loads afterward to make the page interactive.
Incremental Static Regeneration (ISR) is the hybrid — static pages that revalidate in the background. Next.js pioneered this: serve a cached page instantly (SSG speed), but after a configurable window, the next request triggers a background rebuild. The user who triggers it still gets the cached version — no waiting. Clever trick, and it solves the “rebuild the entire site for one content change” problem.
The Hydration Tax#
There’s a catch, though. When the server sends HTML and the browser also needs to make that page interactive, something has to bridge the gap. That something is hydration — the process of attaching JavaScript event handlers and state to the server-rendered HTML.
The browser downloads the JS bundle, parses it, rebuilds the component tree in memory, reconciles it with the existing DOM, and attaches event listeners. All of that work just to arrive at the same visual state the server already produced. On a decent connection you might not notice, but on mobile or slow networks, there’s a 1–2 second window where the page looks ready but buttons don’t respond. Misko Hevery (creator of Angular and Qwik) called this “pure overhead,” and he’s not wrong.
The industry’s response has been to minimize, defer, or eliminate hydration:
- Islands architecture (Astro) — only interactive components ship JavaScript. The rest is static HTML. A blog post with one search widget? Only the search widget hydrates.
- Selective SSR (TanStack Start) — opt routes out of server rendering entirely. An admin panel nobody’s going to Google? Skip SSR, save the server the work:
// TanStack Start — no SSR for this route. Why bother?
export const Route = createFileRoute("/admin/analytics")({
ssr: false,
component: AnalyticsDashboard,
});
- React Server Components (Next.js App Router) — components are server-only by default. They never ship JavaScript to the browser. You opt in to client interactivity with
'use client', and only those components hydrate. Teams report 30–50% reductions in client-side JS.
Why Crawlers Care (Mechanically)#
The SEO benefits aren’t some vague “it’s better for Google” hand-wave. They’re mechanical:
Complete HTML on first response. Crawlers get your title, description, Open Graph tags, structured data, and full content without executing a single line of JavaScript. First-pass indexing captures everything.
Core Web Vitals. Google uses LCP (Largest Contentful Paint), INP (Interaction to Next Paint), and CLS (Cumulative Layout Shift) as ranking signals. Server-rendered pages have content in the initial HTML, so LCP is fast. Less JavaScript means better INP. Stable server-rendered layouts mean less CLS. SSG pages routinely hit sub-1-second LCP because they’re pre-built files on a CDN.
Real links. File-based routing produces actual <a href="/posts/something"> tags that crawlers follow. No hash routing, no JavaScript-dependent navigation, no onClick={() => navigate('/somewhere')} that crawlers might miss.
Social previews that actually work. Meta tags are in the initial HTML. When someone shares your link on LinkedIn, the preview card shows the right title, description, and image — because the platform reads the HTML, not your JavaScript.
Picking Your Strategy#
The framework comparison table could fill an entire post (and maybe it will), but the decision usually comes down to a few questions:
If your content is mostly static — blogs, docs, marketing — Astro ships zero JavaScript by default and lets you sprinkle in interactive islands from any framework. Hugo if you don’t need a JS framework at all (it’s what this blog uses, and it builds in milliseconds).
If you’re in the React ecosystem and want maximum type safety, TanStack Start is the one I reach for. End-to-end type inference from server loader to component, selective SSR per route, and it’s not coupled to any hosting platform.
If you need ISR, React Server Components, or the largest ecosystem, Next.js is the pragmatic default. The mental model is more complex (RSC + App Router caching behavior can be genuinely confusing), but the capability ceiling is the highest.
If bundle size is everything, SvelteKit compiles away the framework overhead. Smallest output of any meta-framework, no virtual DOM.
The Wheel Turns#
What’s funny is that server-rendered HTML is the oldest pattern on the web. PHP, Rails ERB, Java JSPs — they all rendered HTML on the server. Then the SPA era swung the pendulum fully client-side. Now meta-frameworks are swinging it back, but with the ergonomics of component-based development, type-safe data fetching, and selective hydration that the PHP era could only dream of.
The server always won the SEO and performance game. We just spent a decade figuring out how to make it feel like a SPA too.
More on the specific framework comparisons (and my experience with TanStack Start’s selective SSR in production) in a future post.
