Trailing slash: two URLs for one page unless you decide

/page and /page/ are different URLs to Google. The root is the exception. Choose a form, redirect the other, and keep every link on your site consistent. This guide shows the exact steps and where to change it in Next.js.

By , founder of Porteur · Updated 14 September 2026 · Markdown

What Google treats as different

Google treats these as different URLs: /page and /page/. The content can be the same, but the URLs are not. That can split signals, waste crawl, and show duplicates.

  • /page and /page/ are different
  • /blog and /blog/ are different
  • /images/logo.png and /images/logo.png/ are different, and the second should 404

The root is the exception: https://yourproduct.com and https://yourproduct.com/ are the same to browsers and Google. You do not need a redirect there.

www and non www are also different hosts. Pick one host and redirect the other with a 301 on every path. Verify a Domain property in Search Console so you see both. Keep that separate from the trailing slash choice.

Pick one form and write the rule

You can choose either. Modern sites do not need to mirror a filesystem. The win is consistency and a clean redirect rule that runs everywhere.

  • Choose no trailing slash for pages, for example /pricing, /guides/getting-started, /blog
  • Keep the slash for section listings if you prefer, for example /blog/ only if you commit to it sitewide
  • Never add a slash to files with an extension, for example /app.js, /image.jpg, /feed.xml

Write the rule once. If you choose no trailing slash, redirect every path ending in a single slash to the same path without the slash. If you choose slashed URLs, do the opposite. Exclude the root and files with extensions from the rule so you do not break assets.

The redirect, one hop only

Use a 301 permanent redirect. Make it one hop, not a chain. Test it on every affected URL. Update internal links so the redirect is not used for your own clicks.

# Next.js, next.config.js
module.exports = {
  trailingSlash: false, // set true if you choose slashed URLs
  async redirects() {
    return [
      // If you choose no trailing slash
      {
        source: '/:path*/',
        has: [
          { type: 'host', value: '(?<host>.*)' }
        ],
        destination: '/:path*',
        permanent: true,
      },
      // If you choose slashed URLs, use the inverse:
      // { source: '/:path*', destination: '/:path*/', permanent: true }
    ]
  }
}
# nginx, no trailing slash canonical
map $request_uri $noslash_redirect {
  default 0;
  ~^(.+?)/$ 1;
}
server {
  # ...
  if ($noslash_redirect) {
    return 301 $scheme://$host$1; # $1 from the map's regex
  }
}
# Apache, no trailing slash canonical
RewriteEngine On
RewriteCond %{REQUEST_URI} .+/$
RewriteRule ^(.+?)/$ /$1 [R=301,L]

Exclude files with extensions and the root. For example, pattern your rule to skip \.(css|js|png|jpg|svg|xml|json|txt|ico|pdf)$ and to not rewrite the empty path. Test that /images/logo.png/ returns a 404, not a redirect to strip the slash. That avoids hiding mistakes.

# Quick tester: check final status is 200 and only one hop
# urls.txt contains a list of canonical URLs you expect to serve 200
while read -r url; do
  hops=$(curl -s -o /dev/null -w '%{num_redirects}' "$url/")
  code=$(curl -s -L -o /dev/null -w '%{http_code}' "$url")
  if [ "$hops" != "1" ] || [ "$code" != "200" ]; then
    echo "Check $url  hops:$hops  status:$code"
  fi
done < urls.txt

Next.js settings that touch URLs

Next.js does not choose your canonicals or write your titles. It gives you switches and files to express your choice. Set them and keep them in code so they survive deploys.

  • trailingSlash in next.config.js controls the build output form for static routes
  • redirects in next.config.js applies the 301 rule across the app
  • Metadata API: set metadataBase and alternates.canonical for each route or in generateMetadata
  • app/sitemap.ts should emit only canonical URLs
  • app/robots.ts should allow the canonical paths and, if needed, point to the sitemap
  • Do not add a slash to asset routes or API routes unless you want a 404
// app/[...slug]/page.tsx
export async function generateMetadata({ params }) {
  const path = Array.isArray(params.slug) ? params.slug.join('/') : ''
  const url = new URL(path ? `/guides/${path}` : '/guides', 'https://yourproduct.com')
  return {
    metadataBase: new URL('https://yourproduct.com'),
    alternates: { canonical: url.pathname },
    openGraph: { url: url.pathname, title: 'Getting started' },
    title: 'Getting started'
  }
}

If you switch trailingSlash in a live app, treat it as a URL change. Redirect in one hop, update canonicals and the sitemap, and expect a short period of fluctuation. Move one thing at a time. Do not redesign, change domain and flip the slash setting on the same day.

How to find mixed slashes on your site

You want a complete list before you write the redirect. Start with your own pages. Then look at links from outside. Fix your site first so you do not rely on the redirect for your own internal clicks.

  1. Search Console

    Open the Links report. Sort by Top linked pages. Spot pairs like /pricing and /pricing/. Click each to see linking pages. Fix the internal pages linking to the non canonical form. Use the Performance report to add a Page filter for /page and /page/ to see if both receive impressions.

  2. Your sitemap

    Download your live sitemap.xml. Scan for any /path/ entries if you chose no slash, or the inverse if you chose slashed URLs. Your sitemap should contain only canonical URLs.

  3. A local crawl

    Run a simple script against your own domain to list internal links. Grep for hrefs ending with a slash. Here is a quick Node snippet you can adapt. It prints any internal link that breaks your rule.

  4. Server logs

    If you have access, search access logs for requests to /path/ and /path. Group by URL. This shows which form is requested more. Use it to prioritise pages you need to fix.

// Node quick crawler: find internal hrefs that end or do not end with '/'
import fetch from 'node-fetch';
import { JSDOM } from 'jsdom';

const origin = 'https://yourproduct.com';
const wantSlash = false; // set true if you choose slashed URLs
const seen = new Set([origin]);

async function crawl(url, depth = 0) {
  if (depth > 2) return; // keep it small
  const res = await fetch(url);
  if (!res.ok) return;
  const html = await res.text();
  const dom = new JSDOM(html);
  const links = [...dom.window.document.querySelectorAll('a[href]')]
    .map(a => new URL(a.getAttribute('href'), origin))
    .filter(u => u.origin === origin);

  for (const u of links) {
    const path = u.pathname;
    const isFile = /\.[a-z0-9]{2,6}$/i.test(path);
    if (!isFile && path !== '/') {
      const endsWithSlash = path.endsWith('/');
      if (wantSlash && !endsWithSlash) console.log('Needs slash:', u.href);
      if (!wantSlash && endsWithSlash) console.log('Remove slash:', u.href);
    }
    const href = u.href.replace(/#.*$/, '');
    if (!seen.has(href)) { seen.add(href); await crawl(href, depth + 1); }
  }
}

crawl(origin).catch(console.error);

Also search your codebase. Look for hardcoded links in templates, docs and tests. A quick ripgrep for href="/ and exact paths like "/pricing/" finds most offenders. Fix Markdown docs as well as JSX and HTML. Rebuild and redeploy.

Changing a live site’s trailing slash choice

Treat it like a small site move. You are changing URL paths. Plan it, test it and switch when you can watch logs and Search Console. Expect a few weeks of fluctuation.

  1. Build a redirect map

    Export a list of current URLs from your sitemap and a crawl. Produce a two column map of old URL and new URL. Every /page/ to /page for no slash, or the reverse for slashed. Do not map anything to the home page.

  2. Apply at the edge or server

    Use your CDN or server to run the rule on every request. Avoid application level rewrites that miss static assets or add latency. Keep it one hop from old to new.

  3. Update the site output

    Flip trailingSlash in Next.js if you use it. Update every internal link, rel=canonical, hreflang and JSON LD. Regenerate the sitemap and submit it in Search Console.

  4. Test before switching

    Run a script to request every old URL. Check the final status is 200 and the hop count is one. Fix any 302s or loops. Only then switch.

  5. Monitor after the switch

    Use the Page with redirect and Soft 404 reports in Search Console to catch mistakes. Watch logs for 404s on assets like /app.js/ which signal a bad rule.

Edge cases and simple rules

  • APIs: never use a trailing slash on file like endpoints unless you define it. /api/users and /api/users/ are different routes
  • Assets: never add a slash to files. Let /file.ext/ 404. That reveals broken links quickly
  • Parameters: the trailing slash sits before ?query=params. Your redirect should not drop parameters
  • Case and slashes: respect case. /Page and /page are different on most servers. Do not rewrite case in the same rule
  • Root: leave it. / and the bare origin are the same. No redirect needed there

Questions

Sources

Check my site, free

Paste your URL to see which form your site outputs, how rivals link to it, and the mixed links you still ship across key pages.

  • Free check, no card
  • Read-only, your own accounts
  • Readable by your agent

Read next