One of the main SEO decisions in Next.js is how each route renders. Some crawlers can execute JavaScript, while others may rely primarily on the initial HTML response. Serving key content in that response reduces dependence on crawler-specific rendering support.
The rule of thumb:
- Static (SSG) — marketing pages, docs, blog posts. Fastest, cheapest, fully indexable.
- ISR (Incremental Static Regeneration) — catalogs, listings, anything that changes hourly/daily. Static speed with controlled freshness.
- SSR (dynamic rendering) — personalized or truly real-time pages. Indexable, but slower TTFB.
- CSR (client-only) — dashboards and app internals. Never for content you want indexed.
// app/blog/[slug]/page.tsx — ISR: static HTML, regenerated hourly
export const revalidate = 3600;
export async function generateStaticParams() {
const posts = await getPosts();
return posts.map((post) => ({ slug: post.slug }));
}
Check what the crawler receives — not what your browser shows:
curl -s https://example.com/your-page | grep -i "your-key-phrase"
If your key content isn't in that response, crawlers that don't render the page may miss it.
Watch out for accidental dynamic rendering. Using cookies(), headers() or searchParams in a page silently switches it to SSR. That's not fatal for SEO, but it costs TTFB and server load. We covered these traps in detail in How Caching Works in Next.js with App Router.
Checklist:
- Every indexable route is SSG or ISR; SSR only where personalization demands it
-
curlthe raw HTML and confirm the main content is there - No
cookies()/headers()calls on pages that should be static - Client components used for interactivity, not for content
