Back to News & Insights
Web Development August 23, 2026 · 4 min read

URL Slugs: The Boring Detail That Quietly Affects Your SEO, Caching, and Bug Reports

Every web developer ships slugs. Almost nobody thinks about them until one breaks: a...

URL Slugs: The Boring Detail That Quietly Affects Your SEO, Caching, and Bug Reports

Every web developer ships slugs. Almost nobody thinks about them until one breaks: a duplicate-content SEO problem, a 404 after someone renames a post, or a bug report with a URL full of %E2%80%99.

This post covers what a good slug looks like, the edge cases that break naive implementations, and a copy-paste slugify() you can actually trust.

It usually derives from a title, but it is not the title. It's an identifier with three jobs: Readable by humans — in the address bar, in search results, in a Slack message. Stable — it's part of a permalink. Links, bookmarks, and search engine indexes depend on it not changing. Safe — no encoding surprises across browsers, CDNs, log pipelines, and markdown parsers.

/Blog/My-Post and /blog/my-post are different URLs to most servers and to Google. Mixed case invites duplicate-content splits and broken links from people retyping URLs. Normalize to lowercase at creation time and 301-redirect the wrong-case variants. Hyphens, not underscores

Google has been explicit about this for years: hyphens are word separators, underscores are not. urlslugguide reads as one token; url-slug-guide reads as three words. Hyphens also survive underlining in UI (an underscore under an underlined link is invisible). Short, but not cryptic

Aim for 3–6 meaningful words. Strip stop words (a, the, and, of...) when the slug stays readable without them: Title: "A Complete Guide to the URL Slugs That Google Actually Likes" Bad: a-complete-guide-to-the-url-slugs-that-google-actually-likes Good: url-slug-guide

The slug should survive the title being A/B tested. If you tie the slug to the exact title, every headline tweak becomes a redirect decision. ASCII-safe by default

café → cafe, naïve → naive. Accented characters work in modern browsers, but they get percent-encoded the moment they're copied into plain text: caf%C3%A9. That's ugly in search results and a minefield in log processing and old tooling. Never change a published slug (without a 301)

A slug is a contract. If you must rename, keep a redirect from the old slug forever. WordPress does this automatically; if you're rolling your own CMS, store historical slugs in a table and check it on 404.

| Input | Naive output | Problem | |---|---|---| | Beyoncé's Résumé | beyonc-s-r-sum | Accents deleted, not transliterated | | C++ vs C# in 2026 | c-vs-c-in-2026 | Meaning destroyed | | 你好世界 | (empty string) | CJK wiped out entirely | | 100% 🔥 tips | 100-tips | Fine — but did you decide that, or did it just happen? |

Transliterate before stripping. String.prototype.normalize("NFKD") decomposes accented characters into base + combining marks, which you can then remove:

Handle domain terms with a replacement map before the generic pass: {"c++": "cpp", "c#": "csharp", "&": "and"}. No amount of Unicode cleverness knows that C# is csharp — that's product knowledge.

Decide your CJK policy explicitly. Three legitimate options: Keep the script — /blog/你好世界 is valid and Google indexes it fine; it just percent-encodes when copied (%E4%BD%A0%E5%A5%BD...). Right choice for a zh/ja/ko-audience site. Transliterate — pinyin/romaji via a library (pinyin, kuroshiro). Readable ASCII, but lossy and adds a dependency. Fall back to an ID — /blog/post-8843 when the slug would come out empty. Boring and bulletproof.

The bug isn't picking the "wrong" option — it's not picking one and shipping whatever the regex happens to do.

Enforce uniqueness at the database. Two posts titled "Weekly Update" → same slug. Append -2, -3 on collision, and put a unique index on the column so a race condition can't sneak duplicates past you.

Before shipping a slug scheme, paste a few real titles from your content backlog through it — especially ones with apostrophes, ampersands, and non-English words. For one-off checks (a landing page, a campaign URL, a colleague's draft) I keep SlugGenerator.app in my bookmarks — paste text, get the slug, no install.

For the pipeline itself: ✅ My Post!!! and my post produce the same slug (idempotent normalization) ✅ Empty-after-stripping input falls back to something (ID, date) instead of "" ✅ Changing a title does not silently change the slug of a published post ✅ Old slugs 301 to new ones ✅ Unique index on the slug column

TL;DR Lowercase + hyphens + ASCII, 3–6 words, stop words dropped normalize("NFKD") for accents, replacement map for domain terms, explicit CJK policy Slugs are permalinks: never mutate without a 301, enforce uniqueness in the DB Test with your ugliest real titles, not "Hello World" — or throw them at SlugGenerator.app and eyeball the output

Want to discuss this further?

Book a free strategy call with our team to see how these insights apply to your specific business goals.

Book a consultation