How-To//6 min read/Jason Gordon
Trailing Slashes: The URL Decision Nobody Makes
/pricing and /pricing/ are two different URLs to your server, to Google and to your analytics. Most sites never pick one. Here is why it costs you, which form to choose, how we set it on Vercel and in custom code, and a curl loop that audits any site in ten minutes.

companya.com/pricing and companya.com/pricing/ are two different URLs. Your server thinks so, Google thinks so, your analytics thinks so. Most sites never pick one, so the framework picks for them, inconsistently, and nobody notices until the numbers stop adding up.
Here is the whole answer up front: pick one form, point every internal link straight at it, and 301 the other. Neither form is better. Being inconsistent is what costs you.
They are genuinely two different URLs
A URL path is a literal string, and /pricing is not the same string as /pricing/. That is RFC 3986, the spec every browser, CDN and crawler implements.
There is exactly one exception. At the root, companya.com and companya.com/ are the same URL by spec, because an empty path is defined as equivalent to /. Every other path in your site is not covered by that rule.
The convention comes from the filesystem era. A trailing slash meant a directory and Apache would serve its index file. No slash meant a file. Modern frameworks resolve routes from a table, not a disk, so the distinction carries no meaning anymore. It just carries consequences.
Google's Search Central team has held the same position since their 2010 post To slash or not to slash: the two forms are separate URLs that can serve different content, there is no ranking preference between them, and the job is to pick one and be consistent.
The bug almost nobody knows about
Relative links resolve differently depending on the slash, and this breaks real sites. The algorithm is defined in RFC 3986 section 5, Reference Resolution: a relative path is merged against the base URL with everything after the last slash discarded. The trailing slash is what decides where that last slash falls.
Put <a href="page-two"> in your template. On /page it resolves to /page-two. On /page/ it resolves to /page/page-two. Same HTML, same template, two different destinations.
The same applies to relative asset paths. <img src="logo.png"> on /about pulls /logo.png. On /about/ it pulls /about/logo.png, which is a 404.
This is why a site can look perfect on the canonical form and quietly 404 half its assets on the other. If you have ever chased a bug that only reproduces on "some pages" and never on the homepage, check the slash before you check anything else.
Four costs, all real
Inconsistency is not a tidiness problem. It has a bill attached.
| Cost | What happens |
|---|---|
| Split analytics | /pricing and /pricing/ appear as two rows in GA4. Every report on that page is understated. |
| Split crawl and link equity | Google crawls both, indexes one, and backlinks pointing at the wrong form pass through a redirect. |
| A redirect hop on every internal click | Each hop is an extra round trip before any content arrives. Users on mobile networks pay the most. |
| Broken relative links and assets | Per the section above. Silent, and it looks like a random bug. |
The redirect hop deserves a note on honesty. A redirect adds a full request-response cycle before the real page starts loading, and Catchpoint's Web Performance 101 documents one page where 46 redirects contributed to an almost 13-second load. What nobody has published is a clean per-redirect millisecond figure, because it depends on connection reuse, protocol and network. Treat it as one avoidable round trip, not as a number you can quote.
Which form should you pick?
Whichever one your site already uses. This is not a taste decision and there is no upside to switching.
- Existing site with search history: match what Google has already indexed. Check Search Console, not your preference.
- Migrating from WordPress: WordPress defaults to trailing slash (how we move WordPress clients), so keep the slash unless you enjoy remapping every URL you own.
- New build: match your framework default. Next.js and Vite ship slash-free.
- Never switch an established form for aesthetics. The migration cost is real. The gain is zero.
Company A runs a WordPress site at companya.com/services/ with four years of Analytics history. They rebuild on Vercel. The correct answer is trailing slash, because everything they already own points there. What the new stack would have preferred is irrelevant.
How we do it on Vercel
One config flag, and Vercel handles the redirect at the edge (why we host there). It lives in vercel.json or its TypeScript equivalent.
{
"$schema": "https://openapi.vercel.sh/vercel.json",
"trailingSlash": true
}
Set true and paths without a slash redirect to the slash form. Set false and the reverse. Four things worth knowing:
- Vercel issues a 308, not a 301. Both are permanent, Google treats them the same, and 308 additionally preserves the request method and body. A 301 historically let clients downgrade POST to GET. Query strings and UTM parameters survive either way.
trailingSlash: trueexcludes paths with file extensions./sitemap.xmldoes not become/sitemap.xml/.- Do not set this in two places. If you are on Next.js,
next.config.jsalso has atrailingSlashoption. Pick one layer. Conflicting values produce redirect loops. - Legacy URL maps go in bulk redirects, not into
vercel.json.vercel redirects upload redirects.csvhandles thousands of rules without a deployment. Nobody should be hand-writing 800 redirect entries.
For static HTML, cleanUrls: true strips .html extensions and redirects to the extensionless path. Set it alongside trailingSlash, not instead of it.
How we do it in custom code
The principle is the same everywhere: normalise once, at the edge, and normalise your internal links at build time.
- Nginx:
rewrite ^(.*[^/])$ $1/ permanent;to add, orrewrite ^(.*)/$ $1 permanent;to remove. - Apache:
DirectorySlash Onplus aRewriteRulein.htaccess. - Cloudflare: a Redirect Rule or a Worker on the path, which puts it ahead of your origin entirely.
- Express: one middleware before your router, comparing
req.pathand issuingres.redirect(308, ...). - Astro, Hugo, Eleventy: all three have a
trailingSlashbuild option. Use it rather than post-processing. - Nginx reference: the
rewritedirective docs cover flag behaviour if you need anything beyondpermanent.
The internal links are a separate job, and it is a source change, not a redirect. Rewrite them at the link layer, not by hand. One function inside your <Link> component that normalises every internal path on output, plus the same function in your sitemap generator and your canonical tag, means nothing can drift back. Editing 800 href attributes by hand guarantees the 801st is wrong by Friday.
The nine places the form has to match
This is the part teams skip, and it is why sites stay half-migrated for years.
- Every internal
href - The
<link rel="canonical">tag, and each page must canonical to itself, not the homepage - Every
<loc>insitemap.xml - Any path referenced in
robots.txt hreflangannotations@idandurlfields in your JSON-LD- Your Search Console property and any URL inspections
- GA4 and GTM configs, plus every ad landing URL you are paying for
- The redirect rule catching the other form
One more: collapse your redirect chains. http://companya.com/pricing going to HTTPS, then to www, then to the slash form is three hops where one will do. Write the rule so the first response lands on the final URL.
The canonical-to-homepage mistake in item 2 is worth calling out on its own. It is the single most common trailing-slash-adjacent bug I see in audits, usually on campaign landing pages, and it actively tells Google to drop the page from the index.
Audit any site in ten minutes
Run this against your own domain. It tells you the current state before you change anything.
for p in / /pricing /about /blog; do
echo "== $p"
curl -sI "https://companya.com$p" | head -2
curl -sI "https://companya.com$p/" | head -2
done
You want exactly one form returning 200 and the other returning 301 or 308. Two 200s means both forms are live and you are splitting everything. Two redirects means you have a loop.
Then check three more things: Search Console coverage for both forms appearing as separate URLs, your GA4 page-path report for duplicate rows, and a crawl of your sitemap confirming every <loc> matches the form that returns 200.
One rule to take away. Pick a form. Point every internal link straight at it. Redirect the other. It is an afternoon of work that most sites never do, and it quietly costs them for years.
We handle this as standard on every SpecLoop build. If you want to know what your current site is doing, the curl loop above will tell you in about a minute.
Sources & references
- RFC 3986, Uniform Resource Identifier (URI): Generic Syntax, incl. section 5 Reference Resolution
- Google Search Central, To slash or not to slash (April 2010)
- Catchpoint, Web Performance 101: redirects (46 redirects on one page, almost 13 seconds to load)
- Vercel docs, Configuring projects with vercel.json (trailingSlash, cleanUrls)
- nginx docs, ngx_http_rewrite_module
Frequently asked
Questions people ask about this
- No. Neither form ranks better. Google treats them as two distinct URLs with no preference between them. Consistency is the only thing that affects your results.
About the author
Jason Gordon
Founder, SpecLoop
Jason Gordon is the founder of SpecLoop, a spec-driven AI development studio that builds production-grade business applications from a complete specification, in weeks, at flat rate, with 100% code ownership. He also builds GeoTest, a free tool that scores how visible a site is to AI search engines. He writes about AI search, spec-driven development, and why most AI builds never reach production.
Keep reading
Related posts

Strategy
Why a Custom Native AI Site Beats WordPress and Rented Platforms in the New Google Era
Google's AI Overviews now appear on nearly half of all searches and are cutting organic traffic 20–40%. Here's why rebuilding on a custom native site is the only durable answer for security, speed, and AI visibility.

Engineering
Spec-Driven Development: A Practical Guide for AI-Assisted Teams
Spec-driven development is the discipline of writing the rules down before the code, entities, permissions, states, edge cases, and acceptance criteria, and keeping that document alive as the build progresses. Here is what a working spec contains, how to write one in an afternoon, and how it changes the economics of AI-assisted engineering.

Strategy
Why We're Moving Our Clients Off WordPress
We still maintain dozens of WordPress sites. Here's the honest operator view from inside the stack, what changed, why we're migrating our own book of business to custom native sites, and how we do it without breaking anything.

Strategy
Why We're Migrating Our Own WordPress Clients to Custom Native Sites in 2026
We still maintain dozens of WordPress sites. Here's the honest operator view from inside the stack, what changed in 2026, why we're moving clients off, and how we're doing it without burning the platform that served them well for a decade.
Next step
Want a spec for your build?
We write the full specification before any code is generated, then ship in 30–60 days at one flat rate. You own every line.