跪拜 Guibai
← Back to the summary

SPA's Empty #root Kills SEO — How Next.js SSR Feeds Both Crawlers and AI

Introduction: A Frontend Developer's Question

Have you ever wondered: where does the traffic for veteran content sites like Juejin and CSDN come from? The answer is almost always SEO.

Look at the other side — the countless AI Agent product websites flooding the market are uniformly built with Next.js. Why? The answer lies in an old SPA flaw — SEO is practically zero.

This article records the entire chain I've recently sorted out: why SPA has poor SEO → how Next.js solves it → App Router conventions → basic SEO / GEO syntax. Written for friends struggling in the same big frontend circle.

1. What is Next.js

In one sentence: Next.js is a full-stack React framework; its Vue ecosystem counterpart is Nuxt.js.

Create a project:

npx create-next-app@latest

Choose default configuration, and you get the whole family bucket in one go:

Dependency Purpose
React / React-DOM UI
TypeScript Type safety
Tailwind CSS Styling
ESLint Code standards

2. The Glory and Shortcomings of SPA

Let's first give SPA its due: its experience is genuinely good.

Native requires writing two sets (Android + iOS), SPA only needs to write HTML once. On this ledger, the frontend wins big.

But the shortcoming is equally fatal: no SEO.

The index.html returned by an SPA server looks like this:

<div id="root"></div>
<script src="main.jsx"></script>

Where is the actual page content? You have to wait for the browser to download JS → React mounts to #rootuseEffect sends a request to fetch data → only then can it render.

Search engine crawlers (Baidu, Google, Bing) don't wait for your JS to finish running; they grab the HTML and leave, seeing only an empty #root. In the PC era, search engines were the traffic gateway, and SEO was life itself. Without indexable content, a website is invisible to search engines.

3. CSR vs SSR: Where Exactly Does the Component Render

This is the root of the SEO problem — where exactly does the component render:

See through it with a formula:

JSX Component (Template) + Data = HTML (Rendered Result)

The same React component, the same data — where the formula executes determines what the crawler sees:

CSR SSR
Equation execution location Browser (Client) Server (Node)
Returned to browser Empty #root + JS Complete HTML
Crawler sees Empty shell Title / Body / Links
SEO Practically zero Excellent

4. How SSR is Implemented: JSX + Data = HTML

The principle is actually very simple: a React component is essentially a function, just a template. As long as it doesn't listen to events or use useEffect, the component function plus data can be directly compiled into an HTML string in a Node environment — there is no DOM on the server, the output is a formatted string.

// app/todos/page.tsx —— this component renders on the server
export default async function TodosPage() {
  const todos = await getTodos() // fetch data on the server
  return (
    <ul>
      {todos.map((todo) => (
        <li key={todo.id}>{todo.title}</li>
      ))}
    </ul>
  )
}

What the browser receives is not an empty shell, but:

<ul>
  <li>Write a Juejin blog post</li>
  <li>Finish learning Next.js basics</li>
</ul>

The crawler grabs it accurately every time. Then the browser-side JS performs hydration, taking over the static HTML and turning it into an interactive React application — after SSR, the experience remains just as good as SPA. You get both fish and bear's paw.

5. Next.js App Router: Convention Over Everything

Next.js routing requires no configuration; files are routes, which is the foundation of its "out-of-the-box" confidence.

app/
├── layout.tsx           # Shared layout (nav, footer), wraps all pages
├── page.tsx             # → /
├── about/
│   └── page.tsx         # → /about
└── post/
    └── [id]/
        └── page.tsx     # → /post/123 (dynamic route)

Rendering rule: when requesting /about, first execute the layout.tsx layout, then execute the about/page.tsx component, compiling the tsx into HTML.

layout.tsx looks like this:

export default function RootLayout({
  children,
}: {
  children: React.ReactNode
}) {
  return (
    <html lang="zh-CN">
      <body>
        <nav>…Navigation…</nav>
        {children}
        <footer>…Footer…</footer>
      </body>
    </html>
  )
}

Dynamic routing [id] is the key point — one file serves millions of pieces of content:

// app/post/[id]/page.tsx
export default async function PostPage({
  params,
}: {
  params: { id: string }
}) {
  const post = await getPost(params.id)
  return (
    <article>
      <h1>{post.title}</h1>
      <p>{post.content}</p>
    </article>
  )
}

/post/123, /post/456…… every single one is a complete HTML rendered by SSR, the entire site gets indexed by search engines, and traffic comes naturally.

6. Basic SEO Syntax

SEO has two layers:

First layer: Tell search engines "who you are" (meta information in <head>)

In Next.js App Router, directly export metadata:

import type { Metadata } from 'next'

export const metadata: Metadata = {
  title: 'My AI Product Website',
  description: 'One sentence clarifying the value provided',
  keywords: ['AI', 'Assistant', 'Tool'],
}

Generated HTML:

<head>
  <title>My AI Product Website</title>
  <meta name="description" content="One sentence clarifying the value provided" />
  <meta name="keywords" content="AI, Assistant, Tool" />
</head>

Second layer: Create content. This is the reason users visit you, and also the main body that search engines index. No matter how well the title is written, without content it's an empty frame. This is where SSR's value lies — content is delivered to the crawler's mouth in the form of complete HTML.

7. GEO: The New Battlefield in the AI Era

Traditional SEO is for search engine crawlers. But the AI era has arrived, and user entry points have shifted from Baidu to Doubao, ChatGPT, Perplexity.

GEO (Generative Engine Optimization): Getting AI to include your content and purchase links when generating answers.

SEO GEO
Target Search engine crawlers AI large models
Mechanism Crawl HTML for indexing Read content, cite when generating answers
Goal Rank high in search results Your content + links appear in answers
Prerequisite SSR outputs complete HTML Content is structured, authoritative, understandable by AI

Why do AI product websites use Next.js? Because in the GEO era, AI also needs to read your web pages — if it's still an SPA's empty #root, AI can't read it either. One complete SSR HTML feeds both crawlers (SEO) and AI (GEO); one investment, double the returns.

8. Summary

String the whole chain together:

SPA (#root empty shell) ── Poor SEO ──→ Need SSR so crawlers can see content
                                └→ Next.js (React full-stack framework)
                                    ├─ Files as routes (App Router)
                                    ├─ JSX + Data = HTML (executed on server)
                                    ├─ Meta tags tell search engines who you are
                                    └─ Complete HTML feeds both crawlers (SEO) and AI (GEO)

The big frontend has more than just SPA in its hands. Mastering Next.js means holding three cards simultaneously: frontend experience, backend capability, and SEO traffic. For your next AI product website, why not start with this command:

npx create-next-app@latest