Next.js SEO
How to make a Next.js site crawlable, indexable, and rankable — the two routers, rendering modes (SSG/SSR/ISR/Server Components), the App Router Metadata API, sitemap.ts, robots.ts, next/image, next/link, and the mistakes that quietly break it.
Next.js solves the hardest JavaScript SEO problems by default — if you use it right. The App Router's Server Components and SSG/ISR put content in the HTML so there's no render-queue delay; the native Metadata API resolves titles, canonicals, and Open Graph on the server; sitemap.ts and robots.ts are file conventions. The framework gives you the infrastructure but writes none of your tags for you. The failures are predictable: missing metadataBase, no priority on the LCP image, metadata exported from a Client Component (silently does nothing), and 404 views returning 200.
TL;DR — Next.js is one of the best frameworks for SEO when you use it right. Build your pages on the server or at build time (not entirely in the browser), set a unique title, description, and canonical on every page, and use the built-in
next/imageandnext/linkcomponents. Next.js gives you all the tools — but it won’t write your SEO tags for you.
What Next.js SEO is
Next.js is a popular framework built on React for making websites and web apps. Because it’s React under the hood, a lot of the page can be built with JavaScript — and that’s where SEO questions come in. Next.js SEOMaking sure search engines can crawl, render, and index content that depends on JavaScript. is just the practice of making sure search engines can crawl, render, and indexStoring a crawled page in the search index so it can appear in results. Crawled is not the same as indexed — Google selects what to keep, and indexing isn't guaranteed. a Next.js site, and using the framework’s built-in features to do that well.
The good news: Next.js can build your pages ahead of time or on the server, so search engines get the finished HTML right away. That avoids the biggest risk with JavaScript sites — content that only shows up after scripts run. (For the broader picture, see JavaScript SEOMaking sure search engines can crawl, render, and index content that depends on JavaScript..)
The one big decision: where the page gets built
When someone — or GooglebotGooglebot is Google's web crawler — the software that fetches pages so Google can index and rank them. It comes in two variants, Googlebot Smartphone (primary, under mobile-first indexing) and Googlebot Desktop, and runs an evergreen Chromium renderer. — asks for a page, where does the finished HTML come from? Next.js gives you a few options:
- At build time (Static / SSG) — the page is built into a plain HTML file ahead of time. Fast, and search engines get everything immediately.
- On the server, per request (SSR) — the server builds the full page each time. Also search-friendly, and always fresh.
- A mix (ISR) — static pages that refresh on a timer. A good default for most content.
- In the browser (Client-Side / CSR) — the server sends a near-empty shell and JavaScript fills it in. This is the risky one for SEO; avoid it for your main content.
The modern App Router uses Server Components by default, which means your content ends up in the HTML automatically — a great starting point for SEO.
The simple checklist
- Give every page a unique title and description.
- Set a canonical URLHow search engines pick one canonical URL among duplicates and consolidate signals onto it. on every page.
- Use the
next/imagecomponent for images (it stops the page from jumping around as images load, and makes them smaller). - Use
next/linkfor internal linksAn internal link is a hyperlink from one page on a website to another page on the same website. Internal links help search engines discover your pages and pass ranking signals (PageRank and anchor-text context) between them. (it creates real links Google can follow). - Don’t put your main content behind client-side renderingTurning HTML, CSS, and JavaScript into the final visual page and DOM..
- Create a sitemapA sitemap is a file that lists the pages, images, videos, and other files on your site so search engines can discover them. It helps discovery, but submitting a sitemap doesn't guarantee crawling or indexing. and a robots.txtA plain-text file at the root of a host that tells crawlers which URLs they may and may not request. It controls crawling, not indexing — a blocked URL can still be indexed if it's linked from elsewhere. (Next.js has simple file-based ways to do both). Evidence for this claim Next.js App Router supports special metadata files for sitemap and robots output. Scope: Next.js App Router file conventions. Confidence: high · Verified: Next.js: Metadata files
The thing people get wrong
“Next.js handles SEO automatically.” It doesn’t — not the parts that matter. Next.js gives you the machinery (a metadata system, sitemapA sitemap is a file that lists the pages, images, videos, and other files on your site so search engines can discover them. It helps discovery, but submitting a sitemap doesn't guarantee crawling or indexing. and robots conventions, an image component), but you still have to write your titles, descriptions, and canonicals yourself. Evidence for this claim Next.js provides metadata APIs, but developers supply page-specific metadata values. Scope: Next.js App Router metadata and generateMetadata APIs. Confidence: high · Verified: Next.js: Metadata and OG images Every page needs its own. A site where every page shares one title, or has no canonical, is the most common way a Next.js build underperforms.
Want the deeper version — the two routers, the Metadata API, metadataBase, the LCPLargest Contentful Paint — render time of the largest visible image or text block, relative to when the page started loading. ≤2.5 s (at the 75th percentile) is good.
image trick, and the mistakes that quietly break indexingStoring a crawled page in the search index so it can appear in results. Crawled is not the same as indexed — Google selects what to keep, and indexing isn't guaranteed.? Switch to the Advanced
tab.
TL;DR — Next.js solves the hardest JavaScript SEOMaking sure search engines can crawl, render, and index content that depends on JavaScript. problems by default when you use it right. The App Router defaults to Server Components and supports SSG/SSR/ISR — all of which ship rendered HTML, so there’s no render-queue delay. Set metadata with the native Metadata API (
metadata/generateMetadata), and remembermetadataBaseor your canonicals and OG images go relative. Useapp/sitemap.tsandapp/robots.ts, pre-build dynamic routes withgenerateStaticParams, setpriorityon the LCPLargest Contentful Paint — render time of the largest visible image or text block, relative to when the page started loading. ≤2.5 s (at the 75th percentile) is good. image, and keep links as realnext/linkanchors. The Pages Router is also SEO-capable vianext/head. The failures are predictable — and most of them aren’t Next.js’s fault, they’re yours.
Where Next.js fits
Next.js is a React framework, so everything in JavaScript SEOMaking sure search engines can crawl, render, and index content that depends on JavaScript. applies. What makes it worth its own guide is that Next.js ships first-class answers to most JS-SEO problems: server renderingTurning HTML, CSS, and JavaScript into the final visual page and DOM., static generation, a metadata system, and sitemapA sitemap is a file that lists the pages, images, videos, and other files on your site so search engines can discover them. It helps discovery, but submitting a sitemap doesn't guarantee crawling or indexing./robots conventions. The hard part isn’t whether Google can read it — Google has rendered JavaScript for years — it’s choosing the right renderingTurning HTML, CSS, and JavaScript into the final visual page and DOM. mode and not leaving the SEO basics unwired. This is a specialized case of headless CMS SEOA headless CMS decouples content storage and editing (the backend) from how that content is rendered and delivered (the frontend), serving content over an API instead of a built-in templated 'head'. Its SEO outcomes come almost entirely from how the separate frontend renders pages.: the CMSA content management system (CMS) is software that lets users create, manage, and publish digital content — like blog posts and pages — without writing raw code. WordPress, Drupal, and Joomla are the most common open-source CMS platforms. barely matters, the frontend’s rendering decisions decide everything.
One framing to keep straight: “this is a Next.js site” doesn’t tell you how any single URL is delivered. Rendering mode, cachingCaching stores a copy of a page or resource — in a browser, a CDN edge node, or a search crawler's own cache — so it can be served again without regenerating or re-downloading it. It isn't a direct ranking factor, but it feeds page speed and crawl efficiency., and Server/Client Component boundaries are set per route (sometimes per segment) — a project can mix a static marketing page, an SSR product page, and a Client Component dashboard. Don’t extrapolate one route’s behavior to “the whole app”; test the specific URL.
Two routers, two sets of mechanics
Next.js has two routers, and they handle SEO differently:
- Pages Router (the older model) — data fetching via
getStaticProps/getServerSideProps; metadata via<Head>fromnext/head(or thenext-seopackage); no Server Components. - App Router (v13+, the current and recommended approach) — React Server Components
by default; the native Metadata API (
metadataexport /generateMetadata); file conventions forapp/sitemap.tsandapp/robots.ts;generateStaticParamsfor dynamic routes. Evidence for this claim The App Router uses Server Components and supports generateStaticParams plus metadata file conventions. Scope: Current Next.js App Router behavior; route rendering can become dynamic based on APIs used. Confidence: high · Verified: Next.js: Server and Client Components Next.js: generateStaticParams
Both can rank well. The App Router gives you a cleaner, integrated metadata system (no
next/head juggling) and Server Components out of the box, which is why I’d reach for it
on a new build. But “App Router or you can’t do SEO” is a myth — plenty of Pages Router
sites rank fine.
Rendering modes and what each means for SEO
How Google handles JavaScript is a three-phase pipeline — crawl, then a deferred render wave, then indexStoring a crawled page in the search index so it can appear in results. Crawled is not the same as indexed — Google selects what to keep, and indexing isn't guaranteed.. “All pages with a 200 HTTP status codeAn HTTP status code is the three-digit number a server returns with every response to tell a browser or crawler what happened to its request — success, redirect, client error, or server error. For SEO the code matters as much as the content: it tells Google and Bing whether to index a page, follow a redirect, retry later, or drop the URL from the index. are sent to the rendering queue.” The whole game in Next.js is choosing a mode that puts your content in the HTML before that render wave, so there’s nothing to wait for. Evidence for this claim Google crawls, renders, and indexes JavaScript pages, and successful pages can enter the rendering queue. Scope: Google Search processing, not a promise that a URL will be indexed. Confidence: high · Verified: Google: JavaScript SEO basics
- Static Site Generation (SSG) — pages pre-rendered at build time. HTML is
immediately available with no render-queue risk. Best for content that doesn’t change
every minute.
generateStaticParams()(App Router) /getStaticPaths()(Pages Router) decides which dynamic routes get pre-built. - Incremental Static Regeneration (ISR) — static pages that revalidate after a set
interval (
export const revalidate = 3600). CrawlersA crawler — also called a spider or bot — is an automated program that fetches web pages, extracts their links, and queues new URLs to visit. Search engines use crawlers to discover and download content for their index. get static HTML with low TTFBTime to First Byte — the time from the start of a request to when the first byte of the response arrives. It's a diagnostic metric (not a Core Web Vital) and a major input to FCP and LCP; ≤0.8 s is good. and the content stays fresh. A strong default — with one trap: after the window expires the next request (possibly GooglebotGooglebot is Google's web crawler — the software that fetches pages so Google can index and rank them. It comes in two variants, Googlebot Smartphone (primary, under mobile-first indexing) and Googlebot Desktop, and runs an evergreen Chromium renderer.) still gets the stale page; the fresh version serves on the request after that. For genuinely volatile data (prices, stock), SSR is safer. - Server-Side Rendering (SSR) — HTML rendered per request. CrawlersA crawler — also called a spider or bot — is an automated program that fetches web pages, extracts their links, and queues new URLs to visit. Search engines use crawlers to discover and download content for their index. get fully
rendered HTML immediately; the tradeoff is server latency, so watch TTFB and LCPLargest Contentful Paint — render time of the largest visible image or text block, relative to when the page started loading. ≤2.5 s (at the 75th percentile) is good..
export const dynamic = 'force-dynamic'or using request-time APIs (cookies, headers) opts a route into SSR. - React Server Components (App Router default) — render on the server and send HTML;
no JavaScript ships for the component itself. Content is in the initial response with no
hydrationActivating server-rendered HTML in the browser by attaching JavaScript handlers. gap. This is the best default for SEO. Interactivity lives in Client Components
marked
'use client'. - Client-Side Rendering (CSR) — rendered entirely in the browser. Googlebot can index
it after the render wave (median ~10 seconds, but the 90th percentile stretches to
hours), and other crawlers — BingbotBingbot is Microsoft Bing's primary web crawler — the bot that discovers, fetches, and renders pages to build the Bing index. That index also powers Yahoo, DuckDuckGo, Ecosia, and Microsoft Copilot, so Bingbot's reach is far wider than Bing's own search-market share., AI bots, social preview bots — may get an empty
page. In the App Router, CSR is opt-in (
'use client'); in the Pages Router, avoid fetching primary content inuseEffect. Don’t use it for content you want ranked.
A reminder I keep coming back to: “Googlebot can render it” is not the same as “you should make Googlebot render it.” Rendering is expensive, deferred, and not universal across crawlers.
The Metadata API (App Router)
The Metadata API is Server Component only — metadata resolves on the server before
the page renders, so it lands in the initial HTML. Export metadata from layout.js or
page.js:
export const metadata: Metadata = {
title: 'My Page',
description: 'Page description',
}Or, when the tags depend on fetched data, use generateMetadata():
export async function generateMetadata({ params }) {
const post = await getPost(params.slug)
return { title: post.title, description: post.description }
}The fields that matter for SEO:
title— supports a string, a template ('%s | Brand'), a default, and an absolute override. Set the template once in the root layout and per-page titles inherit it.description,alternates.canonical(the correct way to set a canonical in the App Router),openGraph(images must resolve to absolute URLs),twitter(also used by LinkedIn and Slack previews), androbots(index/follow plus googleBot-specific directives like max-snippet, max-image-preview).metadataBase— required for canonical and OG image URLs to resolve correctly. Forgetting it is the single most common Next.js metadata bug: relative URLs leak into your canonical and Open GraphOpen Graph (OG) tags are `<meta>` elements in a page's head, defined by the Open Graph protocol (ogp.me, created by Facebook), that describe a page as a shareable object — its title, description, image, URL, and type. They control how a link preview card looks when the page is shared on Facebook, LinkedIn, Slack, Discord, WhatsApp, and iMessage. They are not a direct Google ranking factor, though Google reads og:title, og:image, and og:site_name as inputs to how a result appears. tags, breaking social previews and muddying canonical signals.
A title-template example:
// app/layout.tsx
export const metadata: Metadata = {
metadataBase: new URL('https://example.com'),
title: { template: '%s | Brand Name', default: 'Brand Name' },
}
// app/blog/page.tsx
export const metadata: Metadata = { title: 'My Blog Post' }
// Output: <title>My Blog Post | Brand Name</title>Two gotchas. First, metadata is shallowly merged from layout to page — a nested
object like openGraph defined in a child segment replaces the parent’s entirely, so
a page-level openGraph: { title: 'Home' } quietly drops any openGraph.images set in
the layout. Second — and this one bites people — metadata only works in Server
Components. Export it from a 'use client' file and it silently does nothing.
Streaming metadata. For dynamically rendered pages, generateMetadata can stream the
metadata after the initial HTML. Googlebot executes JavaScript and inspects the full DOM,
so streamed metadata works for Google. But Next.js detects “HTML-limited bots” —
Bingbot, Twitterbot, Slackbot, facebookexternalhit — and ships them blocking
metadata in the <head> instead. Per the Next.js docs, “streaming metadata is disabled
for bots and crawlers that expect metadata to be in the <head> tag.” This is automatic;
no configuration needed. It’s a detail almost no competing guide covers, and it’s why
streaming metadata isn’t a risk for the bots that can’t wait for it. Prerendered pages
are a different case entirely — metadata there resolves at build time, so there’s no
stream to worry about. This behavior is version-specific (current as of Next.js 16.2.10);
re-check the generateMetadata docs when you upgrade. And because delivery paths differ
by entry point, verify metadata two ways, not one: a direct/production request (curl -I
or View Source) and a client-side navigation to the same route — the head can update
differently between the two.
Metadata with next/head (Pages Router)
On the Pages Router, metadata lives in <Head> from next/head:
import Head from 'next/head'
export default function Page() {
return (
<>
<Head>
<title>My Page | Brand</title>
<meta name="description" content="Description" />
<link rel="canonical" href="https://example.com/my-page" />
</Head>
{/* page content */}
</>
)
}Set title and description per page (not just in _app.js), and put a canonical on
every page including paginated variants. The next-seo package standardizes this with a
<NextSeo> component and structured-data helpers. Migrating to the App Router mostly
means trading next/head and next-seo for the native metadata export.
Do you need the next-seo package? It’s a third-party plugin (not part of Next.js
itself) — actively maintained, at v7.2.0 as of this writing, not archived. Its own docs
are explicit about where it fits: for standard meta tagsMeta tags are HTML elements in a page's head that pass metadata about the page to search engines and browsers. For SEO only a few matter — the title element, the meta description, and the robots meta tag — while meta keywords and most others are ignored. on the App Router, the package’s
README recommends using Next.js’s built-in generateMetadata/metadata export instead of
<NextSeo>; on the Pages Router, <NextSeo> is still a reasonable convenience layer
over next/head. The one App Router use case the package still covers is its JSON-LDJSON-LD (JavaScript Object Notation for Linked Data) is a script-based structured data format, typically paired with the schema.org vocabulary to describe page content for search engines and AI systems. Google recommends it over Microdata and RDFa because it's the easiest format to implement and maintain at scale — but all three work, and structured data isn't a ranking signal.
helper components (ArticleJsonLd, FAQPageJsonLd, etc. with useAppDir), which some
teams prefer over hand-rolling <script type="application/ld+json">. Bottom line: on a new
App Router build, reach for the native Metadata API first — the package is optional, not a
requirement, and its own maintainers say so.
Sitemaps
In the App Router, app/sitemap.ts is a file convention that outputs /sitemap.xml:
import type { MetadataRoute } from 'next'
export default function sitemap(): MetadataRoute.Sitemap {
return [
{ url: 'https://acme.com', lastModified: new Date(), priority: 1 },
{ url: 'https://acme.com/blog', lastModified: new Date(), priority: 0.8 },
]
}For large sites, generateSitemaps() shards into multiple files (Google’s limit is
50,000 URLs per sitemapA sitemap is a file that lists the pages, images, videos, and other files on your site so search engines can discover them. It helps discovery, but submitting a sitemap doesn't guarantee crawling or indexing.), each served at /.../sitemap/[id].xml. The sitemap output also
supports image sitemapsAn image sitemap is a sitemap (or an extension added to an existing sitemap) that lists the images on your pages using Google's image namespace, helping Google discover images it might otherwise miss., video sitemapsA video sitemap is an XML sitemap (or mRSS feed) that uses the video:video extension to tell Google about videos hosted on your pages — the landing page, a thumbnail, a title, a description, and a link to the video file or player. It helps Google discover and surface video content, but it doesn't guarantee indexing., and localized alternates.languages. On the
Pages Router, use next-sitemap or generate pages/sitemap.xml.js with
getServerSideProps.
robots.txt
app/robots.ts generates your robots file programmatically:
export default function robots(): MetadataRoute.Robots {
return {
rules: [{ userAgent: '*', allow: '/', disallow: '/private/' }],
sitemap: 'https://acme.com/sitemap.xml',
}
}Per-user-agent rules and multiple sitemaps are supported. The Pages Router uses a static
public/robots.txt. The rule you cannot get wrong on either router: never disallow your
JavaScript or CSS — Google won’t render from blocked files, and on a JS framework that
can blank out the page entirely.
next/image and Core Web Vitals
next/image is one of the strongest reasons to use the framework for SEO. It lazy-loads
below-the-fold images, requires width/height (or fill) so it reserves space and
prevents layout shift / CLSCumulative Layout Shift — a unitless score for unexpected visual movement, taken from the largest burst (session window) of layout shifts, not the lifetime sum. ≤0.1 is good., serves WebP/AVIF automatically, and emits a proper
srcset from the sizes prop. The single most important CWV optimization is the
priority prop on your hero / above-the-fold image, which preloads it for a faster LCP:
<Image src="/hero.jpg" width={1200} height={630} priority alt="Hero" />Forgetting priority on the LCP image is the most common Next.js CWV mistake — and CWV
problems are widespread on real Next.js sites (see the Stats tab for the Salt Agency
data). alt is required: empty for decorative images, descriptive for content images.
next/link and internal linking
next/link renders standard <a href> anchors in the HTML, so Google follows them
normally, and it adds client-side navigation plus background prefetching of in-viewport
links in production. The SEO rule is simple: use next/link for internal linksAn internal link is a hyperlink from one page on a website to another page on the same website. Internal links help search engines discover your pages and pass ranking signals (PageRank and anchor-text context) between them., and never
substitute an onClick handler or JavaScript navigation that doesn’t produce a real
anchor — those links aren’t crawlable. Use prefetch={false} on low-value links to save
bandwidth if you need to.
Dynamic routes and generateStaticParams
generateStaticParams() tells Next.js which dynamic routes to pre-render at build time:
// app/blog/[slug]/page.tsx
export async function generateStaticParams() {
const posts = await getPosts()
return posts.map((post) => ({ slug: post.slug }))
}Pages built this way are fully static HTML — best for SEO. Without it, dynamic routes are
rendered on-demand (SSR) by default, which is fine but reintroduces server latency. Combine
it with revalidate (ISR) for content that updates regularly. Make sure all important
dynamic URLs are in generateStaticParams so nothing waits on the render queue.
Structured data (JSON-LD)
The Metadata API has no structured-data field — you inject JSON-LD as a <script> in a
Server Component, which keeps it in the server-rendered HTML at zero client-bundle cost:
const jsonLd = { '@context': 'https://schema.org', '@type': 'Article', /* … */ }
return <script type="application/ld+json"
dangerouslySetInnerHTML={{ __html: JSON.stringify(jsonLd) }} />Article/BlogPosting, BreadcrumbList, Product, and FAQPage are the usual types. Validate with the Rich ResultsRich results (formerly 'rich snippets') are enhanced search listings — stars, images, prices, breadcrumbs, video thumbnails, and more — that Google and Bing build from structured data. They're a display feature, not a ranking factor, and eligibility never guarantees they'll show. Test after any rendering change.
Common Next.js SEO mistakes
Drawn from real audits and the patterns above:
- Missing or relative canonicals — often from a forgotten
metadataBase, which also breaks OG image URLs. - No
priorityon the LCP image — the biggest CWV miss. - 404 views returning
200— use the built-innotFound()to return a real status; soft 404sA soft 404 is a URL that returns a success status code (usually 200 OK) even though the page is empty, missing, or shows a 'not found' message. It isn't a status code a server sends — it's a label search engines apply after comparing the response code against the rendered content, and they treat the page like a 404 for indexing. are rampant on Next.js sites. metadataexported from a Client Component — silently does nothing; it’s Server Component only.- CSR for primary content — fetching critical content in
useEffectmeans non-Google crawlers get empty pages. - Hash (
#) routing instead of the History API — those views aren’t separately crawlable. - Not exporting
generateStaticParams— dynamic routes render on-demand instead of being pre-built. openGraphoverwritten by layout inheritance — child segments replace, not merge.- Blocking JS/CSS in robots.txtA plain-text file at the root of a host that tells crawlers which URLs they may and may not request. It controls crawling, not indexing — a blocked URL can still be indexed if it's linked from elsewhere. or via a Content Security Policy that stops Googlebot’s headless Chrome from loading scripts — test with URL Inspection.
Deployment notes
Next.js is built by Vercel; hosting there gives tight integration (edge CDN for static and
ISR pages, good TTFB) but isn’t required. Define permanent redirectsA redirect sends browsers and crawlers from a requested URL to a different one. An HTTP redirect specifically is a 3xx status code paired with a Location header; meta refresh and JavaScript redirects achieve a similar navigation without being a 3xx response themselves. Permanent redirects (301/308) are Google's signal the target should be canonical; temporary ones (302/303/307) aren't. in redirects() in
next.config.js (returns 308, or 301 with permanent: true) for reliable signaling to
crawlers, set security and caching headers via headers(), and use X-Robots-Tag response
headers for path-based noindexNoindex is a directive that tells search engines to keep a page out of their index, so it won't appear in search results. It works only on pages a crawler can actually fetch — a page blocked in robots.txt can never be noindexed. rules when per-page robots metadata is awkward.
What to check where. Cache state, status codes, redirects, and streamed metadata don’t all show up in the same test — a route can look fine in one check and still be broken in another:
| Check | Where to look | Why it can differ from what you see rendered |
|---|---|---|
| Cache/revalidation age | Response headers on a direct request (curl -I) | ISR can serve a stale page on the request right after the window expires |
| Direct HTTP status | curl -I on the production URL, not the rendered UI | A “not found” view without notFound() still returns 200 |
| Redirect behavior | The actual context that fires it — redirect() in a Server Action, a Route Handler, vs. a client onClick | Status code and response path differ by invocation context, not just the destination |
| Streamed metadata | Direct request and client-side navigation to the same route | Ordinary clients can get streamed metadata; HTML-limited bots get blocking metadata; the two paths aren’t identical |
| Client-side transitions | Navigate in-app, then re-check the <head> | A route that’s correct on first load can drift after a client transition |
None of this is guaranteed by the framework — Next.js gives you the mechanisms
(redirects(), notFound(), revalidation, streaming), but cache keys, invalidation,
preview state, and deployment configuration are still your responsibility to get right and
to test in production, not just locally.
One last note on dynamic rendering — serving prerendered HTML to bots and JavaScript to users. Google has deprecated it as a recommendation: “dynamic rendering was a workaround and not a long-term solution.” You don’t need it on Next.js anyway — SSR, SSG, ISR, and Server Components all put content in the HTML natively. Mention it so you recognize it in an audit; don’t build on it.
The happy path: App Router + Server Components + ISR + the Metadata API (with
metadataBase) + next/image with priority. Get those right and most of Next.js SEONext.js SEO is the set of practices and built-in features that make a Next.js site crawlable, indexable, and rankable — rendering mode (SSG/SSR/ISR/Server Components), the App Router Metadata API, sitemap.ts/robots.ts conventions, next/image, and next/link. is
handled.
AI summary
A condensed take on the Advanced version:
- Next.js solves most JS-SEO problems by default — if you pick the right renderingTurning HTML, CSS, and JavaScript into the final visual page and DOM. mode and wire up the basics. The framework gives you infrastructure; it writes none of your tags.
- Two routers: App Router (v13+, recommended) uses Server Components and the native
Metadata API; Pages Router uses
next/head/next-seo. Both can rank. - RenderingTurning HTML, CSS, and JavaScript into the final visual page and DOM. modes: SSG and Server Components are the best default (content in the HTML, no render-queue delay). ISR is a strong middle ground but has a stale-on-first-request-after-revalidation trap. SSR for volatile data. CSR is risky — non-Google crawlersGooglebot is Google's web crawler — the software that fetches pages so Google can index and rank them. It comes in two variants, Googlebot Smartphone (primary, under mobile-first indexing) and Googlebot Desktop, and runs an evergreen Chromium renderer. may get an empty page.
- Metadata API (App Router):
metadataexport orgenerateMetadata(), Server Component only.metadataBaseis required or canonicals and OG images go relative.openGraphin a child segment replaces the parent’s; metadata in a'use client'file does nothing. - Streaming metadata works for Google but Next.js ships blocking metadata to HTML-limited botsA crawler — also called a spider or bot — is an automated program that fetches web pages, extracts their links, and queues new URLs to visit. Search engines use crawlers to discover and download content for their index. (BingbotBingbot is Microsoft Bing's primary web crawler — the bot that discovers, fetches, and renders pages to build the Bing index. That index also powers Yahoo, DuckDuckGo, Ecosia, and Microsoft Copilot, so Bingbot's reach is far wider than Bing's own search-market share., Twitterbot, Slackbot, facebookexternalhit) automatically.
- File conventions:
app/sitemap.ts(withgenerateSitemaps()for 50k+ URLs) andapp/robots.ts. Never block.js/.css. next/imageprevents CLSCumulative Layout Shift — a unitless score for unexpected visual movement, taken from the largest burst (session window) of layout shifts, not the lifetime sum. ≤0.1 is good., serves WebP/AVIF; setpriorityon the LCPLargest Contentful Paint — render time of the largest visible image or text block, relative to when the page started loading. ≤2.5 s (at the 75th percentile) is good. image — the top CWVGoogle's three real-user UX metrics — LCP (loading), INP (responsiveness), and CLS (visual stability) — used by Google's ranking systems, with no official weight attached, measured on field data. win.next/linkrenders real crawlable<a href>anchors and prefetches.generateStaticParamspre-builds dynamic routes; without it they render on-demand.- JSON-LDJSON-LD (JavaScript Object Notation for Linked Data) is a script-based structured data format, typically paired with the schema.org vocabulary to describe page content for search engines and AI systems. Google recommends it over Microdata and RDFa because it's the easiest format to implement and maintain at scale — but all three work, and structured data isn't a ranking signal. is injected as a
<script>in a Server Component (no Metadata API field). - The
next-seopackage is optional, not required. It’s actively maintained third-party software (v7.2.0, not archived), and its own docs recommend the built-ingenerateMetadata/metadataexport for App Router meta tagsMeta tags are HTML elements in a page's head that pass metadata about the page to search engines and browsers. For SEO only a few matter — the title element, the meta description, and the robots meta tag — while meta keywords and most others are ignored. —<NextSeo>remains useful mainly on the Pages Router, or for its JSON-LDJSON-LD (JavaScript Object Notation for Linked Data) is a script-based structured data format, typically paired with the schema.org vocabulary to describe page content for search engines and AI systems. Google recommends it over Microdata and RDFa because it's the easiest format to implement and maintain at scale — but all three work, and structured data isn't a ranking signal. helper components. - Rendering mode is set per route, not per project — don’t assume one URL’s behavior from another’s; test the specific route (direct request and client navigation both).
- Top mistakes: missing
metadataBase, no LCPLargest Contentful Paint — render time of the largest visible image or text block, relative to when the page started loading. ≤2.5 s (at the 75th percentile) is good.priority, 404s returning200(usenotFound()),metadatain a Client Component, CSR primary content. - Dynamic rendering is deprecated — you don’t need it; SSR/SSG/ISR/Server Components cover it.
Official documentation
Primary-source documentation from Next.js and the search engines.
Next.js
- Metadata and OG Images — the App Router metadata system,
metadataBase, and OG image generation. - generateMetadata API Reference — static
metadatavs.generateMetadata, streaming metadata, and the HTML-limited botsA crawler — also called a spider or bot — is an automated program that fetches web pages, extracts their links, and queues new URLs to visit. Search engines use crawlers to discover and download content for their index. list. - sitemap.xml File Convention —
app/sitemap.ts,generateSitemaps(), image/video/localized sitemapsA sitemap is a file that lists the pages, images, videos, and other files on your site so search engines can discover them. It helps discovery, but submitting a sitemap doesn't guarantee crawling or indexing.. - robots.txt File Convention —
app/robots.ts, per-user-agentA user agent is the HTTP request header a client (browser, crawler, or bot) sends to identify itself. For crawlers, a short user-agent token — a substring of that string — is what robots.txt rules actually target. rules, and multiple sitemapsA sitemap is a file that lists the pages, images, videos, and other files on your site so search engines can discover them. It helps discovery, but submitting a sitemap doesn't guarantee crawling or indexing.. - Learn: SEO — Next.js’s own SEO learning path (introductory; partly predates the App Router).
- Understand JavaScript SEO Basics — the crawl → render → indexStoring a crawled page in the search index so it can appear in results. Crawled is not the same as indexed — Google selects what to keep, and indexing isn't guaranteed. pipeline, the 200-status renderingTurning HTML, CSS, and JavaScript into the final visual page and DOM. queue, soft 404sA soft 404 is a URL that returns a success status code (usually 200 OK) even though the page is empty, missing, or shows a 'not found' message. It isn't a status code a server sends — it's a label search engines apply after comparing the response code against the rendered content, and they treat the page like a 404 for indexing. in SPAs, canonicals, and the History API.
- Dynamic Rendering (deprecated workaround) — why Google deprecated it and what to use instead (SSR, static renderingTurning HTML, CSS, and JavaScript into the final visual page and DOM., hydrationActivating server-rendered HTML in the browser by attaching JavaScript handlers.).
- In-Depth Guide to How Google Search Works — where rendering sits in crawl → index → serve.
Bing / Microsoft
- IndexNow / indexnow.org — the push protocol to wire to a Next.js publish/revalidation event.
Quotes from the source
On-the-record statements from Google, Next.js, and Google reps. Each link is a deep link that jumps to the quoted passage on the source page.
Google — how JavaScript pages are processed
- “All pages with a 200 HTTP status codeAn HTTP status code is the three-digit number a server returns with every response to tell a browser or crawler what happened to its request — success, redirect, client error, or server error. For SEO the code matters as much as the content: it tells Google and Bing whether to index a page, follow a redirect, retry later, or drop the URL from the index. are sent to the renderingTurning HTML, CSS, and JavaScript into the final visual page and DOM. queue, no matter whether JavaScript is present on the page.” — Google Search Central docs. Jump to quote
Google — dynamic renderingTurning HTML, CSS, and JavaScript into the final visual page and DOM. is deprecated
- “Dynamic rendering was a workaround and not a long-term solution for problems with JavaScript-generated content in search engines.” — Google Search Central docs. Jump to quote
- “…creates additional complexities and resource requirements.” — Google Search Central docs. Jump to quote
Next.js — streaming metadata and HTML-limited botsA crawler — also called a spider or bot — is an automated program that fetches web pages, extracts their links, and queues new URLs to visit. Search engines use crawlers to discover and download content for their index.
- “Streaming metadata is disabled for bots and crawlersA crawler — also called a spider or bot — is an automated program that fetches web pages, extracts their links, and queues new URLs to visit. Search engines use crawlers to discover and download content for their index. that expect metadata to be in the
<head>tag (e.g. Twitterbot, Slackbot, BingbotBingbot is Microsoft Bing's primary web crawler — the bot that discovers, fetches, and renders pages to build the Bing index. That index also powers Yahoo, DuckDuckGo, Ecosia, and Microsoft Copilot, so Bingbot's reach is far wider than Bing's own search-market share.).” — Next.js docs,generateMetadata. Jump to quote
John Mueller, Google (via Search Engine Journal coverage)
- On the growing role of JavaScript in SEO: “You’re going to run into significantly more JavaScript over the next years than in the 2-ish decades in SEO before. If you’re keen on technical SEOTechnical SEO is the practice of making a site easy for search engines to crawl, render, index, and (now) be eligible for AI answers. It's the foundation that lets your content and links rank — not a ranking trick of its own., then past HTML you’re going to need to understand JS more and more.” Read the coverage
Next.js SEO checklist
A quick pass to confirm a Next.js site is crawlable, indexable, and fast:
- Primary content renders in HTML on first request (Server Components / SSG / SSR / ISR) — not only after client-side JavaScript.
- No important page depends on CSR for its main content.
- Every page has a unique title and description (use a title
templatein the root layout). -
metadataBaseis set in the root layout (App Router) so canonicals and OG images are absolute. - A canonical is set per page via
alternates.canonical(App Router) or<link rel="canonical">(Pages Router) — not a single shared homepage canonical. -
metadatais exported only from Server Components, never a'use client'file. -
generateStaticParamscovers all important dynamic routes. -
app/sitemap.ts(ornext-sitemap) outputs a current sitemapA sitemap is a file that lists the pages, images, videos, and other files on your site so search engines can discover them. It helps discovery, but submitting a sitemap doesn't guarantee crawling or indexing.;generateSitemaps()shards sites over 50k URLs. -
app/robots.ts/public/robots.txtexists and does not block.jsor.css. - Images use
next/imagewithwidth/heightorfill; the LCPLargest Contentful Paint — render time of the largest visible image or text block, relative to when the page started loading. ≤2.5 s (at the 75th percentile) is good. image haspriority; all havealt. - Internal linksAn internal link is a hyperlink from one page on a website to another page on the same website. Internal links help search engines discover your pages and pass ranking signals (PageRank and anchor-text context) between them. use
next/link(real<a href>) — noonClick-only navigation. - 404 views call
notFound()and return a real404(no soft 404sA soft 404 is a URL that returns a success status code (usually 200 OK) even though the page is empty, missing, or shows a 'not found' message. It isn't a status code a server sends — it's a label search engines apply after comparing the response code against the rendered content, and they treat the page like a 404 for indexing.). - JSON-LDJSON-LD (JavaScript Object Notation for Linked Data) is a script-based structured data format, typically paired with the schema.org vocabulary to describe page content for search engines and AI systems. Google recommends it over Microdata and RDFa because it's the easiest format to implement and maintain at scale — but all three work, and structured data isn't a ranking signal. is injected in a Server Component
<head>/body and passes the Rich Results Test. - Permanent redirectsA 301 redirect is the HTTP status code for a permanent move: it tells browsers and search engines a URL has moved for good, and it's the strongest signal for consolidating a page's ranking signals onto the new URL. Google says permanent redirects don't cause a loss in PageRank. live in
redirects()innext.config.js(308 /permanent).
The mental models
1. RenderingTurning HTML, CSS, and JavaScript into the final visual page and DOM. mode is the product. Before debugging anything in a Next.js site, answer one question: how is this route rendered? Server Components / SSG / SSR / ISR put content in the HTML and are low-risk; CSR is the risky one. Almost every Next.js SEOMaking sure search engines can crawl, render, and index content that depends on JavaScript. problem resolves to this.
2. The framework gives infrastructure, not content. Next.js ships the Metadata API, sitemapA sitemap is a file that lists the pages, images, videos, and other files on your site so search engines can discover them. It helps discovery, but submitting a sitemap doesn't guarantee crawling or indexing./robots conventions, and the Image component — but it writes none of your titles, descriptions, canonicals, or structured dataStructured data is a standardized way of labeling page content (using the schema.org vocabulary in JSON-LD, Microdata, or RDFa) so search engines can understand its meaning. It's not a direct ranking factor — its value is rich results and entity understanding.. “Next.js handles SEO automatically” is the most expensive myth here.
3. One source of truth for URLs.
Set metadataBase once and build canonicals, OG images, and sitemapA sitemap is a file that lists the pages, images, videos, and other files on your site so search engines can discover them. It helps discovery, but submitting a sitemap doesn't guarantee crawling or indexing. entries from it
(absolute, never relative). One base URL kills the relative-canonical and broken-OG-image
class of bugs.
4. Server Components first, Client Components only where needed.
Default to Server Components so content and metadata land in the HTML. Reach for
'use client' only for interactivity — and remember metadata can’t come from a Client
Component.
5. The decision rule for renderingTurning HTML, CSS, and JavaScript into the final visual page and DOM.. Mostly static (blogs, docs, marketing) → SSG (or ISR on a timer). Always-fresh / volatile (prices, stock) → SSR. Changes hourly/daily, want static speed → ISR (mind the stale-first-request trap). Interactive, behind a login, not indexedStoring a crawled page in the search index so it can appear in results. Crawled is not the same as indexed — Google selects what to keep, and indexing isn't guaranteed. → CSR is fine. Public content you want ranked → never CSR.
Next.js SEO — cheat sheet
RenderingTurning HTML, CSS, and JavaScript into the final visual page and DOM. modes
| Mode | Where HTML is built | SEO | Best for | Watch out for |
|---|---|---|---|---|
| SSG | Build time → static | ✅ Best | Mostly-static content | Stale until rebuild |
| Server Components | Server (App Router default) | ✅ Best | Most content | Interactivity needs Client Components |
| ISR | Static + timed regen | ✅ Good | Hourly/daily content | First request post-revalidation is stale |
| SSR | Server, per request | ✅ Good | Always-fresh data | Higher TTFBTime to First Byte — the time from the start of a request to when the first byte of the response arrives. It's a diagnostic metric (not a Core Web Vital) and a major input to FCP and LCP; ≤0.8 s is good. / infra cost |
| CSR | In the browser | ⚠️ Risky | Logged-in dashboards | Empty page to non-Google crawlersGooglebot is Google's web crawler — the software that fetches pages so Google can index and rank them. It comes in two variants, Googlebot Smartphone (primary, under mobile-first indexing) and Googlebot Desktop, and runs an evergreen Chromium renderer. |
App Router vs Pages Router
| Feature | App Router | Pages Router |
|---|---|---|
| Metadata | metadata / generateMetadata | next/head + next-seo |
| Server Components | Default | Not available |
| Streaming metadata (botA crawler — also called a spider or bot — is an automated program that fetches web pages, extracts their links, and queues new URLs to visit. Search engines use crawlers to discover and download content for their index.-aware) | Yes | No |
| SitemapA sitemap is a file that lists the pages, images, videos, and other files on your site so search engines can discover them. It helps discovery, but submitting a sitemap doesn't guarantee crawling or indexing. | app/sitemap.ts | next-sitemap / manual |
| Robots | app/robots.ts | public/robots.txt |
| Canonical | alternates.canonical | <link rel="canonical"> in <Head> |
| Data fetching | async Server Components | getStaticProps / getServerSideProps |
Fast rules
- Set
metadataBaseor canonicals/OG images go relative. metadatais Server Component only —'use client'exports do nothing.- Child-segment
openGraphreplaces the parent’s (no merge). priorityon the LCPLargest Contentful Paint — render time of the largest visible image or text block, relative to when the page started loading. ≤2.5 s (at the 75th percentile) is good. image = the top CWVGoogle's three real-user UX metrics — LCP (loading), INP (responsiveness), and CLS (visual stability) — used by Google's ranking systems, with no official weight attached, measured on field data. win.- Internal linksAn internal link is a hyperlink from one page on a website to another page on the same website. Internal links help search engines discover your pages and pass ranking signals (PageRank and anchor-text context) between them. =
next/link(real<a href>); noonClick-only nav. - 404 → call
notFound()(real404, not soft 404A soft 404 is a URL that returns a success status code (usually 200 OK) even though the page is empty, missing, or shows a 'not found' message. It isn't a status code a server sends — it's a label search engines apply after comparing the response code against the rendered content, and they treat the page like a 404 for indexing.). - Never disallow
.js/.css. - Streaming metadata → blocking for BingbotBingbot is Microsoft Bing's primary web crawler — the bot that discovers, fetches, and renders pages to build the Bing index. That index also powers Yahoo, DuckDuckGo, Ecosia, and Microsoft Copilot, so Bingbot's reach is far wider than Bing's own search-market share., Twitterbot, Slackbot, facebookexternalhit.
- Dynamic renderingTurning HTML, CSS, and JavaScript into the final visual page and DOM.: deprecated — use SSR / SSG / ISR / Server Components.
Quick checks for a Next.js build
A few command-line checks before you reach for a full crawlerA crawler — also called a spider or bot — is an automated program that fetches web pages, extracts their links, and queues new URLs to visit. Search engines use crawlers to discover and download content for their index..
Is your content in the raw HTML (or only after JS runs)?
macOS / Linux:
# Raw HTML as the server sends it — the "first fetch", before any client JS
curl -sL -A "Mozilla/5.0 (compatible; Googlebot/2.1; +http://www.google.com/bot.html)" \
https://example.com/page/ -o raw.html
# Is your headline actually in the raw HTML? (empty result = CSR / JS-dependent)
grep -o "Your headline text" raw.html
# Did metadataBase do its job? Canonical and OG URLs should be absolute, not relative
grep -iE 'rel="canonical"|og:(url|image)' raw.htmlWindows (PowerShell):
$ua = "Mozilla/5.0 (compatible; Googlebot/2.1; +http://www.google.com/bot.html)"
Invoke-WebRequest -Uri "https://example.com/page/" -UserAgent $ua -OutFile raw.html
Select-String -Path raw.html -Pattern "Your headline text"
Select-String -Path raw.html -Pattern 'rel="canonical"','og:url','og:image'If the headline is missing from raw.html but shows in your browser, that route is
client-rendered. If canonicals or OG image URLs come out relative, you’ve forgotten
metadataBase.
Confirm you aren’t blocking JS/CSS (including the Next.js _next directory)
macOS / Linux:
curl -sL https://example.com/robots.txt | \
grep -iE "disallow.*\.(js|css)|Disallow:\s*/_next"Windows (PowerShell):
(Invoke-WebRequest "https://example.com/robots.txt").Content |
Select-String -Pattern "Disallow.*\.(js|css)","Disallow:\s*/_next"Any match here is almost always a mistake — Google won’t render from blocked files. A plain
curl can’t run JavaScript, so for the rendered DOM use URL InspectionA Google Search Console feature that reports how Google sees one specific URL on a property you own. By default it shows the last-indexed snapshot; a separate \"Test live URL\" mode fetches the current version.’s “View Crawled
Page → rendered HTML.”
Patrick's relevant free tools
- Raw vs. Rendered HTML Checker — See what's in your page's initial HTML versus after JavaScript runs — headless-Chrome rendering only when the page actually needs it, a rendering-strategy verdict (SSR / prerendered / CSR / hybrid), ~15 calibrated JavaScript-SEO checks (noindex, canonicals, robots.txt blocking, links, soft 404s), a side-by-side raw-vs-rendered diff, and shareable reports.
- SEO Incident Simulator — Practice thirty deterministic technical SEO incident investigations — indexability, crawl controls, redirects, sitemaps, markup, caching, DNS, bot verification, rendering, hreflang, and faceted navigation — with clearly labeled fixture evidence and Find → Fix → Verify handoffs.
- Staging vs. Production SEO Diff — Compare matched release URLs across redirects, canonicals, robots directives, hreflang, selected headers, schema eligibility, and raw or optionally rendered content with honest not-evaluated states.
Tools for auditing a Next.js site
- URL InspectionA Google Search Console feature that reports how Google sees one specific URL on a property you own. By default it shows the last-indexed snapshot; a separate \"Test live URL\" mode fetches the current version. (Google Search ConsoleA free Google service that reports how a site performs in Google Search and surfaces problems with how Google crawls, indexes, and serves it. It's first-party data straight from Google — but you don't need it to appear in results.) — the source of truth. Run a live test,
then view the rendered HTML, screenshot, and page resources (what loaded vs.
what was blocked) to catch CSR gaps and blocked
_nextresources. - Rich ResultsRich results (formerly 'rich snippets') are enhanced search listings — stars, images, prices, breadcrumbs, video thumbnails, and more — that Google and Bing build from structured data. They're a display feature, not a ranking factor, and eligibility never guarantees they'll show. Test — confirm JSON-LDJSON-LD (JavaScript Object Notation for Linked Data) is a script-based structured data format, typically paired with the schema.org vocabulary to describe page content for search engines and AI systems. Google recommends it over Microdata and RDFa because it's the easiest format to implement and maintain at scale — but all three work, and structured data isn't a ranking signal. made it into the rendered output after any renderingTurning HTML, CSS, and JavaScript into the final visual page and DOM. change.
- LighthouseLighthouse is Google's free, open-source tool that audits a page under simulated lab conditions and scores it 0–100 across Performance, Accessibility, Best Practices, and SEO. It's lab data — useful for debugging, not a ranking signal. / Chrome DevTools — measure LCPLargest Contentful Paint — render time of the largest visible image or text block, relative to when the page started loading. ≤2.5 s (at the 75th percentile) is good., CLSCumulative Layout Shift — a unitless score for unexpected visual movement, taken from the largest burst (session window) of layout shifts, not the lifetime sum. ≤0.1 is good., and INPInteraction to Next Paint — the input-to-paint latency at the 75th percentile of a page's interactions. ≤200 ms is good.; check whether your hero
image is preloaded (
priority). - Ahrefs Site Audit — crawls with JavaScript renderingTurning HTML, CSS, and JavaScript into the final visual page and DOM., surfacing missing/relative canonicals, broken metadata, redirect chainsA → B → C instead of A → C. Each hop loses link equity and adds latency., and indexability issues across the frontend.
- Screaming Frog SEO Spider — crawl with JS rendering on/off to diff raw vs. rendered HTML, and build pre/post crawl comparisons for migrations.
- Bing Webmaster ToolsMicrosoft's free portal for monitoring and improving how a site appears in Bing search — the peer to Google Search Console, plus IndexNow instant indexing, richer backlink data, and keyword volumes. Because Bing's index also feeds Microsoft Copilot, it doubles as a window into AI-search visibility. — Bing’s URL Inspection and where IndexNowIndexNow is an open push protocol that lets you instantly tell participating search engines (Bing, Yandex, Naver, Seznam, and Yep) which URLs you've added, changed, or removed via a simple HTTP request — and one submission is shared across all of them. Google does not use it. submissions appear.
Mistakes to avoid on Next.js
Concrete patterns that quietly break SEO on real Next.js builds — prevent these before they ship, rather than debugging them after a traffic drop.
Shipping without metadataBase in the root layout
Why it’s wrong: without metadataBase, alternates.canonical and openGraph.images
resolve to relative paths instead of absolute URLs. A relative canonical can confuse
canonicalizationHow search engines pick one canonical URL among duplicates and consolidate signals onto it. signals, and relative OG image URLs break link previews on social and
messaging apps.
What to do instead: set metadataBase: new URL('https://example.com') once in
app/layout.tsx and let every page inherit it. Check it with grep -iE 'rel="canonical"|og:(url|image)' against a raw HTML fetch — the URLs should start with https://.
Overwriting a layout’s openGraph from a child segment
Why it’s wrong: metadata merges shallowly from layout to page. A page-level
openGraph: { title: 'Home' } doesn’t merge with the layout’s openGraph.images — it
replaces the whole object, silently dropping the images.
What to do instead: either repeat the full openGraph object (images included) at
every level that overrides it, or only override the specific top-level metadata fields you
actually need to change, and leave openGraph untouched where the layout’s default is fine.
Exporting metadata from a Client Component
Why it’s wrong: the Metadata API is Server Component only. Add 'use client' to a file
that also exports metadata and the export does nothing — no error, no warning, just a
page with no title or description.
What to do instead: keep metadata / generateMetadata exports in a plain Server
Component file (page.tsx or layout.tsx without 'use client'). If a page needs client
interactivity, put that in a separate child component and import it — don’t add
'use client' to the file that owns the metadata export.
Fetching primary content in useEffect (or relying on full CSR)
Why it’s wrong: content rendered only in the browser isn’t in the initial HTML. Google will eventually render it, but the median render delay is real and the 90th percentile stretches to hours — and non-Google crawlersGooglebot is Google's web crawler — the software that fetches pages so Google can index and rank them. It comes in two variants, Googlebot Smartphone (primary, under mobile-first indexing) and Googlebot Desktop, and runs an evergreen Chromium renderer. (Bing, social preview botsA crawler — also called a spider or bot — is an automated program that fetches web pages, extracts their links, and queues new URLs to visit. Search engines use crawlers to discover and download content for their index., most AI crawlersAI crawlers are bots from AI companies that fetch web pages to train language models, build AI-search indexes, or answer live user questions. They come in three categories, each with its own user-agent tokens and its own robots.txt controls.) often don’t render JavaScriptMaking sure search engines can crawl, render, and index content that depends on JavaScript. at all, so they see an empty page.
What to do instead: default to Server Components, SSG, ISR, or SSR for anything you
want indexedStoring a crawled page in the search index so it can appear in results. Crawled is not the same as indexed — Google selects what to keep, and indexing isn't guaranteed.. Reserve 'use client' and useEffect data-fetching for interactive UI that
doesn’t need to be crawlable — a filter widget, not the article body.
Returning 200 for a “not found” view
Why it’s wrong: renderingTurning HTML, CSS, and JavaScript into the final visual page and DOM. a “not foundA 302 (\"Found\") is a temporary redirect: it forwards users to a new URL while telling search engines the original URL should stay in the index. It's a weak canonicalization signal, not the zero-equity dead end of SEO folklore.” message without calling notFound() returns a
normal 200 status. That’s a soft 404A soft 404 is a URL that returns a success status code (usually 200 OK) even though the page is empty, missing, or shows a 'not found' message. It isn't a status code a server sends — it's a label search engines apply after comparing the response code against the rendered content, and they treat the page like a 404 for indexing. — Google can index the empty-content page instead of
recognizing it as missing, and soft 404s are one of the most common real-world Next.js SEONext.js SEO is the set of practices and built-in features that make a Next.js site crawlable, indexable, and rankable — rendering mode (SSG/SSR/ISR/Server Components), the App Router Metadata API, sitemap.ts/robots.ts conventions, next/image, and next/link.
problems.
What to do instead: call the built-in notFound() function so the route returns a real
404. Confirm with curl -I on a known-missing URL and check the status code directly,
not just what renders in the browser.
Skipping generateStaticParams on important dynamic routes
Why it’s wrong: without it, dynamic routes fall back to on-demand renderingTurning HTML, CSS, and JavaScript into the final visual page and DOM. (SSR), which still works for SEO but adds server latency to every first request for that URL — including GooglebotGooglebot is Google's web crawler — the software that fetches pages so Google can index and rank them. It comes in two variants, Googlebot Smartphone (primary, under mobile-first indexing) and Googlebot Desktop, and runs an evergreen Chromium renderer.’s.
What to do instead: enumerate the URLs that matter (product pages, blog posts,
category pages) in generateStaticParams() so they’re pre-built, and pair it with
revalidate (ISR) for content that changes after launch.
Test yourself: Next.js SEO
Five quick questions on making a Next.js site crawlable, indexable, and fast. Pick an answer for each, then check.
Resources worth your time
My related writing
- JavaScript SEO: A Definitive Guide — my full guide to renderingTurning HTML, CSS, and JavaScript into the final visual page and DOM., DOM parityWhether the rendered DOM matches what you expect the raw HTML to become., canonicals in JS, sitemapsA sitemap is a file that lists the pages, images, videos, and other files on your site so search engines can discover them. It helps discovery, but submitting a sitemap doesn't guarantee crawling or indexing., and the renderingTurning HTML, CSS, and JavaScript into the final visual page and DOM.-mode trade-offs that Next.js sits on top of. This Next.js guide is the framework-specific layer on top of it.
- The Beginner’s Guide to Technical SEO — where rendering and crawlingCrawling is how search engines use automated bots (like Googlebot and Bingbot) to discover URLs and download pages. A page has to be crawlable to be indexed, but crawling on its own isn't a ranking factor. fit in the bigger picture.
My speaking
- JavaScript SEO — Ungagged 2019 (SlideShare) — my walkthrough of how frameworks separate frontend from backend and how GooglebotGooglebot is Google's web crawler — the software that fetches pages so Google can index and rank them. It comes in two variants, Googlebot Smartphone (primary, under mobile-first indexing) and Googlebot Desktop, and runs an evergreen Chromium renderer. renders. (Standing disclaimer: the dynamic-rendering recommendation in that deck is now outdated — Google deprecated it.)
From around the industry
- Metadata and OG Images (Next.js docs) — the App Router metadata system,
metadataBase, and OG image generation. - generateMetadata API Reference (Next.js docs) — static vs. dynamic metadata and the HTML-limited botsA crawler — also called a spider or bot — is an automated program that fetches web pages, extracts their links, and queues new URLs to visit. Search engines use crawlers to discover and download content for their index. / streaming-metadata behavior.
- sitemap.xml File Convention (Next.js docs) —
app/sitemap.ts,generateSitemaps(), and localized/image sitemapsAn image sitemap is a sitemap (or an extension added to an existing sitemap) that lists the images on your pages using Google's image namespace, helping Google discover images it might otherwise miss.. - Common SEO Issues on Next.js Websites (Salt Agency) — a 50-site audit study with real data on soft 404sA soft 404 is a URL that returns a success status code (usually 200 OK) even though the page is empty, missing, or shows a 'not found' message. It isn't a status code a server sends — it's a label search engines apply after comparing the response code against the rendered content, and they treat the page like a 404 for indexing. and LCPLargest Contentful Paint — render time of the largest visible image or text block, relative to when the page started loading. ≤2.5 s (at the 75th percentile) is good. failures.
- How Google Handles JavaScript Throughout the Indexing Process (Vercel) — render-timing data from nextjs.org’s own server beacons.
- The Complete Next.js SEO Guide (Strapi) — a thorough framework-walkthrough covering rendering modes, metadata, and structured dataStructured data is a standardized way of labeling page content (using the schema.org vocabulary in JSON-LD, Microdata, or RDFa) so search engines can understand its meaning. It's not a direct ranking factor — its value is rich results and entity understanding..
- App Router vs Pages Router for SEO (Wisp) — a focused comparison of the two routers from an SEO angle.
- r/TechSEO — the community for rendering/indexingStoring a crawled page in the search index so it can appear in results. Crawled is not the same as indexed — Google selects what to keep, and indexing isn't guaranteed. debugging.
Stats worth citing
- Render timing is usually fast, occasionally very slow. Vercel’s analysis of 37,000+ server-beacon pairs on nextjs.org foundA 302 (\"Found\") is a temporary redirect: it forwards users to a new URL while telling search engines the original URL should stay in the index. It's a weak canonicalization signal, not the zero-equity dead end of SEO folklore. a median render time of ~10 seconds, but a 90th percentile of ~3 hours and a 99th percentile of ~18 hours — which is exactly why you don’t want primary content waiting on the render queue. Source
- CWVGoogle's three real-user UX metrics — LCP (loading), INP (responsiveness), and CLS (visual stability) — used by Google's ranking systems, with no official weight attached, measured on field data. and soft 404sA soft 404 is a URL that returns a success status code (usually 200 OK) even though the page is empty, missing, or shows a 'not found' message. It isn't a status code a server sends — it's a label search engines apply after comparing the response code against the rendered content, and they treat the page like a 404 for indexing. are widespread on real Next.js sites. Salt Agency’s audit of 50
Next.js sites found 41/50 returning soft 404sA soft 404 is a URL that returns a success status code (usually 200 OK) even though the page is empty, missing, or shows a 'not found' message. It isn't a status code a server sends — it's a label search engines apply after comparing the response code against the rendered content, and they treat the page like a 404 for indexing. (404 views at a
200status) and only 3/50 passing LCPLargest Contentful Paint — render time of the largest visible image or text block, relative to when the page started loading. ≤2.5 s (at the 75th percentile) is good. thresholds — a reminder that the framework’s CWV advantages only help if you usenext/imagewithpriorityand return real status codes. Source
Next.js SEO
Next.js SEO is the set of practices and built-in features that make a Next.js site crawlable, indexable, and rankable — rendering mode (SSG/SSR/ISR/Server Components), the App Router Metadata API, sitemap.ts/robots.ts conventions, next/image, and next/link.
Related: JavaScript SEO, Headless CMS SEO
Next.js SEO
Next.js SEOMaking sure search engines can crawl, render, and index content that depends on JavaScript. refers to the practices, configurations, and framework features used to make a website built with Next.js — a React-based full-stack framework — easily crawlable, indexable, and rankable by search engines. Because Next.js supports multiple renderingTurning HTML, CSS, and JavaScript into the final visual page and DOM. modes (SSG, SSR, ISR, and React Server Components), it inherits both the benefits and the risks of JavaScript-heavy architectures, while shipping first-class APIs for metadata, sitemapsA sitemap is a file that lists the pages, images, videos, and other files on your site so search engines can discover them. It helps discovery, but submitting a sitemap doesn't guarantee crawling or indexing., robots.txtA plain-text file at the root of a host that tells crawlers which URLs they may and may not request. It controls crawling, not indexing — a blocked URL can still be indexed if it's linked from elsewhere., and image optimization that make technical SEOTechnical SEO is the practice of making a site easy for search engines to crawl, render, index, and (now) be eligible for AI answers. It's the foundation that lets your content and links rank — not a ranking trick of its own. far easier than on a bare React single-page app.
Next.js has two routers with different SEO mechanics. The older Pages Router uses getStaticProps/getServerSideProps and manages metadata through next/head (or the next-seo package). The current App Router (v13+) defaults to React Server Components and provides a native Metadata API — the metadata export or the generateMetadata() function — that resolves on the server so tags appear in the initial HTML. The App Router also adds file conventions like app/sitemap.ts and app/robots.ts, plus generateStaticParams() for pre-building dynamic routes.
The biggest wins come from using the framework as intended: Server Components and SSG/ISR put content in the HTML so there is no renderingTurning HTML, CSS, and JavaScript into the final visual page and DOM.-queue delay, next/image prevents layout shiftCumulative Layout Shift — a unitless score for unexpected visual movement, taken from the largest burst (session window) of layout shifts, not the lifetime sum. ≤0.1 is good. and serves modern formats, and next/link renders real crawlable anchors. The most common mistakes are a missing metadataBase (which breaks canonical and Open GraphOpen Graph (OG) tags are `<meta>` elements in a page's head, defined by the Open Graph protocol (ogp.me, created by Facebook), that describe a page as a shareable object — its title, description, image, URL, and type. They control how a link preview card looks when the page is shared on Facebook, LinkedIn, Slack, Discord, WhatsApp, and iMessage. They are not a direct Google ranking factor, though Google reads og:title, og:image, and og:site_name as inputs to how a result appears. URLs), no priority on the LCPLargest Contentful Paint — render time of the largest visible image or text block, relative to when the page started loading. ≤2.5 s (at the 75th percentile) is good. image, exporting metadata from a Client Component (where it does nothing), and 404 views that return a 200 status.
Related: JavaScript SEO, Headless CMS SEO
Build-time retrieval analysis plus live signals for this exact article. The automatic chunk report includes a deterministic readiness score and is ready without a model download.
Search Console
sampleGA4 traffic (28d)
sampleCloudflare traffic (7d)
sampledCrUX field data (28d, phone)
sampleGoogle NLP entities
localChangelog
Updated Jul 18, 2026.
Editorial summary and recorded change details.Summary
Live-verified the next-seo npm package's current maintenance status (v7.2.0, not archived, still active) and confirmed its own docs recommend the built-in Metadata API for App Router meta tags — resolved the package-vs-built-in question the article previously left implicit. Added a per-route rendering qualification, a version-gated note on streaming-metadata behavior with a direct-request-vs-client-navigation test, and a consolidated production-checks table for cache age, HTTP status, redirect context, streamed metadata, and client transitions.
Change details
-
Added a short next-seo-package-vs-built-in-Metadata-API section in the Pages Router lens, live-verified against the package's npm/GitHub/README (v7.2.0, actively maintained, App Router guidance points to generateMetadata).
-
Added a per-route (not per-project) rendering-mode qualification to the 'Where Next.js fits' section.
-
Added a version gate and direct-request-vs-client-navigation test note to the streaming metadata explanation.
-
Added a 'What to check where' production table covering cache/revalidation age, direct HTTP status, redirect context, streamed metadata, and client-side transitions.
Full comparison unavailable — no prior snapshot was archived for this revision.