Next.js trailingSlash: Why Your Sitemap URLs Redirect
With trailingSlash: true, Next.js redirects /about to /about/ and rewrites your canonical tags and <Link> hrefs to match. It does not touch the URLs you return from sitemap.ts, or plain <a> tags. If those lack the slash, every one of them answers with a 308 redirect.
I tested both settings on Next.js 16.4.4 with next build and next start: a root layout with metadataBase, an /about page with its own canonical, a blog post from generateStaticParams, and a sitemap.ts. I read the status and tags with curl.
Without trailingSlash (the default)
/about 200 canonical https://example.com/about
/about/ 308 Location: /about
/blog/post-1 200
/blog/post-1/ 308 Location: /blog/post-1This is consistent. Write every internal link and sitemap URL without the slash and nothing redirects.
With trailingSlash: true
/about 308 Location: /about/
/about/ 200 canonical https://example.com/about/
/blog/post-1 308 Location: /blog/post-1/
/blog/post-1/ 200 canonical https://example.com/blog/post-1/The redirects flip, and the metadata follows. The /about page set its canonical as "/about", and Next.js output https://example.com/about/. It did the same when I wrote the canonical as a full URL, https://example.com/about, and og:url got the slash too.
Links were split. On the home page, <Link href="/about"> rendered as href="/about/", but a plain <a href="/blog/post-1"> stayed as written, so that link points at a redirect.
The sitemap is not rewritten
This sitemap.ts returned the URLs without a slash:
export default function sitemap(): MetadataRoute.Sitemap {
return [
{ url: "https://example.com/about" },
{ url: "https://example.com/blog/post-1" },
];
}With trailingSlash: true, sitemap.xml still listed https://example.com/about and https://example.com/blog/post-1, and both returned 308. Google's sitemap guide says it will crawl your URLs exactly as listed, and to include the URLs you want to see in search results. A sitemap full of redirects lists none of them.
In Search Console, these URLs end up under "Page with redirect". Google describes that as a non-canonical URL that redirects to another page and won't be indexed.
The fix: one helper for sitemap URLs
// app/sitemap.ts
import type { MetadataRoute } from "next";
const BASE = "https://example.com";
// Must match trailingSlash in next.config.
const url = (path: string) => `${BASE}${path.endsWith("/") ? path : `${path}/`}`;
export default function sitemap(): MetadataRoute.Sitemap {
return ["/", "/about", "/blog/post-1"].map((path) => ({ url: url(path) }));
}After the change, the sitemap listed https://example.com/, https://example.com/about/ and https://example.com/blog/post-1/, and all three returned 200. If you leave trailingSlash off, drop the slash in the helper instead, so the sitemap matches the URLs that return 200.
Use <Link> for internal links, which follows the setting for you. For plain <a> tags, links in Markdown or CMS content, and hreflang URLs you build yourself, write the paths the same way as the helper.
Check your own site
curl -s https://your-site.com/sitemap.xml \
| grep -o "<loc>[^<]*" | sed "s/<loc>//" \
| while read u; do echo "$(curl -s -o /dev/null -w "%{http_code}" "$u") $u"; doneEvery line should start with 200. A 308 means the sitemap and your trailingSlash setting disagree.
Related
- Next.js sitemap.ts: revalidate, lastmod and splitting, tested
- Next.js canonical URL: 4 traps I tested and the fix
- Next.js redirect 307 vs 308 vs 301 for SEO: what I tested
- Next.js development