# 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.

Updated 2026-09-14 · Source: https://porteur.ai/guides/trailing-slash-seo

## 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.

> Do not keep both forms live. Pick one canonical, 301 the other, and link only to the canonical. A rel=canonical tag alone is not a redirect.

## 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.

```js
# 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 }
    ]
  }
}

```

```bash
# 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
  }
}

```

```bash
# 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.

```bash
# 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

```

## Keep internal links, canonicals and sitemaps consistent

Google will follow the redirect, but do not make it clean up your own site. Link to the canonical everywhere. That saves crawl, improves reporting, and removes confusion.

- Update every internal link to the canonical form: navigation, footer, content, RSS, emails and UI microcopy
- Set rel=canonical to the canonical URL, not to the other form
- Update hreflang, Open Graph, Twitter cards and JSON LD to the canonical
- Regenerate the XML sitemap to list only canonical URLs
- Fix pagination, breadcrumbs and faceted links to match the choice

A fixed page looks like this: https://yourproduct.com/guides/getting-started with rel=canonical to that exact URL, every internal link pointing to it, and the sitemap containing the same URL. There is no /guides/getting-started/ anywhere on the site if you chose no slash.

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

```js
// 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.

```js
// 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.

## What to do with inbound links on the other form

Keep the redirect. You cannot force external sites to change their links. A 301 passes the request to the canonical URL. Do not stack another hop on top of it.

If a high value link uses the wrong form, ask for an update as a nice to have. It is not urgent if your redirect is correct. The one hop 301 is the safety net.

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

### Does a trailing slash affect SEO?

The choice itself does not win rankings. Consistency does. Two forms split signals and waste crawl. Pick one, redirect the other with a 301 in one hop, and link only to the canonical. That keeps reports and link equity clean.

### Should a directory path end with a slash?

You can choose. The web no longer relies on directories. Many teams use no slash for pages like /pricing and /blog. If you choose slashed URLs for section listings, keep it sitewide and redirect the no slash form. Do not add slashes to files with extensions.

### What about the home page and the root slash?

The root is the exception. https://yourproduct.com and https://yourproduct.com/ are the same. You do not need to redirect between them. Make your canonicals and links use one form across the site, but do not worry about the root pair.

### How do I set this in Next.js?

Set trailingSlash in next.config.js to match your choice. Add a redirects rule to 301 the other form. Use the Metadata API to set alternates.canonical per page. Emit only canonical URLs in app/sitemap.ts. Update internal links in code so you do not rely on the redirect for your own navigation.

### How do I encode a forward slash in a URL?

A path separator is a literal slash. Do not encode it in normal paths. If a slash must be data within a segment, you need to percent encode it as %2F and ensure your router and server treat it as data. Most sites avoid this pattern and use hyphens or encode in a query string instead.

### What if external sites link to the other form?

Keep the one hop 301. That is enough. If a key partner can update the link, ask for it, but it is optional once your redirect is correct. Do not chain redirects from legacy rules. Fold them into a single hop to the current canonical.

## Read next

- [Build a redirect map: one old URL, one new URL, tested](https://porteur.ai/guides/redirect-map): A practical redirect map: where to find old URLs, how to match them, store the table, apply 301s, and test every row for one hop and a 200.
- [Changing your URL structure: when it is worth it and how to do it](https://porteur.ai/guides/changing-url-structure): When to change URLs for SEO, how to plan the map and redirects, how to keep equity, and what to expect in Search Console after the switch.
- [www or non-www: pick one, redirect the other](https://porteur.ai/guides/www-vs-non-www): Pick www or non-www, there is no SEO gain either way. 301 redirect every path to the chosen host, set canonicals, and verify a Domain property.
- [Moving from HTTP to HTTPS: the checklist that keeps rankings](https://porteur.ai/guides/http-to-https-migration): Move from HTTP to HTTPS without losing rankings. Use one-hop 301s, update canonicals and sitemaps, fix mixed content, and verify the HTTPS property.
- [Canonical tags: what they do and the mistakes that cost rankings](https://porteur.ai/guides/canonical-tag): What a canonical tag does, when to use one, the mistakes that cost rankings, and how to check and fix canonicals on a small site.
- [Technical SEO checklist for a small site](https://porteur.ai/guides/technical-seo-checklist): Run these 20 technical SEO checks, in order. Each shows how to check it free and what fixed looks like for a site under 1,000 pages.

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: https://porteur.ai/
