Anthony Wentzel

Zero of 129 pages indexed: a Next.js canonical URL bug

Every page on our Next.js site set its canonical URL to the homepage, so Google indexed none of them. Three small config mistakes and the fix.

From 2026 · 2 min read · Published August 8, 2026

We'd been publishing on the Pineapples blog steadily: more than a hundred posts, FAQ schema, structured data, an llms.txt. Then in July 2026 we looked closely at Google Search Console and found a canonical URL problem.

0 of 129 URLs in the sitemap were indexed.

Three causes, each small, all compounding.

1. An inherited canonical URL

In the Next.js App Router, metadata set in a layout is inherited by every page beneath it. Both of our route-group layouts set:

app/(app)/layout.tsx
export const metadata = {
  alternates: { canonical: '/' }, // every page inherits this
}

So every blog post told Google, in its <head>, that its canonical version was the homepage. Google did exactly what it was told and treated them as duplicates.

The fix is a per-route canonical in the layout, plus an explicit one for each post:

// layout
alternates: { canonical: './' }, 
 
// app/blog/[slug]/page.tsx
export async function generateMetadata({ params }) {
  const { slug } = await params
  return { alternates: { canonical: `/blog/${slug}` } }
}

2. A site URL from a year ago

The sitemap was built from a NEXT_PUBLIC_SITE_URL environment variable that had been set about a year earlier, back when the apex domain was primary. Since then we'd made www canonical. So all 129 sitemap URLs pointed at the apex, and every one of them redirected.

A sitemap full of redirects tells a crawler that none of your listed URLs are the real ones. We rotated the variable in Vercel and updated the code fallbacks to match.

3. robots.txt agreed with the mistake

robots.txt pointed at the apex sitemap too. Same fix.

Why nobody noticed

Every individual piece looked fine. Pages rendered. The sitemap validated. Canonical tags were present. The mistakes only showed up in aggregate, in a report we weren't checking often enough.

The checklist we use now

  • Canonicals are per-route. Nothing sets an absolute canonical in a layout.
  • The site URL has one source of truth, and it matches the domain that doesn't redirect.
  • curl -I a handful of sitemap URLs after every domain change. Anything other than a 200 is a bug.

It's a humbling one: all that content work, invisible, because of a single inherited line.


Related: Seven rounds to a fast mobile PageSpeed score · Build a monitor, not a dashboard: the Cloudflare Zaraz false negative