A Next.js site is technically ready for SEO when every indexable page is server-rendered, has a unique title, description, canonical URL and one H1, carries JSON-LD structured data, appears in a generated sitemap, and passes Core Web Vitals on mobile. The reliable way to get there is to generate all of those from the same content the page renders, so they cannot drift apart. This is the checklist I work through when I build or audit a Next.js site.
Does a Next.js site need special SEO work?
Less than most stacks, but not none. Next.js gives you server rendering, route-level code splitting and image optimisation out of the box, which is the shortest path to fast pages. What it does not give you is the layer an SEO plugin provides on WordPress: titles, descriptions, canonicals, schema and sitemaps. You build that layer yourself, once, and every page inherits it.
What should a Next.js technical SEO checklist include?
| Item | Why it matters | How to do it in Next.js |
|---|---|---|
| Server rendering | Crawlers get full HTML on the first request | Server Components or static generation. Do not fetch main content in a client effect |
| Unique title and description | Controls the search snippet | generateMetadata per route, from the page's data |
| Canonical URL | Stops duplicate versions splitting signals | alternates.canonical in metadata |
| One H1 per page | Clear topic for readers and crawlers | Enforce it in your page-level components |
| Structured data | Rich results and answer engines | A JSON-LD script per page |
| XML sitemap | Discovery of every page | app/sitemap.ts, generated from your content |
| robots.txt | Crawl rules and sitemap location | app/robots.ts |
| Open Graph and social images | Good link previews on LinkedIn and chat apps | openGraph metadata and an image per page |
| Images | Usually the largest bytes on the page | next/image with sizes set, hero loaded with high priority |
| Redirects | Old URLs keep their value | redirects() in the config, or middleware for patterns. The Redirect Map Builder writes the array for you |
| Real 404s | Avoids soft 404s in Search Console | notFound() for missing content |
| Trailing slashes | One URL per page | Pick one form and redirect the other |
| llms.txt | A clean summary for answer engines | A route or static file at the root |
| Security headers (optional) | Protects visitors and signals a well-run site | headers() in next.config.js, checked with the Security Headers Checker |
In the App Router, most of this lives in the Metadata API and the sitemap.ts and robots.ts file conventions, so there is no plugin to configure and nothing to keep in sync by hand.
Related guide: next-seo vs Next.js Metadata API: which should you use?
How should you render pages for SEO?
Server render every page you want indexed, so the HTML arrives complete instead of waiting on a client-side bundle. In the App Router, Server Components do this by default. The principle holds for any React framework: this portfolio runs on TanStack Start rather than Next.js, and the same rule applies there.
The common mistake is a page that renders a shell on the server and loads the real content in the browser. Check with View Source, or with curl, on your key pages. If the headline and body copy are not in the raw HTML, fix that before anything else.
How do generateStaticParams and ISR help SEO?
For content pages such as blog posts, services and case studies, generateStaticParams pre-renders every known slug at build time. Crawlers get fast, complete HTML and your server does almost no work per request.
To keep those pages fresh without a full rebuild, set export const revalidate = 3600 on the route, or call revalidatePath from a webhook when your CMS publishes. If only the slugs you generate should exist, set dynamicParams = false so anything else returns a 404 instead of an empty page.
How do you handle metadata at scale?
Generate it from data. Fetch the page content once, use it for both the page and generateMetadata, and every new page built from your components gets a title, description, canonical and Open Graph tags automatically. For a single page, the free Meta Tag Generator drafts title and description options sized for Google from your page content.
Event and campaign pages deserve their own care. Give each one its own title, description and Open Graph image, so shares on LinkedIn and in chat apps show the right headline. An opengraph-image.tsx file in the route folder can generate that image from the page's data. If campaign pages ship from the same codebase as the main site, they inherit the whole SEO setup with no extra work.
How do you add hreflang to a Next.js site?
If you serve different versions of a page to the UK, the US and India, for example with different pricing, currency or spelling, declare them with alternates.languages in the metadata:
export const metadata = {
alternates: {
canonical: "https://example.com/uk/pricing",
languages: {
"en-GB": "https://example.com/uk/pricing",
"en-US": "https://example.com/us/pricing",
"en-IN": "https://example.com/in/pricing",
"x-default": "https://example.com/pricing",
},
},
};
Every version must list all the others and itself, or Google ignores the set. If one English site serves every market with the same content, skip hreflang entirely.
Which structured data should a Next.js site include?
Start with what describes your business and your pages honestly:
- Organization or Person, for who is behind the site.
- BreadcrumbList, for the path to each page.
- BlogPosting or Article, for posts.
- ItemList, for listing pages.
- Product, Service or SoftwareApplication, where they fit.
A minimal version in a Next.js page looks like this:
export default function Page() {
const jsonLd = {
"@context": "https://schema.org",
"@type": "BlogPosting",
headline: "Technical SEO checklist for a Next.js website",
author: { "@type": "Person", name: "Mohit Goyal" },
};
return (
<script
type="application/ld+json"
dangerouslySetInnerHTML={{ __html: JSON.stringify(jsonLd) }}
/>
);
}
The free Schema Markup Generator writes valid JSON-LD from a plain description, and my guide to JSON-LD structured data covers which types to use where. Validate every template with Google's Rich Results Test before launch.
How do you serve real 404s and redirects?
When a slug has no content, call notFound(). That returns a true 404 status. A page that says "not found" but returns 200 is a soft 404, and Search Console will flag it and waste crawl budget on it. If pages are sitting unindexed, my post on Crawled – currently not indexed walks through the causes.
For moved URLs, add permanent entries to redirects() in next.config.js, one hop from old to new, with no chains. Pick a trailing slash policy with the trailingSlash option and stick to it, so every page has exactly one URL.
How should you handle pagination and filter URLs?
Give each paginated page, such as /blog/page/2, its own self-referencing canonical. Pointing them all at page one tells Google to ignore the posts listed deeper down.
Filters and sorting are where duplicate URLs multiply. Keep filter state in query parameters, canonical those URLs to the unfiltered page, and do not link every combination. If a filter has real search demand, such as a category, give it a clean static path with its own title and content instead.
How do you stop preview deployments being indexed?
Preview and staging URLs are public, and an indexed copy can compete with your live site. Make the rule explicit in middleware rather than relying on platform defaults:
import { NextResponse } from "next/server";
export function middleware() {
const res = NextResponse.next();
if (process.env.VERCEL_ENV !== "production") {
res.headers.set("X-Robots-Tag", "noindex, nofollow");
}
return res;
}
Mirror the same check in app/robots.ts so non-production deployments disallow crawling too.
How do you get cited by AI Overviews and answer engines?
Answer engines quote pages that answer clearly and describe the author consistently: open with the direct answer, use question-shaped headings, add a short FAQ and keep facts identical across pages, structured data and profiles. An llms.txt file adds a clean plain-text summary; this portfolio runs on TanStack Start and React and publishes one. The AI Crawler Checker shows whether your robots.txt lets AI crawlers in and whether your llms.txt is found. The full approach is in how to get cited in AI Overviews and ChatGPT and what llms.txt is and how to add it.
Related guide: Does FAQ schema still help SEO and AI search in 2026?
How do Core Web Vitals fit in?
Speed is part of technical SEO, and Google measures it through Core Web Vitals: LCP, CLS and INP. In Next.js the usual wins are:
next/imagewith correct sizes, and the hero image loaded early.- Self-hosted fonts through
next/font, to avoid layout shift from late font swaps. - Fewer third-party scripts, loaded after the page is interactive.
- Heavy media, such as a full-screen video hero, treated as progressive enhancement, so the page stays usable while it loads.
Paste a PageSpeed Insights report into the Page Speed Fix Planner for a ranked fix list, and see Next.js site failing Core Web Vitals? for the deeper fixes.
What should you check before launch?
- 1
View Source on key templates
Home, a service page, a blog post and a listing page. Content, title, canonical and JSON-LD should all be in the HTML.
- 2
Validate structured data
Run the Rich Results Test on each template.
- 3
Check robots and noindex
Production must be indexable. Previews and staging must not be.
- 4
Test redirects
Every old URL returns one 301 to the right new page.
- 5
Submit the sitemap
Add it in Search Console and inspect a few key URLs.
Moving from WordPress? The migration has its own risks, covered in how to move from WordPress to Next.js without losing SEO, and the Redirect Map Builder turns your old WordPress URLs into a ready-to-paste redirects array.
I build Next.js sites with this checklist done from day one through my Next.js and React websites service, and audit existing sites through technical SEO and Core Web Vitals. If your Next.js site isn't ranking the way it should, book a 20-minute discovery call and I'll tell you where I'd start.



