NextJS SEO Playbook for 2026

The crawl problem hiding in your routes
Plenty of teams find an SEO regression the hard way. Not through a rankings dip, but through a Search Console alert: hundreds of URLs flagged "Discovered, not indexed." The cause usually isn't weak content. It's missing or wrong metadata cascading down from a layout component that no page bothers to override.
The App Router changed how metadata works in NextJS. Dropping tags straight into _document.tsx is gone. What replaced it is a structured, composable system that rewards the teams who learn it and quietly punishes the ones who don't.
This playbook walks through the technical SEO work that moves the needle in a modern NextJS app, ordered by how much each piece affects ranking and indexing.
Metadata hierarchy and the generateMetadata function
The App Router resolves metadata at the segment level. Every layout.tsx and page.tsx can export a static metadata object or an async generateMetadata function. When a layout and a page both export metadata, they merge, and the page wins any conflict.
Static metadata fits pages with fixed titles and descriptions:
export const metadata = {
title: "How We Build",
description: "Our engineering practice and delivery approach.",
openGraph: {
title: "How We Build",
description: "Our engineering practice and delivery approach.",
images: ["/og/how-we-build.png"],
},
};
Dynamic routes need generateMetadata. It gets the same params and search params the page does, so you can fetch the document and fill in metadata straight from it:
export async function generateMetadata({
params,
}: {
params: { slug: string };
}) {
const post = await getPost(params.slug);
return {
title: post.title,
description: post.excerpt,
alternates: { canonical: `https://example.com/blog/${params.slug}` },
};
}
The alternates.canonical field carries more weight than most teams give it. Leave it blank and Google picks a canonical for you, and its pick might not be the URL you wanted indexed.
Sitemap generation that actually keeps up
NextJS supports a sitemap.ts file that runs at build time for static exports and at request time for server-rendered apps. If your site has dynamic routes, the sitemap has to list every path:
import { getAllPosts } from "@/lib/blog";
export default async function sitemap() {
const posts = await getAllPosts();
const postUrls = posts.map((p) => ({
url: `https://example.com/blog/${p.slug}`,
lastModified: p.date,
changeFrequency: "monthly" as const,
priority: 0.7,
}));
return [
{ url: "https://example.com", lastModified: new Date(), priority: 1.0 },
{
url: "https://example.com/services",
lastModified: new Date(),
priority: 0.9,
},
...postUrls,
];
}
One rule worth enforcing: set lastModified to when the file or record actually changed, not to today. Stamp today's date on every build and you tell crawlers the whole site churns constantly, which can eat into your crawl budget.
Structured data for rich results
Schema markup is one of the few technical SEO levers that can surface a visible search feature fast. For blog content, Article schema with valid author, datePublished, and dateModified fields is the floor you should hit.
Add it with a JSON-LD component in the page or layout:
export function ArticleSchema({ title, description, date, url }: SchemaProps) {
const schema = {
'@context': 'https://schema.org',
'@type': 'Article',
headline: title,
description,
datePublished: date,
author: {
'@type': 'Organization',
name: 'Your Company',
url: 'https://example.com',
},
}
return (
<script
type="application/ld+json"
dangerouslySetInnerHTML={{ __html: JSON.stringify(schema) }}
/>
)
}
On service pages, Service and LocalBusiness schemas add context that can help with knowledge panel eligibility. On pricing pages, Offer schema puts your prices into product results.

Robots and indexing control
A robots.ts file in the app directory generates robots.txt at build time. Say plainly what crawlers can and can't reach:
export default function robots() {
return {
rules: [{ userAgent: "*", allow: "/" }],
sitemap: "https://example.com/sitemap.xml",
};
}
Individual pages can also set their own robots metadata to stay out of the index:
export const metadata = {
robots: { index: false, follow: false },
};
That beats a global meta robots tag in the root layout, which hits every page and is easy to forget you set.
Core Web Vitals as ranking signals
Once LCP, CLS, and INP became confirmed ranking factors, performance work started paying off directly in SEO. It matters most on the pages pulling the most organic traffic: landing pages, blog posts, service pages.
For those routes:
- Don't default to client components. Server components render HTML at build or request time, so first paint lands faster.
- Use
next/imagewith explicitwidth,height, andpriorityon above-the-fold images. Skip the dimensions and you get layout shift, which drags CLS down. - Keep total JavaScript light on text-heavy pages. A blog post has no business needing 400kB of client JS just to show words.
Run Lighthouse CI in your pipeline with per-route budgets. Catching a regression at PR time costs far less than clawing back rankings after a bad release goes out.
CI enforcement for SEO hygiene
The organizational move with the highest payoff is making SEO quality a gate in the pipeline. Add a metadata check that reads the build output and confirms:
- Every generated route has a non-empty
titleanddescription - No two published routes carry the same title
- Every dynamic route has a canonical URL
Teams that catch this at PR time spend a fraction of the debugging hours that teams relying on a Search Console alert three weeks later end up burning.
Where to focus first
Auditing an existing NextJS app? Work through it in this order:
- Check that
generateMetadataruns on every dynamic route - Compare sitemap coverage against the real URL count in Search Console
- Confirm canonical tags across all routes, especially paginated and filtered views
- Validate structured data with Google's Rich Results Test
- Run Lighthouse on your top five traffic routes and clear the CLS and LCP issues
Only that last step pulls in design or product. The rest is pure engineering you can ship in a single sprint.
For small and medium-sized businesses
For a smaller team, the payoff here is concrete. You move faster, you carry less operational risk, and a tight budget goes further. Nobody's asking you to adopt every shiny tool. The point is picking the web platform work and the AI-assisted workflows that actually move a number you care about.
Start with one workflow where the economics are obvious. Set a baseline. Improve it in 30-day chunks. Risk stays low, and your team builds real confidence as it goes.
Content & SEO Helpers
As an Amazon Associate I earn from qualifying purchases.
- Product-Led SEO by Eli SchwartzA practical playbook for turning content into compound traffic and demand.View on Amazon →
- Don't Make Me Think, RevisitedA usability classic for navigation, landing pages, and clearer content structure.View on Amazon →
- The Design of Everyday ThingsA strong lens for making content, flows, and interface decisions easier to understand.View on Amazon →
- AccelerateA useful reference when publishing systems need faster feedback loops and cleaner execution.View on Amazon →