Last Reviewed: October 16, 2026 | Fact-Checked: October 16, 2026
To get Next.js App Router pages indexed, let Google crawl them, give each page a unique title and canonical through the Metadata API, render the content as text on the server, list real URLs in sitemap.ts, and check results with Search Console's URL Inspection tool. Google says its AI features need no special markup. Skip sitemap priority, the keywords tag and FAQ schema for rich results.
Next.js SEO is mostly about not breaking basic things. A page that Google cannot crawl, render or reach through links will not be indexed, however good its metadata looks.
I'm Janak Pokharel, an SEO and paid ads consultant. I'm not a full-time developer, but my own site, janakpokharel.com.np, runs on Next.js with Supabase on Vercel, and I audit it with Google Search Console on a schedule. This checklist covers what Google needs to index a page, how to set metadata and canonicals, how to add JSON-LD safely, what belongs in sitemap.ts and robots.ts, and how to check indexing after a deploy. I checked every code pattern against the current Next.js docs on October 16, 2026. Some older guides still read params synchronously, so I note what the docs say now. I also list the mistakes I found on my own site. Use the checklist before launch and again after every deploy. The goal is simple: pages that Google can reach, read and trust.
What does Google need from a Next.js page to index it?
Google needs four things to index a Next.js page: permission to crawl it in robots.txt, a link path from pages it already knows, content it can read as text after rendering, and a canonical URL that points to the version you want indexed. Google also states that indexing is never guaranteed, even when every requirement is met.
Those four points come from two Google sources. Google's AI features documentation tells site owners to allow crawling, make content easy to find through internal links and keep important content in text form. The SEO Starter Guide says Google primarily finds pages through links from pages it already crawled.
Next.js helps with the third point. Server Components render HTML on the server. If a page can be prerendered, its metadata lands in the initial HTML. If metadata streams in, the Next.js docs say it is appended to the body and that Googlebot interprets it correctly. Bots that cannot run JavaScript still get metadata in the head.
How do you set metadata and canonicals in the App Router?
Set metadataBase once in the root layout, then export a metadata object or a generateMetadata function from each page or layout. Both work only in Server Components. In current Next.js, params is a Promise, so await it. Use alternates.canonical to name the preferred URL so duplicates resolve to one indexable page.
Root layout:
// app/layout.tsx
import type { Metadata } from 'next'
export const metadata: Metadata = {
metadataBase: new URL('https://example.com'),
}
A dynamic page:
// app/blog/[slug]/page.tsx
import type { Metadata } from 'next'
type Props = { params: Promise<{ slug: string }> }
export async function generateMetadata({ params }: Props): Promise<Metadata> {
const { slug } = await params
const post = await getPost(slug) // your own data source
return {
title: post.seoTitle,
description: post.seoDescription,
alternates: { canonical: `/blog/${slug}` },
}
}
Three rules from the docs are worth remembering. You cannot export both metadata and generateMetadata from the same segment. If a page needs client features, keep page.tsx as a Server Component and move the interactive part into a separate client file. A relative URL in a metadata field without metadataBase causes a build error. Also give every page its own title, and skip the keywords field. Google Search does not use the keywords meta tag.
How do you add JSON-LD safely?
Render JSON-LD as a script tag with type application/ld+json inside a layout or page component. The Next.js documentation warns that JSON.stringify does not sanitize strings, so replace the less-than character with its Unicode escape before injecting. Make sure the structured data matches the visible page text, as Google advises.
// inside app/blog/[slug]/page.tsx
const jsonLd = {
'@context': 'https://schema.org',
'@type': 'Article',
headline: post.title,
datePublished: post.publishedAt,
dateModified: post.updatedAt,
author: { '@type': 'Person', name: 'Your Name' },
}
return (
<article>
<script
type="application/ld+json"
dangerouslySetInnerHTML={{
__html: JSON.stringify(jsonLd).replace(/</g, '\\u003c'),
}}
/>
{/* page content */}
</article>
)
The Next.js JSON-LD guide recommends a native script tag, not next/script, because JSON-LD is data, not executable code. It also points to the Rich Results Test and the Schema Markup Validator for checking your output.
What belongs in sitemap.ts and robots.ts?
Your sitemap.ts should list only canonical, indexable URLs and set lastModified to the real date of the last significant edit. Google ignores priority and changefreq, and it uses lastmod only when the value is consistently and verifiably accurate. Your robots.ts should allow the pages you want indexed and point to the sitemap.
// app/sitemap.ts
import type { MetadataRoute } from 'next'
export default async function sitemap(): Promise<MetadataRoute.Sitemap> {
const posts = await getPublishedPosts() // only published, indexable posts
return [
{ url: 'https://example.com/', lastModified: homepageUpdatedAt },
...posts.map((post) => ({
url: `https://example.com/blog/${post.slug}`,
lastModified: post.updatedAt,
})),
]
}
// app/robots.ts
import type { MetadataRoute } from 'next'
export default function robots(): MetadataRoute.Robots {
return {
rules: { userAgent: '*', allow: '/', disallow: '/admin/' },
sitemap: 'https://example.com/sitemap.xml',
}
}
Some examples, including the one in the Next.js sitemap docs, use lastModified: new Date(). That stamps the current time on every build, so the date says nothing true about the page. Google's sitemap documentation also limits one sitemap file to 50,000 URLs or 50MB uncompressed.
What can you safely skip? Three things:
-
Sitemap
priorityandchangeFrequency. Google ignores both. - The keywords meta tag. Google Search does not use it.
- FAQPage JSON-LD for rich results. Google's FAQ documentation page states that FAQ rich results stopped appearing in Google Search on May 7, 2026. Mark up genuine Q&A if you like, but do not expect a rich result.
How do you confirm indexing after a deploy?
After each deploy, open Search Console's URL Inspection tool for your most important new or changed URLs. The report shows whether Google has indexed the page and which canonical it chose, using the last indexed version. A live test shows the current page but cannot confirm indexing, and Request Indexing has a daily limit with no guarantee.
Follow this order:
- Inspect the URL and read the indexed status and the Google-selected canonical.
- If the page is not indexed, run the live test and check the rendered screenshot, which is available only in a live test.
- Compare the canonical Google chose with the one you declared.
- Confirm the URL appears in your sitemap and has at least one internal link pointing to it.
- Wait. Google suggests allowing a few weeks before judging whether a change helped.
You can also search for your domain in Google. If results point to your site, you are in the index. The URL Inspection documentation explains each field.
What went wrong on my own site?
These are real problems from janakpokharel.com.np, found during recurring Search Console and crawl audits. Each one taught me a rule.
Missing environment variables caused silent fallback content. Production builds from GitHub ran on Vercel without NEXT_PUBLIC_SUPABASE_URL and NEXT_PUBLIC_SUPABASE_ANON_KEY. The site did not crash. It fell back to hardcoded content, so pages looked fine but showed old text. Rule: fail the build when a required variable is missing.
// lib/env.ts, imported by your data layer so it runs at build time
const required = ['NEXT_PUBLIC_SUPABASE_URL', 'NEXT_PUBLIC_SUPABASE_ANON_KEY'] as const
for (const key of required) {
if (!process.env[key]) throw new Error(`Missing ${key}`)
}
A leftover redirect buried a live post. A rule in next.config.mjs sent one published post to the blog index, while the sitemap still listed it. Rule: compare your redirect sources against your sitemap URLs after every content change.
Deleted pages kept getting linked. After I removed four service pages, a crawl found those URLs still linked 28 times. Twenty-four of the links lived in database content and four were hardcoded in a React page. Rule: content links live in two places, so search both before you delete a page. I repointed each link to the closest live page instead of adding redirects.
A wrong upload folder broke a build. A GitHub web upload placed changed files in a subfolder instead of the app directories, and the Vercel build failed. The fix was a pull request from a branch, reviewed in a preview deploy before merging to main.
Do AI search features need special markup?
No. Google's AI features documentation, last updated December 10, 2025, says a page must be indexed and eligible to show in Search with a snippet. It lists no additional technical requirements, no special schema.org markup and no AI text files. Crawl access still matters, because OpenAI, Anthropic and Google each run separate crawlers with separate robots.txt rules.
OpenAI documents separate bots for ChatGPT search, model training and user-initiated visits. Anthropic documents separate bots for training, search and user requests. Whether to allow the training bots is a business decision, not an SEO one. Check each operator's page before you edit robots.ts, because blocking a search bot can keep your site out of that product's answers.
Conclusion
Most Next.js SEO problems are basic: pages Google cannot reach, duplicate URLs, stale sitemaps and silent build fallbacks. Set metadata and canonicals through the Metadata API, escape JSON-LD, keep sitemap dates honest, and inspect new URLs in Search Console after every deploy. Treat the checklist as a routine, not a one-off task, because a single deploy can undo a good setup. Do one thing today: compare your redirect rules with your sitemap. If you want a second pair of eyes on your site's indexing, you can request an SEO proposal. I also offer technical SEO services, and I explain what an audit costs in my technical SEO audit cost guide.
Frequently asked questions
Should I use generateMetadata or a metadata object?
Use the static metadata object when values do not depend on route params or fetched data. Use generateMetadata when they do. You cannot export both from the same route segment, and both work only in Server Components.
Does the App Router hurt SEO because of client rendering?
Not by default. Server Components render HTML on the server, and prerenderable pages include their metadata in the initial HTML. The Next.js docs say streamed metadata is appended to the body and that Googlebot interprets it correctly. Keep important content in server-rendered text.
Do I need a package for sitemaps and robots.txt?
No. Next.js has built-in sitemap.ts and robots.ts conventions. A package can still help with unusual needs, such as very large sites that must be split across many sitemap files, but the basic setup needs no extra dependency.
Does FAQ schema still matter?
Google's documentation says FAQ rich results stopped appearing in Google Search on May 7, 2026. You can keep markup for genuine, visible Q&A, but do not add it to win a rich result. Whether other systems use it is a separate question.
How long until a new page is indexed?
Google says some changes take hours and others take months, and it suggests waiting a few weeks before judging results. Request Indexing in Search Console has a daily limit and guarantees nothing, so fix crawl and link problems first.
Janak Pokharel is an SEO and paid ads consultant based in Kathmandu and a co-founder of Limi Creatives. He publishes guides at Janak Pokharel's website.
Top comments (0)