Technical SEO in Next.js App Router: A Production Guide
A practical Next.js App Router SEO guide covering metadata, canonical URLs, sitemaps, robots rules, JSON-LD, internal links, rendering, and release checks.

Technical SEO in the Next.js App Router is not a collection of magic tags. A production-ready page needs one crawlable URL, useful content in the initial HTML, accurate metadata, consistent canonical signals, clear internal links, and structured data that describes what visitors can actually see.
This is the workflow I use when I want a Next.js page to be understandable to both people and search engines. It applies to portfolios, product sites, documentation, editorial sites, and most content-led applications.
The short answer: what a Next.js page needs
Every indexable page should pass these checks:
- 1.It has a stable, descriptive URL and returns a successful HTTP status.
- 2.Its primary text is available without waiting for a client-side request.
- 3.Its title and description are specific to that page.
- 4.Its canonical URL agrees with the URL used in internal links and the sitemap.
- 5.It is reachable through normal HTML links.
- 6.Its structured data matches visible page content.
- 7.Googlebot can load the CSS, JavaScript, images, and fonts needed to understand it.
- 8.It works well on a mobile viewport and does not fail Core Web Vitals.
Next.js gives us strong primitives for most of this. The engineering work is making those primitives describe the site consistently.
Start with search intent, not metadata
Before opening layout.tsx, define the page's job. A good page answers one primary question for one audience. If two pages answer the same question with only small wording changes, metadata will not solve the overlap.
For an article, write down:
- ●The exact task the reader is trying to complete
- ●The expected level of knowledge
- ●The decision or result they should reach
- ●What this page adds beyond a summary of documentation
Google's guidance on people-first content asks whether a page provides original analysis, substantial value, clear expertise, and a satisfying result. That is a better editorial test than counting how often a phrase appears.
Render the important answer in the initial HTML
The App Router uses Server Components by default. That is useful for SEO because headings, body copy, links, and structured data can be rendered into the document before client JavaScript runs.
// app/guides/[slug]/page.jsx
import { notFound } from "next/navigation";
import { getGuide } from "@/lib/guides";
export default async function GuidePage({ params }) {
const { slug } = await params;
const guide = await getGuide(slug);
if (!guide) notFound();
return (
<article>
<h1>{guide.title}</h1>
<p>{guide.summary}</p>
<GuideBody content={guide.content} />
</article>
);
}Keep client boundaries narrow. A theme switcher, table-of-contents control, or interactive example may need "use client"; the article itself usually does not. This also reduces the risk that important content is absent until hydration or a browser-only fetch completes. My Server Components guide explains that boundary in detail.
Build page-specific metadata
Use a static metadata export when the values do not change by route, and generateMetadata when they depend on a record or route parameter. Next.js documents both in its current Metadata guide.
export async function generateMetadata({ params }) {
const { slug } = await params;
const guide = await getGuide(slug);
if (!guide) return {};
const canonical = `https://example.com/guides/${guide.slug}`;
return {
title: guide.title,
description: guide.description,
authors: [{ name: "Your Name", url: "https://example.com/about" }],
alternates: { canonical },
openGraph: {
type: "article",
url: canonical,
title: guide.title,
description: guide.description,
publishedTime: guide.publishedAt,
modifiedTime: guide.updatedAt,
},
};
}The title should identify the page, not repeat a generic site slogan. The description should accurately summarize the benefit and scope. Google may rewrite either in results, so treat them as strong suggestions rather than guaranteed display copy.
Set metadataBase once in the root layout if you use relative image URLs. Then inspect the built HTML and confirm that canonical and social URLs resolve to the intended production origin—not localhost, a preview hostname, or a staging domain.
Make canonical signals agree
A canonical is not a redirect and it is not an instruction to index a page. It tells search engines which URL you prefer when similar or duplicate versions exist.
For each article, use the same absolute URL in:
- ●The page's
alternates.canonical - ●The XML sitemap
- ●Internal navigation
- ●
og:url - ●The
mainEntityOfPageor@idin structured data
Do not point every article to the home page. Do not alternate between trailing-slash and non-trailing-slash forms without a deliberate redirect policy. Google's canonicalization documentation describes these signals as inputs; Google still chooses the canonical it considers most representative.
Generate robots and sitemap data from the same source
Next.js supports special metadata files for both robots rules and sitemaps. For a content site, generate the sitemap from the same collection that produces the routes. That prevents a common failure: publishing an article that is visible in the UI but absent from the sitemap.
// app/sitemap.js
import { getGuides } from "@/lib/guides";
export default async function sitemap() {
const guides = await getGuides();
return [
{
url: "https://example.com",
lastModified: new Date("2026-09-07"),
priority: 1,
},
...guides.map((guide) => ({
url: `https://example.com/guides/${guide.slug}`,
lastModified: new Date(guide.updatedAt ?? guide.publishedAt),
changeFrequency: "monthly",
priority: 0.8,
})),
];
}Only include canonical, indexable URLs. A sitemap helps discovery; it does not override a noindex, a blocked response, a redirect, a broken page, or weak content. The Next.js sitemap file convention and Google's sitemap guidance are the primary references.
Keep robots rules simple. Allow public pages, disallow only areas that genuinely should not be crawled, and include the absolute sitemap URL.
// app/robots.js
export default function robots() {
return {
rules: { userAgent: "*", allow: "/", disallow: ["/private/"] },
sitemap: "https://example.com/sitemap.xml",
host: "https://example.com",
};
}Do not use robots.txt to remove an already indexed URL. Crawling controls and indexing controls solve different problems.
Add JSON-LD that matches the page
Structured data helps a search engine identify entities and relationships. It is not a place to add claims that visitors cannot verify on the page.
For an article, connect the BlogPosting to a stable author page:
const jsonLd = {
"@context": "https://schema.org",
"@type": "BlogPosting",
"@id": canonical,
headline: guide.title,
description: guide.description,
datePublished: guide.publishedAt,
dateModified: guide.updatedAt ?? guide.publishedAt,
author: {
"@type": "Person",
"@id": "https://example.com/#person",
name: "Your Name",
url: "https://example.com/about",
},
mainEntityOfPage: { "@type": "WebPage", "@id": canonical },
};
<script
type="application/ld+json"
dangerouslySetInnerHTML={{ __html: JSON.stringify(jsonLd) }}
/>Google's Article structured data guide recommends an author type and a URL or sameAs value that helps identify the author. Make the visible byline and markup agree. Validate the final production URL with the Rich Results Test, not only the source object in your editor.
Design an internal-link architecture
Sitemaps are useful, but normal HTML links define how people and crawlers move through the site. Every article should be reachable from a hub page, and related articles should link to one another when the connection helps the reader.
A simple structure works:
| Page | Links to | Purpose |
|---|---|---|
| Home | Latest or best articles | Fast discovery and authority transfer |
| Blog index | Every article | Complete crawl path |
| Article | Related guide, author, work | Context and next step |
| About | Selected articles | Connect expertise to evidence |
Use descriptive anchor text. “Read my React performance guide” carries more meaning than “click here.” Do not force unrelated links into a paragraph to manipulate keywords.
Protect page experience and crawlability
Google needs access to the resources required to render the page. Check that production headers, a CDN, or a security rule do not block /_next/ assets, images, or fonts for crawlers.
For page experience:
- ●Reserve image dimensions so media does not shift text.
- ●Load the likely LCP asset early; do not lazy-load it.
- ●Keep long client tasks away from navigation and primary controls.
- ●Self-host or optimize fonts and provide stable fallbacks.
- ●Make mobile navigation keyboard- and touch-usable.
- ●Keep main text readable at browser zoom and on narrow screens.
For a metric-by-metric diagnostic process, use my React and Core Web Vitals guide.
A production SEO release checklist
Run this against the built application, not only development mode:
- 1.Open the canonical URL and confirm a 200 response.
- 2.View the initial HTML and find the unique title, description, H1, primary copy, and links.
- 3.Confirm there is exactly one intended canonical URL.
- 4.Verify
robots.txtallows the URL and references the sitemap. - 5.Verify the sitemap contains the canonical URL with an honest modification date.
- 6.Parse every JSON-LD block and compare it with visible content.
- 7.Test the page at a narrow mobile width for overflow and unusable controls.
- 8.Check broken internal and external links.
- 9.Inspect the page with Search Console's URL Inspection tool after deployment.
- 10.Monitor impressions, queries, click-through rate, and indexed status before deciding what to rewrite.
Common Next.js SEO failures
The page has metadata but no useful body in the HTML
Search metadata cannot compensate for thin or client-only content. Render the primary answer on the server and let interactivity enhance it.
Preview and production URLs conflict
Hard-code or configure one trusted production origin for canonicals, sitemap entries, JSON-LD identifiers, and social URLs. Do not infer it from untrusted forwarded headers.
Every page reuses the same title and description
Use generateMetadata from the same record that renders the page. Add a test that visits representative detail routes and compares the visible H1 with metadata.
Structured data promises content the page does not contain
Markup must describe visible, accurate information. Invalid or misleading markup can lose rich-result eligibility and user trust.
Dates change on every build
Use the real publication and editorial modification dates. Rebuilding a site is not the same as updating an article.
What “good SEO” means in production
A technically correct Next.js site is eligible to compete; it is not guaranteed to rank. Google explicitly says there is no secret that automatically ranks a site first. Useful content, reputation, links, and time still matter.
The engineering goal is to remove ambiguity: one valuable page, one consistent canonical URL, one clear author, crawlable content, accurate structured data, and measurable performance. Once those foundations are stable, Search Console data can tell you which real queries deserve deeper coverage.