Next.js pages not indexed by Google: causes and the fix path

By Nayan Kyada · · 6 min read

Part of The Sanity + Next.js Guide

Your developer launched the Next.js and Sanity site, the pages look fine, and Google still shows half of them as "Discovered - currently not indexed" or "Crawled - currently not indexed". If you are searching for why your Next.js pages are not indexed, the cause is almost always one of five fixable things, and none of them require a rebuild.

What the symptoms look like

Open Search Console and go to the page indexing report. The reason attached to each URL is the most useful clue you have. These are the patterns I see most often on Sanity-backed sites:

  • Pages excluded by a noindex tag, even though nobody meant to add one.
  • Alternate page with proper canonical tag, where Google thinks your page is a copy of another one.
  • Discovered or Crawled but not indexed, where Google found the URL and chose not to keep it.
  • Soft 404, where an empty or error-like page returns a normal status.
  • Submitted URL not found in the sitemap report, where the sitemap lists URLs that no longer exist.

Traffic that never arrives is a quiet loss. Nobody gets an alert. The cost is every month those pages could have ranked, plus the money spent building them. If the site replaced an older one, the loss is compounded, because the old URLs were ranking and the new ones are not. The website redesign SEO checklist covers the redirect side of that.

Cause 1: a noindex that should not be there

This is the first thing to rule out. Common sources: a shared metadata helper that sets robots to noindex when an environment variable is missing, a Sanity SEO field where editors tick a "hide from search" box on templates and the default leaks to every page, or a staging flag copied into the production environment on Vercel.

Do not trust the code. View source on the live URL and search for the robots meta tag. Also check the response headers for an X-Robots-Tag, which can be set in next.config or at the host level.

Cause 2: canonicals pointing somewhere else

If every page has the canonical set to the homepage, or to a preview domain, Google will drop the page. This happens when the canonical is built from a base URL environment variable that is wrong in production, or when a trailing slash difference makes the canonical not match the served URL.

Build canonicals from one source of truth and make them self-referencing per page. Here is the pattern I use in the App Router:

// app/[slug]/page.tsx
import type { Metadata } from 'next'
import { notFound } from 'next/navigation'
import { client } from '@/sanity/lib/client'
 
const SITE_URL = process.env.NEXT_PUBLIC_SITE_URL ?? 'https://example.com'
 
type Props = { params: Promise<{ slug: string }> }
 
export async function generateMetadata({ params }: Props): Promise<Metadata> {
  const { slug } = await params
  const page = await client.fetch(
    `*[_type == "page" && slug.current == $slug][0]{ title, "description": seo.description, "noIndex": seo.noIndex }`,
    { slug }
  )
  if (!page) notFound()
 
  return {
    title: page.title,
    description: page.description,
    alternates: { canonical: `${SITE_URL}/${slug}` },
    robots: page.noIndex ? { index: false, follow: true } : undefined,
  }
}

Note that robots is only set when the editor explicitly opts out. Leaving it undefined means the default, which is indexable.

Cause 3: the sitemap lists the wrong things

A sitemap that includes redirected URLs, noindexed pages, or drafts tells Google the site is untidy. A sitemap that is missing whole content types means Sanity documents never get discovered except through internal links. Generate it from Sanity, exclude opted-out documents, and use the real last-modified date:

// app/sitemap.ts
import type { MetadataRoute } from 'next'
import { client } from '@/sanity/lib/client'
 
const SITE_URL = process.env.NEXT_PUBLIC_SITE_URL ?? 'https://example.com'
 
export default async function sitemap(): Promise<MetadataRoute.Sitemap> {
  const pages = await client.fetch<{ slug: string; updated: string }[]>(
    `*[_type == "page" && defined(slug.current) && seo.noIndex != true]{ "slug": slug.current, "updated": _updatedAt }`
  )
  return pages.map((p) => ({
    url: `${SITE_URL}/${p.slug}`,
    lastModified: new Date(p.updated),
  }))
}

Then submit the sitemap URL in Search Console and compare submitted against indexed counts.

Cause 4: soft 404s and empty pages

If a Sanity document exists but its body is empty, or a dynamic route renders a "not found" message without calling notFound(), Google sees a thin page and reports a soft 404. Also be aware that when a page streams, the status code can already be sent as 200 before a not-found is triggered, and Next.js handles this by adding a noindex tag to the output. Check what your error pages actually return with a header check rather than assuming.

On the content side, add a Studio validation rule so editors cannot publish a page with no title, description, or body. That prevents thin pages at the source.

Cause 5: weak internal linking and duplicate content

"Crawled - currently not indexed" is often a quality signal rather than a technical fault. Pages that exist only in the sitemap with no links from other pages, near-identical location or service pages, and tag archives with one post each all land here. The fix is editorial plus structural: link important pages from navigation and related content, merge or remove near-duplicates, and give each page a clear reason to exist. Adding proper JSON-LD structured data helps Google understand the page, but it will not rescue a thin one.

The fix path and how to verify

  1. Export the not-indexed URLs from Search Console and group them by reason.
  2. Check the live HTML and headers of three URLs per group for robots, canonical, and status.
  3. Fix the shared cause in code (metadata helper, sitemap, environment variables) rather than page by page.
  4. Fix content causes in Sanity: thin pages, missing SEO fields, duplicates.
  5. Redeploy, use the URL inspection tool on one URL per group, then request indexing.
  6. Recheck the report after a couple of weeks. Counts should move toward the sitemap total.

If you are not sure whether the problem is technical or editorial, a Next.js website audit answers that quickly. Choosing a stack with this in mind from the start is covered in how to choose a CMS for SEO.

What to ask a developer

  • Can you show me the rendered robots tag, canonical, and status code for five live URLs?
  • Where does the base URL come from, and is it set correctly in production?
  • How is the sitemap generated, and does it exclude noindexed and redirected pages?
  • What happens in Studio when an editor publishes a page with an empty SEO field?
  • Will you give me a before-and-after of the indexing report?

A developer who answers these with evidence, not reassurance, is the right one.

Work I do

Diagnosing and fixing indexing problems on Sanity and Next.js sites is work I do as part of a Performance & Sanity audit, which starts from $800. You can see the details on the services page.

Frequently asked questions

Why are my Next.js pages not indexed by Google?

The usual causes are a stray noindex tag or robots rule, a missing or wrong canonical, a sitemap that lists the wrong URLs, thin or duplicated pages, and error pages returning a 200 status. Search Console's page indexing report tells you which one applies to each URL.

How long does it take Google to index pages after fixing them?

Usually days to a few weeks for a small or mid-sized site, and longer for a large site with weak internal links. Requesting indexing in Search Console for key URLs speeds up the first pages, but it does not guarantee anything.

Can draft mode or preview settings cause pages to be noindexed?

Yes. If a preview or staging environment variable leaks into production, or a shared metadata helper sets robots to noindex for non-production hosts, live pages can ship with a noindex tag. Always check the rendered production HTML, not just your code.