Understanding the ins and outs of website growth, we help ensure that your site grows over time with ever-increasing reach and accessibility. Not only do we employ the latest digital marketing techniques for driving traffic directly to your website, but our strategies also focus on gaining loyalty from those visitors so they come back again and again.
Leave your contacts to get a comprehensive and aggressive digital marketing plan taking your business to new heights.
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, 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 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'
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 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'
Next.js serves that file at /robots.txt, and per the file-convention reference, 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 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 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 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, 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.
Set metadataBase once in the root layout. Without it, relative Open Graph and Twitter image paths resolve against http://localhost:3000 by default.
Implement generateMetadata() on every dynamic route. A page that skips it does not fail; it silently inherits the parent layout's title and description.
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.
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.
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.
Set canonical URLs explicitly through alternates.canonical, especially on routes reachable through more than one URL pattern.
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.
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 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.