afaqrashid.com  ·  portfolio · 2026 ed.

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-1

This 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"; done

Every line should start with 200. A 308 means the sitemap and your trailingSlash setting disagree.

Related

Sources