Skip to content
pageinspection

Two live URLs for every page, decided by one character

Neither form is better. That is the whole difficulty: because there is no right answer, the decision never gets made, and a site ends up serving both, which is a duplicate of every page it has.

The short answer

A trailing slash makes two different URLs. Serving the same content at both is duplicate content with no stated preference, and internal links that mix the two forms send crawlers through a redirect on every hop. Pick whichever form your stack already generates, enforce it with one redirect at the edge, and make every internal link match.

ImportantAudit check · Slash consistency

One character, two addresses

To anything reading a URL, /guides/sharpening and /guides/sharpening/ are different resources. There is no rule making them equivalent. The convention that a trailing slash means a directory comes from serving files off a disk, and on a site where every path is a route it carries no meaning at all.

The one place the distinction is genuinely load-bearing is relative link resolution. From /guides/sharpening/, a relative link to grits resolves to /guides/sharpening/grits. From /guides/sharpening, the same link resolves to /guides/grits. If your templates use relative URLs anywhere, serving both forms means serving two pages whose links point at different places.

Everything else about the choice is arbitrary, and both major forms are used by large sites without any observable disadvantage. What matters is that exactly one of them is reachable.

Pick the form your stack already prefers

The decision procedure is short: find out what your framework, host and CDN do by default, and agree with them. Fighting a default is how you end up with two layers normalising in opposite directions and an extra redirect on every request.

  • Check your framework's setting first. Most have one flag that decides this and applies it to generated links, generated sitemaps and server redirects at once. That is the cheapest place to make the decision, because everything downstream inherits it.
  • Then check your host or CDN. Static hosts frequently add or strip a slash before your application sees the request. If your framework says one thing and your host does the other, you get a redirect loop or a permanent extra hop, and neither is obvious from the code.
  • Then enforce with a 301, once. One rule, at the edge, redirecting the form you did not choose. Permanent, not temporary, so the decision is durable.
  • Then fix your own links. A site that redirects correctly and links internally to the redirecting form is paying a hop on every internal navigation, and telling every crawler that the canonical form is the one you redirect away from.

The one asymmetry worth knowing: the root is always /. Any rule that strips trailing slashes has to exempt it, and a rule that does not is the classic cause of a redirect loop on the home page.

Compare the form against what the page claims

This reads the URL you enter, notes which form it was served at, and compares that against the canonical the page declares. A disagreement means the address you can reach is not the address the page says it is, which is the sharpest single-page version of this finding.

Compare the slash form against the canonical

No signup required. Each free search audits one page, paste any URL to see it in action.

A pass tells you one page agrees with itself. Consistency across every template, link, sitemap entry and redirect rule is the actual check, and it needs the crawl.

Where the two forms get generated

Nobody types both forms. They are produced, in four places, and each one produces a different symptom.

sources.txt
source                        symptom
----------------------------  ------------------------------------------
hand-written internal links   some links redirect, some do not, on the
                              same page
the sitemap generator         sitemap lists the form your redirect rule
                              sends away from
the canonical helper          canonical disagrees with the URL served,
                              on every page
og:url and structured data    a share is credited to one form, the
                              index to the other

The third row is the one to fix first, because a canonical that contradicts the served URL undermines the tag everywhere else it appears. And the fourth is why the og:url pairing is a separate check: two self-declarations disagreeing about a slash split a share count as effectively as disagreeing about a domain.

A single helper solves all four. One function that takes a path and returns the absolute, normalised, canonical URL, used by your link components, your canonical tag, your sitemap and your Open Graph output. Then the form is defined in one place and cannot drift.

Why an arbitrary choice is rated Important

Because the arbitrariness is the point. The two forms are equivalent, so nobody argues for one, so the decision is never made, so both are served. That is the failure mode, and it applies to every URL on the site at once.

Scope is what earns the Important rating. Most findings affect a page or a template. This one, when it is wrong, doubles your URL space: every page has a twin, every internal link may be pointing at the wrong one, and every signal is potentially divided. There is no other single character in this audit with that reach.

It stays below Critical because both forms work. Visitors get the page, crawlers get the page, and a canonical tag or a redirect resolves the ambiguity as soon as somebody decides. Nothing is excluded, which keeps it out of the top band, and the sheer breadth keeps it well clear of the bottom one.

Testing both forms in one command

Two requests per URL, and the pair of answers is the finding.

check-slash.sh
for path in /guides/sharpening /shop /about; do
  for url in "https://example.com$path" "https://example.com$path/"; do
    printf '%-46s %s\n' "$url" \
      "$(curl -s -o /dev/null -w '%{http_code} -> %{redirect_url}' "$url")"
  done
done

# and the root, which must never be stripped
curl -s -o /dev/null -w 'root: %{http_code} %{redirect_url}\n' https://example.com/

One 200 and one 301 per pair is correct, in whichever direction you chose. Two 200s means both forms are live and you have a duplicate of every page. Two 301s, or a redirect pointing back at itself, is a loop, and it usually means your framework and your host disagree.

The last command is worth keeping in whatever you use for smoke tests. A slash rule that catches the root is the fastest way to take a whole site offline with a configuration change nobody reviewed.

Questions this check raises

Does the trailing slash matter for SEO?
Only in that inconsistency matters. Neither form ranks better. What costs you is serving both, which duplicates every page, or linking to one form while redirecting to the other, which adds a hop to every internal navigation and every crawl.
Which form should I choose?
The one your framework already produces, because fighting the default means maintaining a rule forever. Next.js and most static hosts default to no trailing slash; some CMSs and Apache directory serving default to having one. The choice is arbitrary; the enforcement is not.
How do I enforce one form?
A single 301 at the edge, in the CDN or the server config, so it applies before any application code runs. Then fix the internal links, because a redirect that fires on every internal navigation is a redirect you are paying for on every page view rather than only on external traffic.