Skip to content
Rendering · a technical SEO specialism

A framework that can render anything will render the wrong thing confidently.

Next.js gives you four rendering strategies and no opinion about which one a given route should use. Most Next.js SEO defects are not bugs — they are a correct implementation of a decision nobody realised they were making.

01

Rendering strategy is a per-route decision

And it defaults silently

The single most consequential Next.js SEO question is which rendering strategy each route actually uses in production — and the answer is frequently not the one the team believes. A "use client" added three components up the tree, a dynamic function called in a layout, a cookie read during render: each quietly changes what arrives in the HTML for every route beneath it.

The diagnosis is not a code review. It is a comparison: fetch the route with JavaScript disabled, diff it against the rendered DOM, and do that per template rather than per page. What comes back is specific — this heading is present, this product description is not — and it is the finding everything else depends on.

Technical SEO

02

Metadata, and the routes that inherit it

generateMetadata or nothing

The App Router resolves metadata by merging the nearest metadata export or generateMetadata up the segment tree. That is a good design and it produces one recurring defect: a route with no metadata of its own silently inherits the layout’s, and a dynamic route with a generateMetadata that throws or returns early gets the same treatment.

The symptom is a set of pages sharing one title. The cause is a data fetch failing quietly inside metadata generation while the page body renders fine from a separate fetch. Nothing surfaces it except reading the shipped titles across the route set, which is a five-minute check and almost never run.

The related trap is metadataBase. Unset, relative Open Graph and canonical URLs resolve against whatever the deployment thinks its origin is — which on a preview deployment is a preview URL, canonicalised into production if that build is ever promoted.

03

Dynamic routes and the canonical they generate

One page, many addresses

Dynamic segments and catch-alls make it easy to serve one page from several addresses: a trailing slash variant, a case variant, an optional segment present and absent, a locale prefix that is sometimes implicit. Each is a distinct URL and each returns 200.

The canonical has to be generated from a single normalisation function rather than assembled per route, or the routes drift — and they drift in the direction of whatever a developer wrote most recently. Where the framework’s own trailingSlash setting disagrees with what the canonical emits, the site contradicts itself in the head of every page.

Canonicalisation, defined

04

ISR, and telling the truth about freshness

revalidate is not a date

Incremental static regeneration solves a real problem and creates a reporting one: a route regenerated on a timer has a new build timestamp and frequently identical content. A lastmod or a dateModified derived from build time then reports every page as changed on every revalidation.

That is worse than omitting it. A freshness signal that fires constantly is a signal that has been trained out of usefulness, and it is a self-inflicted one. The honest source is the content’s own last modification in whatever system authors it — the CMS timestamp, the commit date, the record’s updatedAt — not the moment the cache decided to rebuild.

This site makes the same argument about itself, and it took shipping the bug to learn it properly.

Five ways to measure vitals wrong

05

Soft 404s and client-side navigation

Status codes on the router

A client-side route transition has no HTTP status. Calling notFound() from a server component returns a real 404 on a hard request; a not-found state rendered inside a client component after a fetch resolves returns 200 with an apology on it, forever.

The check is direct and cheap: request a deliberately invalid dynamic segment with curl and read the status line. Sites that pass this in the browser and fail it on the wire are common, and the consequence is an index slowly filling with pages that say nothing.

06

Generated sitemaps and the parity problem

One file you fully control

A sitemap.ts generated from the same data source as the routes is the correct pattern and the reason most Next.js sitemaps are structurally sound. The defect is drift at the edges: a route excluded by middleware but present in the data, a draft record with a published flag the sitemap query does not filter, a locale enabled in config and not in content.

The sitemap is the one file where a site states its own intent unambiguously. A submitted URL that redirects, 404s or canonicalises elsewhere is the site contradicting itself in the one place it had complete control, which is why Atlas treats sitemap parity as a technical finding rather than a hygiene note.

XML sitemaps, in the technical channel

What is checked in a Next.js codebase — 10
rendering strategy per routegenerateMetadataserver vs client componentsdynamic route canonicalsISR revalidationsoft 404sgenerated sitemapsstreaming and fallbacksstructured data placementmiddleware redirects
07

Common questions

Answer first, then the reasoning

Is Next.js good for SEO?

Yes, when the rendering strategy per route is a decision rather than an accident. Next.js can produce fully server-rendered HTML for every route; it can equally produce an empty shell for a route nobody realised had become client-rendered. The framework is not the variable — knowing which mode each route is in is.

Why do my Next.js pages share the same title?

Almost always metadata inheritance. The App Router merges the nearest metadata export up the segment tree, so a route with no metadata of its own inherits the layout’s — and a generateMetadata whose data fetch fails quietly produces the same result while the page body renders normally from a separate fetch.

Does React Server Components change technical SEO?

It changes where the answer lives, not what the question is. The question has always been what arrives in the served HTML. RSC makes the good answer easier to reach and makes the boundary easier to cross accidentally, because one "use client" high in the tree changes every route beneath it.

Has Atlas published a Next.js engagement?

No. This page describes method, not a portfolio — JavaScript rendering and indexation are published scope in the technical channel, and the checks above are the ones Atlas runs. Where a client result is not published on this site, it is because there is not one to publish.

09

Build visibility

Seven questions · no discovery call

Atlas reads the site before the first conversation. These are the seven things worth knowing first.

Next.js SEO Build visibility