Next.js SEO guide: metadata, sitemaps, hreflang and JSON-LD in the App Router
Most of it lives in the codebase and gets set up once. The rest is not hiding your text behind JavaScript.

Is Next.js good for SEO?
Yes, because it can ship static or server-rendered pages with all the text already in the HTML, which is what search engines need. It is not automatic, though. A Next.js app built from client components that fetch content after hydration can be as invisible as any single-page app.
Google processes JavaScript sites in three phases: crawling, rendering and indexing. Pages wait in a render queue for a few seconds or longer. The same documentation notes that not all bots can run JavaScript, so server-side rendering or pre-rendering remains the safe default, including for the crawlers behind AI answer engines.
How do you handle titles, descriptions and canonicals?
Through the App Router's Metadata API: export a static metadata object, or a generateMetadata function when values depend on the page, from any layout or page, and Next.js turns it into head tags. Set metadataBase in the root layout to your production domain; fields that need absolute URLs, such as canonicals and Open Graph images, can then use relative paths.
- title and description: unique per page and written for humans, since they are what appears in results.
- alternates.canonical: on every page, pointing to itself in its own language.
- alternates.languages: every language or regional version, plus x-default.
- openGraph: a dedicated image, so shared links look intentional.
One change worth knowing: since Next.js 15.2, metadata from generateMetadata can be streamed and appended to the body after the first chunk of HTML. The generateMetadata reference says Next.js detects HTML-limited bots by user agent and keeps the tags in the head for them, and that Googlebot reads tags in the body correctly. Either way, open the HTML source of a production page and check where the title and canonical end up.
How do you set up hreflang for multiple languages or regions?
Use alternates.languages in each page's metadata, or put the alternates in the sitemap, and follow Google's rules: every version must list itself and all the others, or the annotations are ignored. Google treats the three methods (head tags, HTTP headers, sitemap) as equivalent, so pick one and stay consistent.
Regional variants follow the same pattern. A product selling separately in the UK and the US can declare en-GB and en-US versions, with x-default for everyone else. Note that Google's localisation guidelines say it does not use hreflang or the HTML lang attribute to detect a page's language; it reads the content. A “British” page that still says “color” and prices in dollars sends mixed signals.
On this site, Italian lives at the root and English under /en, with two separate root layouts so each page declares its own lang attribute. In the sitemap, every URL carries its it, en and x-default alternates, with x-default pointing to the Italian version.
How do you generate a sitemap and robots.txt in Next.js?
With two files in the app directory: sitemap.ts returns your URLs and Next.js serves them as XML, and robots.ts does the same for robots.txt, which can also stay a static file in public, as it does here. According to the Next.js sitemap docs, the sitemap is a special route handler that is cached by default unless it uses request-time APIs.
Each entry can include language alternates and a last-modified date. Google is blunt about the latter: it uses lastmod only when it is consistently and verifiably accurate, and it ignores priority and changefreq. Stamping the build date on every URL is the quickest way to get it ignored. A single sitemap is capped at 50,000 URLs or 50 MB uncompressed; beyond that, split it with generateSitemaps.
| Element | Where in Next.js | Google rule to respect |
|---|---|---|
| Title and description | metadata or generateMetadata in pages and layouts | Unique per page, written for readers |
| Canonical | alternates.canonical, with metadataBase in the root layout | One reference URL per piece of content |
| Hreflang | alternates.languages in metadata or in the sitemap | Reciprocal, self-referencing, plus x-default |
| Sitemap | app/sitemap.ts; generateSitemaps above 50,000 URLs | Accurate lastmod only; priority and changefreq ignored |
| Robots | app/robots.ts or public/robots.txt | Do not block pages you want removed with noindex |
| Structured data | application/ld+json script in a page or layout | Must match the visible text |
| Redirects | redirects in next.config, permanent for moves | 308 and 301 are both permanent redirects |
How do you add JSON-LD in Next.js?
Render it as an application/ld+json script tag inside the page or layout, as the Next.js JSON-LD guide recommends. Serialise with JSON.stringify and replace the < character with its unicode escape to prevent HTML injection; the same guide points to the Rich Results Test and the Schema Markup Validator for checking.
What to include: Organization on the home page, Article or BlogPosting on posts with a real author and real dates, BreadcrumbList on inner pages. Here, every page carries a single JSON-LD graph that connects the organisation, the website and the page, and the organisation lists the group's three brands as sub-organisations. Keep the schema saying exactly what the visible page says. It is hygiene, not a shortcut into AI answers, as we explain in how to get cited by ChatGPT and AI Overviews.
How much do speed and Core Web Vitals matter?
They matter as part of page experience: Google recommends achieving good Core Web Vitals and says they align with what its core ranking systems seek to reward. They will not rescue thin content, but they are the part of SEO that depends almost entirely on how you write the code.
2.5 s
- next/image with known dimensions: Next.js uses them to reserve the image's space and avoid layout shift.
- next/font to self-host fonts and load them with no layout shift.
- Server Components by default, with client components only where interaction needs them, keeps shipped JavaScript small.
- Lazy-load heavy parts such as 3D, maps and video with dynamic imports, as we do with the three.js scenes on this site.
Our pre-launch checklist
metadataBase and canonicals
Production domain in the root layout, a canonical on every page, no staging URLs left in tags.
Reciprocal hreflang
Every version lists itself and the others, plus x-default. Check two or three pages in the raw HTML.
Sitemap with honest dates
lastmod tied to the last real content change, not the build. Submit the sitemap to Search Console and Bing Webmaster Tools.
Staging robots and noindex removed
A blanket disallow or noindex from staging must never reach production; it is a classic launch-day mistake.
Redirects from old URLs
With permanent: true, Next.js answers with a 308, which Google's redirects documentation lists as permanent alongside the 301.
URL Inspection in Search Console
Check the rendered HTML of one page per template: home, service, article. If the text is not there, it is not there for Google.
If the site is the first step of a product, technical choices come before content; our SEO guide for startups explains the order. For subscription software, the SaaS SEO guide adds how to separate the marketing site from the app.
Frequently asked questions
App Router or Pages Router for SEO?
Both can render HTML on the server. The App Router has the Metadata API and built-in sitemap and robots conventions, so for a new project it is the more direct route.
Do I still need a package like next-seo?
With the App Router, usually not: the Metadata API covers titles, descriptions, canonicals, hreflang and Open Graph. An extra library adds a dependency to maintain without adding much.
How do I pre-render blog posts at build time?
Use generateStaticParams in dynamic routes: Next.js generates one page per returned parameter at build time instead of on each request. It is the simplest way to get static articles with all the text in the HTML.
How can I see what Google sees?
Use URL Inspection in Search Console, which shows the rendered HTML and any resources Google could not load. For structured data, use the Rich Results Test or the Schema Markup Validator.
Do 3D scenes and heavy animation hurt SEO?
Not in themselves. The risk is putting important text inside a canvas or loading everything up front and slowing the page. Keep content in the HTML and load the 3D afterwards, only when needed.
Sources
- Next.js Docs, generateMetadata (incl. streaming metadata) (19 October 2026)
- Next.js Docs, sitemap.xml file convention (19 October 2026)
- Next.js Docs, How to implement JSON-LD in your Next.js application (19 October 2026)
- Google Search Central, Understand JavaScript SEO basics (aggiornata 2026-03-04) (19 October 2026)
- Google Search Central, Tell Google about localized versions of your page (aggiornata 2026-09-21) (19 October 2026)
- Google Search Central, Build and submit a sitemap (aggiornata 2026-07-08) (19 October 2026)
- Google Search Central, Understanding Core Web Vitals and Google search results (aggiornata 2025-12-10) (19 October 2026)
- Google Search Central, Redirects and Google Search (aggiornata 2026-04-14) (19 October 2026)
Figures and definitions checked on the dates shown.




