# Next JS SEO: Metadata Changes That Break Indexing

**URL:** https://mettevo.com/blog/article/next-js-seo-metadata-changes-that-break-indexing  
**Published:** 2026-08-31  
**Updated:** 2026-08-31  
**Author:** Oleg Silin  
**Category:** Uncategorized

> Learn how Next.js SEO metadata changes can affect indexing, rankings, and visibility. Discover common issues and fixes to keep your pages search-friendly.

![Next JS SEO](https://stage.mettevo.com/wp-content/uploads/2026/08/Next_JS_SEO_with_people_202608311234.jpeg)

---

Next.js supports SEO (search engine optimization) well: it renders full HTML through server-side rendering, static generation, or incremental static regeneration, and it ships a built-in Metadata API. That API changed completely in the App Router. Metadata now cascades through layouts, and since version 15.2 it can stream in separately, which not every crawler reads the same way.

## **Key Takeaways**

-   The App Router's Metadata API cascades through layouts; a page that skips generateMetadata() silently inherits its parent's title and description.
-   The Pages Router's next/head never cascades. Official docs say each page must "completely define what it needs" in <head>.
-   Since Next.js 15.2, metadata from an async generateMetadata() can stream in after the initial HTML instead of blocking it.
-   Next.js's default "HTML-limited bots" list covers roughly 30 crawlers, but not the literal string "Googlebot," and not GPTBot, ClaudeBot, or PerplexityBot.
-   On 2026-08-27, Mettevo's own pull of Google's US top 10 for "next js seo" found zero measured pages using FAQPage schema.

## **Is Next.js good for SEO?**

Yes. Next.js is a React framework for building server-rendered and statically generated web applications, and it handles SEO through rendering strategy and a built-in Metadata API rather than a plugin. Google's own documentation confirms that [Googlebot renders JavaScript with an evergreen version of Chromium](https://developers.google.com/search/docs/crawling-indexing/javascript/javascript-seo-basics), so server-rendered and statically generated Next.js pages get crawled and indexed the same way a plain HTML site would.

The framework's real advantage is choice per route. Server-side rendering (SSR), static site generation (SSG), and incremental static regeneration (ISR) can all live in the same project, so a marketing page stays fully static while a dashboard stays dynamic. Out of the box, Next.js also ships the Metadata API (Application Programming Interface) for title tags, Open Graph, and Twitter Card data. It adds file-based conventions for robots.txt and sitemap.xml that used to need a plugin or a hand-written route.

None of that guarantees a ranking. It removes technical failure modes, like unrendered content or a missing canonical tag, that otherwise cost a site visibility regardless of content quality. Teams choosing between Next.js and plain React should weigh [the architectural differences between Next.js and React](https://mettevo.com/blog/article/next-js-vs-react-complete-comparison-differences-pros-and-cons-and-how-to-choose-in-2025) first; rendering strategy affects SEO more than raw framework speed.

## **How do you add metadata in Next.js?**

There are two ways, and the right one depends on whether the metadata is static or data-dependent. For a page that never changes, export a metadata object from layout.tsx or page.tsx. For a page whose title depends on a database record, a Content Management System (CMS) entry, or a route parameter, export an async generateMetadata() function instead. Next.js's reference documentation is explicit that a route segment can export one or the other, never both.

// app/blog/\[slug\]/page.tsx  
import type { Metadata } from 'next'  
  
export async function generateMetadata(  
  { params }: { params: Promise<{ slug: string }> }  
): Promise<Metadata> {  
  const { slug } = await params  
  const post = await fetch(\`https://api.example.com/posts/${slug}\`).then((r) => r.json())  
  
  return {  
    title: post.title,  
    description: post.excerpt,  
  }  
}

This is the direct replacement for getStaticProps plus a manual <Head> block on the Pages Router. If the page component fetches the same post again, Next.js memoizes the fetch call automatically, so writing the request twice does not cost two round trips. That single function is also where App Router SEO diverges from the Pages Router most sharply, which the next section covers in detail.

## **Metadata API: how the App Router differs from the Pages Router**

On the Pages Router, every page manages its own <Head> independently. Next.js's documentation for the Head component says the contents "get cleared upon unmounting." It instructs developers to make sure "each page completely defines what it needs in head, without making assumptions about what other pages added." Nothing cascades. A sitewide Open Graph image or a canonical domain has to be repeated or wired through a custom \_app.js on every page. Duplicate tags are only handled if a developer remembers to add a key prop. <title> and <base> are the two exceptions Next.js deduplicates on its own.

The App Router replaces that model with inheritance. Metadata is evaluated from the root layout.tsx down to the closest page.tsx. Objects from each segment are shallowly merged, and duplicate keys are replaced by whichever segment sits closest to the page. Next.js's reference documentation spells out what that means for an unset field: for title, "if a page does not define its own title the closest parent's resolved title will be used." The same inheritance applies to any openGraph field a page leaves unset.

That inheritance is also where indexing breaks. A team can migrate fifty dynamic routes and only wire up generateMetadata() on a handful of them; the rest do not error. They silently render the layout's fallback title and description instead. [Google's own guidance](https://developers.google.com/search/docs/appearance/title-link) names "repeated boilerplate text in <title> elements for a subset of pages" as one specific trigger it uses to rewrite a title link instead of using yours.

Pages Router (next/head)

App Router (Metadata API)

Where it lives

<Head> inside each page component

metadata export or generateMetadata() in layout.tsx/page.tsx

Cascades from parent?

No, each page is independent

Yes, shallow merge down the layout tree

Missing field on one page

Tag is simply absent

Silently inherited from the nearest parent

Duplicate-tag handling

Manual, via a key prop

Automatic; last segment to define a key wins

Async data source

Fetched in getServerSideProps, passed as props

generateMetadata() fetches directly, async by default

Migrating the code is the easy part. Auditing every dynamic segment for a missing generateMetadata() call is the part that actually protects indexing.

## **robots.txt and sitemap.xml in the App Router**

Pages Router projects handle robots.txt and sitemap.xml as static files in /public, or with custom logic wired through an API route. The App Router replaces both with dedicated file conventions that Next.js compiles and serves on its own.

// app/robots.ts  
import type { MetadataRoute } from 'next'  
  
export default function robots(): MetadataRoute.Robots {  
  return {  
    rules: { userAgent: '\*', allow: '/', disallow: '/private/' },  
    sitemap: 'https://example.com/sitemap.xml',  
  }  
}

Next.js serves that file at /robots.txt, and per the [file-convention reference](https://nextjs.org/docs/app/api-reference/file-conventions/metadata/robots), it is cached by default unless the function reads a request-time value. app/sitemap.ts works the same way, exporting an array instead of an object:

// app/sitemap.ts  
import type { MetadataRoute } from 'next'  
  
export default function sitemap(): MetadataRoute.Sitemap {  
  return \[  
    { url: 'https://example.com', lastModified: new Date(), priority: 1 },  
    { url: 'https://example.com/blog', lastModified: new Date(), priority: 0.5 },  
  \]  
}

Both files can pull from a database or a headless CMS at build or request time, which matters once the content model lives outside the repository. Teams that [pair Next.js with a headless CMS](https://mettevo.com/blog/article/best-headless-cms-for-next-js-in-2026-complete-comparison-integration-guide) usually generate the sitemap from the same content API that feeds generateMetadata(). A new post then gets a sitemap entry and correct metadata from the same deploy.

## **Rendering and indexing in Next.js**

Google indexes a Next.js page the same way it indexes any JavaScript-rendered site. Google's documentation describes [crawling, rendering, and indexing](https://developers.google.com/search/docs/crawling-indexing/javascript/javascript-seo-basics) as three separate phases, with rendering handled by "an evergreen version of Chromium." Static and server-rendered Next.js pages skip most of that wait, since the HTML is already complete by the time Googlebot fetches it.

Dynamically rendered pages work differently, and this is where App Router metadata introduces a genuinely new mechanism. Since version 15.2, Next.js can send a page's HTML immediately and append the tags resolved by generateMetadata() to the <body> afterward, once the async call finishes, instead of holding up the whole response. Next.js's own documentation states this has been verified for "bots that execute JavaScript and inspect the full DOM (e.g. Googlebot)."

Bots that cannot do that still get the old behavior. Next.js detects them by user agent and blocks the response until metadata resolves, guaranteeing the tags land in <head> before anything is sent. The [default list Next.js ships](https://github.com/vercel/next.js/blob/canary/packages/next/src/shared/lib/router/utils/html-bots.ts) names roughly 30 crawlers, including Bingbot, Slackbot, Twitterbot, and Facebook's facebookexternalhit, plus a pattern that matches Google's secondary crawlers like AdsBot-Google. The literal string "Googlebot" is not one of the entries. That lines up with Next.js's own claim: the safety net exists for bots that cannot execute JavaScript, and Googlebot is not one of them.

The AI crawlers that matter for generative-engine visibility are a different case. [Vercel's own analysis of traffic to nextjs.org](https://vercel.com/blog/the-rise-of-the-ai-crawler), published December 17, 2024, found that OpenAI's crawlers, Anthropic's ClaudeBot, Meta's Meta-ExternalAgent, ByteDance's Bytespider, and PerplexityBot fetch JavaScript files without executing them. Anthropic's crawler pulled JS files in about 24% of requests versus roughly 11% for OpenAI's, a little more than double, but neither one ran the code. None of those user-agent strings appear in Next.js's default bot list either.

We have not tested a live GPTBot or ClaudeBot request against a dynamically rendered Next.js route. Treat this as a documented gap, not an observed failure. The safer fix does not require testing it first: prerender metadata-sensitive routes with SSG or ISR, since, per Next.js's own docs, prerendered pages "don't use streaming since metadata is resolved at build time."

## **What we measured in Google's top 10 for Next.js SEO**

On 2026-08-27, Mettevo pulled Google's United States results for "next js seo" through the Ahrefs API, then tried to fetch every ranking URL to count total words, H2 headings, and FAQPage markup. The result page is not uniform: the pull included Next.js's own tutorial, a Reddit thread, a YouTube walkthrough, a Medium article, and an npm package page for next-seo.

We excluded Reddit, YouTube, and npm from the word-count comparison, since none of them are structured as an article. Pages that returned a 403 status or timed out during the fetch were dropped rather than counted as zero. Some of the remaining Ahrefs URLs point at section-level pages rather than a single article, so the word counts below are a lower bound, not an exact figure.

URL

Domain Rating

Words

H2 headings

FAQPage schema

strapi.io/blog/nextjs-seo

80

4,711

9

No

adeelhere.com (complete Next.js SEO guide)

1

4,983

22

No

Only two pages survived the filters cleanly enough to compare, and the midpoint of those two, 4,847 words, is the only figure we can report for this keyword. That is a sample of two pages. It is not a reliable estimate of what a ranking page needs to look like, and we are flagging it as such rather than presenting it as a benchmark to hit. What is reliable from this pull: neither measurable page carries FAQPage markup, and the 80-versus-1 domain-authority gap suggests this query is not being won on authority alone.

## **What are Next.js SEO best practices?**

These are the checks we would run first once a project is already on the App Router, whether it started from scratch or from one of the common [Next.js themes and templates](https://mettevo.com/blog/article/next-js-themes-templates-best-options-for-development-in-2025).

1.  Set metadataBase once in the root layout. Without it, relative Open Graph and Twitter image paths resolve against http://localhost:3000 by default.
2.  Implement generateMetadata() on every dynamic route. A page that skips it does not fail; it silently inherits the parent layout's title and description.
3.  Replace static robots.txt and sitemap.xml files with app/robots.ts and app/sitemap.ts when the content model lives in a database or CMS.
4.  Prerender everything that is not request-personalized. Static and incrementally regenerated routes resolve metadata at build time, so the streaming-metadata question does not apply to them at all.
5.  For routes that must stay dynamic, check the rendered HTML directly through Search Console's URL Inspection tool rather than assuming every crawler sees the same version.
6.  Set canonical URLs explicitly through alternates.canonical, especially on routes reachable through more than one URL pattern.
7.  Use next/image for content images. alt is a required prop, width and height (or fill) let Next.js reserve space and avoid layout shift, and images default to loading="lazy" unless marked otherwise.
8.  Add JSON-LD (JSON for Linked Data) as a <script type="application/ld+json"> tag rendered inside layout.tsx or page.tsx, exactly as [Next.js's own JSON-LD guide](https://nextjs.org/docs/app/guides/json-ld) shows, so it ships as part of the server-rendered HTML.

## **Next.js vs WordPress for SEO — which ranks better?**

Neither platform ranks better by default. Rankings follow implementation, and both can produce fast, indexable pages or slow, half-rendered ones depending on how they are built.

WordPress renders complete HTML on every request unless a caching layer is added, and its SEO workflow runs through plugins. Yoast or Rank Math handle title templates, sitemaps, and schema through an admin screen, with no code required. Next.js renders HTML too, but the rendering strategy is a per-route decision made in code, and the Metadata API replaces what a WordPress plugin does through TypeScript objects instead of a settings panel.

WordPress

Next.js

Metadata setup

Plugin UI (Yoast, Rank Math)

Code: metadata object or generateMetadata()

Rendering

Server-rendered PHP by default

Per-route: SSR, SSG, or ISR

Sitemap and robots

Plugin-generated

app/sitemap.ts, app/robots.ts

Non-technical editing

Built-in, no deploy needed

Needs a developer or a headless CMS layer

The practical difference shows up in team structure more than in ranking potential. A content team without developer support usually ships SEO changes faster on WordPress. A team already committed to a React codebase gets the same metadata coverage from Next.js without adding a plugin dependency, though every change still needs a deploy.

## **FAQ**

### **Do you need the next-seo package with the App Router?**

Not for standard tags. The next-seo package's own README recommends using the built-in generateMetadata method for the title and meta tags once a project uses the App Router, and positions NextSeo itself for the Pages directory instead. JSON-LD support in the App Router works too, but only through a documented workaround.

### **What belongs in a Next.js robots.txt file?**

At minimum, a User-Agent rule and a Sitemap line pointing to your sitemap.xml. In the App Router, app/robots.ts exports a Robots object instead of a static file, and Next.js serves it automatically at /robots.txt. That file is also where you add per-bot rules, like disallowing one crawler while allowing everything else.

### **Does metadataBase affect SEO rankings?**

Not directly, but it fixes a real bug. Without metadataBase set, Next.js resolves relative Open Graph and Twitter image paths against http://localhost:3000 by default, so social previews can point at a URL crawlers and social platforms cannot reach in production. Setting it once in the root layout fixes every relative URL across the metadata tree.

### **Can you still use the Pages Router in 2026?**

Yes. Next.js has not removed the Pages Router, and next/head still works exactly as documented, including the key prop for deduplicating tags. The tradeoff is that Pages Router metadata never cascades, streaming metadata does not apply to it, and file conventions like app/sitemap.ts exist only in the App Router.