Audit checklists

Next.js SEO Checklist 2026: 15 Checks for App Router Sites

Quick answer

A Next.js SEO checklist covers the Metadata API (unique title, description and canonical per route, metadataBase set), app/robots.ts and app/sitemap.ts, server-rendered content, metadata that reaches crawlers in the head, real 404s from notFound(), permanent redirects, and next/image with the LCP image preloaded.

Next.js can produce some of the most search-friendly sites on the web, and some of the least. The same framework that server-renders a perfect page will also, with one misplaced 'use client', ship an empty shell to every crawler that doesn't run JavaScript. The difference is configuration, and most of it lives in a handful of files.

This Next.js SEO checklist is written for the App Router, which most new Next.js projects use. It sits under the main SEO audit checklist; use that for the platform-neutral checks.

Why Next.js sites need their own SEO checks

Generic crawlers read the final HTML and report what they find. They can't tell you why a canonical is missing or a title is duplicated, and on Next.js the answer is almost always in the code: a layout that sets metadata the page never overrides, a generateMetadata that throws for some slugs, a sitemap built from a stale list.

Three Next.js behaviours deserve particular attention in 2026.

Rendering choices. Server Components render on the server by default, which is good for SEO. Content inside Client Components that fetches data in the browser is not in the initial HTML. Google will usually render it; most AI crawlers will not.

Streaming metadata. For bots Next.js believes can execute JavaScript, generateMetadata output can be streamed and end up in the <body> rather than the <head>. Next.js decides which bots are "HTML-limited" from the user agent, and you can change the list with the htmlLimitedBots option. Prerendered pages are not affected.

Version changes. Next.js 16 deprecated the priority prop on next/image in favour of preload. Checklists and code snippets written for earlier versions will flag the wrong thing.

Insight: In Next.js, permanent: true in redirects() returns a 308, not a 301. Google treats a 308 the same as a 301, so it is fine for SEO, but don't let an audit tool that only looks for 301 convince you the redirects are broken.

How to audit a Next.js site in 7 steps

  1. Run curl -s https://yoursite.com/some-page | grep -iE '<title|rel="canonical"|<h1' to see what the server sends before any JavaScript runs.
  2. Open the root layout.tsx and check metadataBase, the title template and default metadata.
  3. Open one route of each type (static, dynamic, catch-all) and check it exports its own metadata or generateMetadata with a canonical.
  4. Open app/robots.ts and app/sitemap.ts and confirm they behave differently in production and preview environments.
  5. Request a dynamic route with a slug that doesn't exist and confirm the status code is 404.
  6. Check next.config redirects, the trailingSlash setting and any middleware that rewrites or redirects.
  7. Run PageSpeed Insights on the homepage and one dynamic page to check the LCP image is preloaded and fonts don't shift the layout.

The 15-point Next.js SEO checklist

  1. Every route has its own title and description through metadata or generateMetadata. Routes that inherit the layout's defaults produce duplicate titles across the site.
  2. metadataBase is set in the root layout to the production URL, so relative canonical and Open Graph URLs resolve correctly.
  3. Canonical URLs set with alternates: { canonical: '/path' }, self-referencing on indexable pages.
  4. A title template in the root layout (for example '%s | Brand') so page titles stay short and keyword-first.
  5. app/robots.ts blocks non-production environments and allows production. Read an environment variable rather than hard-coding.
  6. app/sitemap.ts lists only canonical, indexable URLs from the same data source as the pages. Use generateSitemaps past 50,000 URLs.
  7. Content is server-rendered. Keep text, headings and links in Server Components; reserve 'use client' for interactive parts.
  8. Metadata reaches crawlers in the <head>. Check the raw HTML for each crawler type you care about; adjust htmlLimitedBots if needed.
  9. Unknown slugs return 404. Call notFound() when data is missing, so empty pages don't become soft 404s.
  10. Redirects are permanent where they should be: permanent: true (308) for moved pages, and no chains through middleware.
  11. Trailing slashes are consistent. Pick trailingSlash: true or false and make sure links, canonicals and the sitemap all match.
  12. next/image used with width, height and sizes, and the LCP image preloaded (preload in Next.js 16, priority before).
  13. next/font used so web fonts load without layout shift.
  14. Navigation uses next/link, which renders real <a href> elements crawlers can follow; avoid router.push for plain links.
  15. JSON-LD rendered in a Server Component as a <script type="application/ld+json">, with < escaped in the serialised data.

Check your own page

Seotific audits a URL against the keyword it should rank for and lists the fixes in order of impact. Free plan, no card.

Run a free audit

Rendering: what crawlers actually receive

The core Next.js SEO question is what is in the HTML the server sends. The App Router gives you several ways to render a route, and they are not equal for search:

Rendering modeContent in initial HTMLGoogleAI crawlers (no JavaScript)Seotific's role
Static (prerendered at build)YesFineFineAudits the HTML as served
Dynamic server renderingYesFineFineSame
Incremental static regenerationYes, possibly staleFineFineRe-audits show when content changes
Server Component with streaming SuspensePartly; streamed in chunksUsually finePartial; depends on what streamsCompares raw and rendered HTML
Client Component fetching on mountNo, placeholder onlyRendered laterNot seenFlags text, H1 or title missing from raw HTML

The practical rule: anything you want ranked or cited should be in a Server Component that fetches its data on the server. A product description loaded with useEffect is a product page with no description to most crawlers.

The method for checking this on any site, with the tell-tale signs of client-only content, is in the JavaScript SEO rendering check guide.

robots.ts and sitemap.ts patterns that avoid mistakes

Both files are code, so they can be wrong in ways a static file can't. Two patterns prevent most problems.

robots.ts by environment. Preview deployments are public URLs. Vercel adds a noindex header to preview deployments by default, but other hosts may not, and a custom domain pointed at a preview branch gets no protection. Base the rules on an environment variable:

// app/robots.ts
import type { MetadataRoute } from 'next'

export default function robots(): MetadataRoute.Robots {
  const isProd = process.env.APP_ENV === 'production'
  return isProd
    ? { rules: [{ userAgent: '*', allow: '/', disallow: ['/api/'] }],
        sitemap: 'https://www.example.com/sitemap.xml' }
    : { rules: [{ userAgent: '*', disallow: '/' }] }
}

sitemap.ts from the same query as the pages. If the blog index reads published posts from the CMS, the sitemap should call the same function. A sitemap built from a separate list drifts: it keeps deleted posts (404s in the sitemap) and misses new ones. Include lastModified from the content's real update date, not new Date(), or every URL looks changed on every build and Google learns to ignore the field.

Common Next.js SEO mistakes

Metadata only in the layout. Wrong: the root layout sets title: 'Acme' and no route overrides it, so 300 pages share one title. Right: a title template in the layout and a specific title in every page's metadata or generateMetadata.

generateMetadata that fails quietly. Wrong: the function fetches the product, the fetch returns null for some slugs, and the page renders with fallback metadata and an empty body. Right: call notFound() when the data is missing, so the URL returns 404 instead of a thin 200.

Canonical built from the request. Wrong: building the canonical from the incoming URL, so ?utm_source= and ?ref= versions each canonicalise to themselves. Right: build it from the route's known path, ignoring query strings unless they change the content.

Preview deployments on a custom domain. Wrong: pointing staging.example.com at a preview branch and assuming the host's default noindex covers it. Right: the environment-aware robots.ts above, plus a noindex header for every non-production host.

Tools for a Next.js SEO audit

curl or view-source for the raw HTML; it is the fastest test of what non-rendering crawlers see. Search Console's URL Inspection shows Google's rendered HTML and the canonical it chose. Lighthouse in Chrome DevTools covers Core Web Vitals in the lab and flags missing metadata.

Seotific audits each URL twice, as raw HTML and rendered in a real browser, and reports when the title, H1, canonical or main text only appears after JavaScript runs. It flags duplicate titles from inherited layout metadata, soft 404s from unhandled slugs and redirect chains through middleware, and weights each one by impact. For the crawl-level checks that apply to every framework, use the technical SEO audit checklist; for a CMS-based site, compare with the Webflow SEO audit checklist.

Frequently asked questions

Is Next.js good for SEO?

Yes, when content is server-rendered. Next.js supports static generation, server rendering and a Metadata API for titles, canonicals and structured data. Problems come from rendering content only in the browser, missing per-route metadata and preview deployments left indexable.

How do I add a canonical tag in Next.js?

In the App Router, set alternates: { canonical: '/your-path' } in the route's metadata or generateMetadata, with metadataBase set in the root layout so the path resolves to a full URL. In the Pages Router, add a <link rel="canonical"> with next/head.

Why is my Next.js metadata in the body instead of the head?

Next.js can stream generateMetadata output for clients it believes run JavaScript, so the tags arrive in the <body>. Crawlers on the HTML-limited list get blocking metadata in the <head>. Adjust the htmlLimitedBots option if a crawler you care about receives streamed metadata, or prerender the page.

Does Next.js generate a sitemap automatically?

Not by default. Add app/sitemap.ts (or a static app/sitemap.xml) and Next.js serves it at /sitemap.xml. Generate the URL list from the same data source as your pages so deleted pages drop out and new ones appear.

Should I use 301 or 308 redirects in Next.js?

Use permanent: true, which returns 308. Google treats 308 like 301 and passes signals the same way. Use a temporary redirect (307) only when the move really is temporary, such as a campaign page.

How do I check what Googlebot sees on a Next.js page?

Use URL Inspection in Search Console and view the crawled page's HTML, which shows the rendered result. Then compare it with curl output of the same URL. If the title, H1 or main text appears only in the rendered version, move it into a Server Component.

Check your own page

Seotific audits a URL against the keyword it should rank for and lists the fixes in order of impact. Free plan, no card.

Run a free audit