A URL is a contract. When you publish a resource at a given address, anyone who links to it, bookmarks it, or indexes it is trusting that address to remain valid. Tim Berners-Lee made this case plainly in Cool URIs don’t change — a document from 1998 that reads as freshly relevant today as it did when the web was young. His central argument: the only good reason for a URI to stop working is that the information itself no longer exists. Everything else — redesigns, CMS migrations, team reorganizations, technology changes — is an implementation detail that should never surface to the outside world through a broken link.
Designing URLs that can survive these pressures requires deliberate decisions made early, and a maintenance discipline that keeps redirects clean as sites evolve.
What Makes a URL Durable
RFC 3986, the authoritative specification for Uniform Resource Identifiers, defines the syntax rules every URL must follow. The spec itself is agnostic about what makes a URL good from a human-usability or longevity standpoint — that judgment sits with the site’s architect.
Durable URLs share a few properties:
They describe the resource, not the implementation. A URL containing .php, .asp, or /servlet/ is already carrying technical debt from the day it is published. The extension reveals the server-side technology and will become embarrassing or misleading the moment the stack changes. Strip extensions from public-facing URLs, or configure your server to serve extensionless routes from the start.
They are not dated unless the date is semantically meaningful. Many publishing systems default to paths like /2019/04/22/my-article/. This is serviceable for news organizations where publication date is genuinely part of the resource identity, but it creates a subtle problem: content that gets updated, evergreen reference material, and republished pieces all carry stale dates in their permanent addresses. If a date is present in a URL, it should be there because readers need it to understand the resource — not because it was the default setting in WordPress.
They are lowercase throughout. The RFC 3986 spec notes that the scheme and host components of a URI are case-insensitive, but the path is case-sensitive on most servers. /Docs/API-Reference and /docs/api-reference are technically different resources. Publishing mixed-case URLs creates duplicate content risks and link rot when incoming links guess the wrong case. Standardize on lowercase and enforce it with a redirect rule.
They use hyphens, not underscores, as word separators. This is a widely cited recommendation from Google’s own URL structure guidance and aligns with how most users parse text: url-design-best-practices is readable at a glance; url_design_best_practices is less so because underscores visually merge with underlined text in older rendering contexts. The distinction also matters to some crawlers’ tokenization of words in path segments.
Slug Design in Practice
A slug is the human-readable portion of a path segment — the /url-design-best-practices part of a full URL. Slug design is where most of the practical decisions are made.
Before and After
Consider a documentation site that ships with these URLs from an auto-generated CMS:
/docs?page=142&lang=en&version=2.1
/Docs/API_Reference/Getting_Started.html
/docs/2023/10/api-getting-started/
Each has a different problem. The first exposes implementation details via query parameters that should be path segments. The second has case inconsistency and underscores, plus a file extension. The third buries the content under a date that will age poorly.
A well-designed equivalent:
/docs/api/getting-started/
This path is lowercase, hyphenated, extensionless, and describes hierarchy meaningfully without exposing dates or technology. If the content is versioned and version matters to users, version belongs in the path explicitly:
/docs/v2/api/getting-started/
Not buried in a query string.
Descriptive Over Clever
Slugs should describe what the resource is, not perform cleverness or keyword-stuff for search engines. A URL like /7-secrets-web-designers-dont-want-you-to-know/ dates itself within months and signals low editorial standards. A URL like /web-design-fundamentals/ can serve the same content for years without embarrassment. The HTML semantics article covers a parallel principle: meaningful names at the structural level compound into more maintainable systems.
Directory Depth and Information Architecture
Path depth is a tradeoff between logical organization and URL length. There is no universal rule, but two failure modes are worth naming.
Too shallow: Everything dumped at root level. /getting-started/, /api/, /changelog/, /pricing/, /blog/, /contact/ all live at the same depth. This works for small sites but does not scale. When the site grows, there is no room to express relationships between resources without retroactively breaking existing URLs.
Too deep: /en/v2/docs/reference/api/endpoints/authentication/oauth/flows/authorization-code/. Each added segment might be defensible in isolation, but the cumulative depth creates URLs that are hard to type, hard to share, and hard to reason about when something breaks. As a practical heuristic, three to four segments from the domain covers most content hierarchies without over-nesting.
The right depth reflects the genuine hierarchy of the content. A site that publishes documentation, a blog, and a component library has three natural top-level sections. Each section has its own sub-hierarchy. That structure should be reflected in the URL scheme:
/docs/ — documentation root
/docs/api/ — API reference section
/docs/api/endpoints/ — endpoint reference
/blog/ — editorial content root
/blog/design-systems/ — category or tag
/components/ — component library root
/components/button/ — individual component
This approach also makes redirects more tractable. When a section moves, you redirect at the section root rather than managing hundreds of individual redirects.
Query Parameters vs. Path Segments
The distinction between /docs/api/ and /docs/?section=api is not merely aesthetic. Path segments and query parameters carry different semantic weight.
Path segments identify a resource. /products/barrier-gates/ names a specific thing. Query parameters filter or modify a view of a resource. /products/?sort=price&category=gates is a filtered view of the products collection, not a distinct resource.
This distinction matters for caching, canonicalization, and indexability. Search engines generally treat each unique query string as potentially distinct, which means faceted navigation and filtering interfaces can generate thousands of near-duplicate URLs. The canonical link element (<link rel="canonical">) and the robots.txt Disallow directive exist partly to manage this problem, but the cleaner solution is not to put filterable interface state into indexable URLs in the first place.
Use path segments for resources you want indexed and bookmarked. Use query parameters for state that modifies how a resource is viewed — sort order, pagination (debatable), language (also debatable — language is sometimes better expressed as a subdomain or path prefix), or search queries.
The web fonts guide illustrates a clean use of path segments for what are genuinely distinct resources rather than filtered views of a single collection.
Canonical Strategy
Even with careful URL design, duplicates arise. A page might be accessible with and without a trailing slash. It might be served on both http and https (though this should be eliminated at the infrastructure level). A pagination sequence might cause near-duplicate content at multiple addresses.
The canonical link element resolves these ambiguities:
<link rel="canonical" href="https://example.com/docs/api/getting-started/" />
This tells crawlers which version of a resource is authoritative. Every page should declare its canonical URL, including the canonical page itself (self-referential canonical links are correct and recommended). The canonical URL should always be the full absolute URL including scheme and domain — relative canonical URLs are technically valid per the HTML spec but create ambiguity in practice.
Trailing slash consistency deserves its own note. Both /docs/api/ and /docs/api should resolve, but only one should be the canonical form. Choose a convention and enforce it with a server-side redirect. The trailing slash form is conventional for directories; the no-trailing-slash form is conventional for files or document resources. Hugo, for example, generates trailing-slash URLs by default through its directory output structure — worth knowing when configuring canonical tags in templates.
Redirect Implementation
When URLs must change — and over the life of a site they will — 301 redirects are the mechanism for preserving the value of existing links. A 301 signals to crawlers that the move is permanent and that link equity should transfer to the new location.
On Apache servers, .htaccess RewriteRule directives handle this:
# Redirect a single old URL to a new URL
RewriteRule ^old-section/old-page/?$ /new-section/new-page/ [R=301,L]
# Redirect an entire old section to a new location
RewriteRule ^old-section/(.*)$ /new-section/$1 [R=301,L]
# Force trailing slash
RewriteCond %{REQUEST_FILENAME} !-f
RewriteRule ^(.*[^/])$ /$1/ [R=301,L]
# Enforce lowercase
RewriteMap lowercase int:tolower
RewriteCond %{REQUEST_URI} [A-Z]
RewriteRule (.*) ${lowercase:$1} [R=301,L]
The R=301 flag sets the HTTP status code. The L flag marks the rule as last — processing stops if this rule matches. Order matters: more specific rules should appear before broader catch-all patterns.
On Nginx, equivalent configuration uses return or rewrite directives:
# Single redirect
location = /old-section/old-page/ {
return 301 /new-section/new-page/;
}
# Section-level redirect using regex
location ~ ^/old-section/(.*)$ {
return 301 /new-section/$1;
}
Cloudflare, Netlify, and similar edge platforms expose redirect rules through their own configuration formats (_redirects files, page rules, Transform Rules) but the underlying logic is identical: match an incoming pattern, return a 301, point to the correct destination.
Redirect chains — where URL A redirects to URL B, which redirects to URL C — should be collapsed. Every hop in a chain adds latency and dilutes the signal clarity for crawlers. When auditing redirects on a large site, tools like Screaming Frog or the Redirect Path browser extension will surface chains that need collapsing.
The Maintenance Discipline
Durable URLs require ongoing attention, not just good initial decisions. A few practices that help:
Document your URL patterns. A short internal spec — even a README in the CMS or repository — that defines slug conventions, directory structure rules, and canonical format saves time when new team members make decisions. Without documentation, conventions drift.
Log and review 404s regularly. 404 responses are broken contracts. A spike in 404s after a deployment is a signal that redirects were missed. Google Search Console, server logs, or a monitoring tool like Sentry can surface these. Every 404 is a link in the wild pointing somewhere that no longer works.
Treat redirects as permanent infrastructure. Redirect rules accumulate. They should be version-controlled alongside the site’s other configuration, reviewed when the site migrates platforms, and tested in staging before deploys. A redirect file that exists only in production is a liability.
The principle underlying all of this is that the web’s value comes from links, and links depend on addresses staying valid. Designing URLs thoughtfully — and maintaining them conscientiously — is one of the more durable investments a development team can make in a site’s long-term health.
