Next.js for SEO: what it does better and what to watch
Next.js sends complete HTML, metadata and sitemaps by default, which suits search. Where it beats other setups, and five mistakes that still cost rankings.
By Sohail Hussain31 October 20266 min read
Next.js is good for SEO because it sends search engines complete HTML with the title, description, canonical URL and structured data already in place, and because its defaults (pre-rendered pages, optimised images, self-hosted fonts) make Core Web Vitals easy to pass. It does not rank a site on its own. A Next.js site can still be invisible to Google if content is rendered only in the browser, if status codes are wrong, or if the metadata is copied across every page.
This post covers what the framework does better than most setups, then the five mistakes we see on Next.js sites that cost them traffic.
What does Next.js do better for search than a typical site?
Four things, all on by default in the App Router.
Pages arrive as finished HTML. Layouts and pages are Server Components unless you opt out. They run on the server, at build time or on request, and the browser receives the full text of the page. Googlebot does not have to run JavaScript to see your headings, copy and links. Social media crawlers, which mostly do not run JavaScript at all, see the same thing.
Metadata is part of the code, per route. Each page exports a metadata object or a generateMetadata function. Next.js turns it into the <title>, meta description, canonical link, Open Graph and Twitter tags. Layouts set defaults and a title template, and pages override them. There is no plugin to configure and no way for two plugins to fight over the same tag. Setting up metadata and Open Graph is the step-by-step guide.
Sitemaps, robots.txt and share images are files in the app. A sitemap.ts file returns a list of URLs and Next.js serves valid XML at /sitemap.xml. A robots.ts file does the same for /robots.txt. An opengraph-image.tsx file draws a share image for every page from its title. Sitemaps and robots.txt in Next.js and generating Open Graph images show working versions.
Speed is the default, not a project. The next/image component serves resized, modern-format images and reserves their space so the layout does not jump. next/font self-hosts fonts with a size-matched fallback. Only Client Components ship JavaScript. These are the three things that usually decide whether a page passes Core Web Vitals, which Google uses as part of its page experience signals.
That combination is why it is the framework behind our website design and development work, and the longer argument is in why we build company websites with Next.js.
| Next.js (App Router) | WordPress with an SEO plugin | Single-page React app | |
|---|---|---|---|
| HTML on first request | Complete, from the server | Complete, from PHP | Mostly empty until JavaScript runs |
| Titles and meta tags | Metadata API, per route | Plugin settings, per page | Added by a library in the browser |
| Sitemap and robots.txt | Code in app/ | Generated by the plugin | Built separately |
| Image optimisation | Built in | Plugin or host feature | Manual |
| Typical Core Web Vitals effort | Low | Medium; caching and plugin discipline | High |
Which Next.js mistakes cost rankings?
Content that only exists after JavaScript runs
The most common problem is a page whose main content is fetched in the browser. A Client Component that loads products or blog posts inside useEffect sends Google a loading spinner. Google does render JavaScript, but it does so in a second pass, and other crawlers often never do. Fetch content in a Server Component and pass it down. Keep "use client" for the parts that need clicks, state or browser APIs. The server components explainer covers the split in plain terms.
A quick test: open the page, view the source (not the inspector), and search for a sentence from the body. If it is not there, crawlers may not see it either.
A 200 status on pages that should be 404s or redirects
Once a Next.js page starts streaming, its HTTP status is already sent. The Next.js docs are explicit about this: if notFound() runs after a <Suspense> boundary or loading.tsx has started the stream, the response stays 200 and Next.js adds a noindex tag instead. A redirect() called mid-stream becomes a client-side redirect rather than an HTTP one.
Google handles the noindex correctly, but a crawler may report these as soft 404s, and a client-side redirect is a weaker signal than a 301 or 308. Check for missing records before any await that sits under a Suspense boundary, and put permanent redirects in next.config.ts or in proxy.ts, which run before rendering. 301 vs 302 redirects covers the redirect side.
The same metadata on every page
A root layout with a title and description, and pages that never override them, gives every URL the same snippet. Google then rewrites the snippets or treats the pages as near-duplicates. Set a title.template in the root layout and a unique title and description in each page or in generateMetadata.
A related trap: metadata objects merge shallowly. If the layout sets openGraph with a site name and image, and a page sets openGraph with only a title, the page's object replaces the layout's completely. The site name and image disappear for that page.
Canonical URLs that are missing or relative to nothing
Without metadataBase in the root layout, relative URLs in canonical and Open Graph fields cause a build error, so most projects set it. The quieter problem is pages that never set alternates.canonical, so URLs with tracking parameters, trailing slashes or upper-case letters compete with the clean version. Set a canonical on every indexable page, and pick one trailing-slash style with the trailingSlash option so the other redirects.
Preview and staging URLs that get indexed
Every pull request on most Next.js hosts gets its own preview URL. Vercel adds a noindex header to preview deployments by default, but self-hosted staging servers and other platforms may not. If a staging site is public, protect it with a password or send X-Robots-Tag: noindex from it, and check Search Console for unexpected hostnames.
Does Next.js handle structured data?
Not through the Metadata API. JSON-LD is rendered as a <script type="application/ld+json"> tag inside the page or layout, which is the approach the Next.js JSON-LD guide recommends. It is a few lines of code per template: an Article object for posts, Organization on the homepage, BreadcrumbList where you show breadcrumbs, FAQPage where there is a real FAQ.
The guide also points out a security detail people miss: JSON.stringify does not escape <, so if any value comes from a CMS or user input, replace < with < before writing it into the page.
When does using Next.js for SEO not pay off?
When the content team needs to publish and restructure pages daily without a developer. Next.js with a headless CMS handles text, images and listings well, but adding a new page type or rearranging a template is code. If organic growth depends on a marketing team shipping new landing pages every week, WordPress or Webflow may get more pages live, and more pages written well usually matters more than a faster framework.
It also does not pay off as a fix for weak content. Moving a site to Next.js improves speed and crawlability. It does not improve the answers on the pages, and that is what ranks.
What should you check after launching a Next.js site?
- View source on three page types and confirm the main text, title, description and canonical are in the HTML.
- Fetch a URL that should not exist with
curl -sIand confirm it returns404, not200. - Open
/sitemap.xmland/robots.txtand confirm they list production URLs only. - Run a page through the meta tags generator to compare what you have with a complete head block.
- After four weeks, check Search Console's Pages and Core Web Vitals reports, and enter the field numbers in the Core Web Vitals checker.
The fuller list for any platform is our technical SEO checklist, and if you want someone to run it on your site, that is what our technical SEO work covers.
Was this guide useful?