DEV Community

dada qi
dada qi

Posted on

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.

What is a slug, precisely?

The slug is the human-readable identifier segment of a URL path:

https://example.com/blog/url-slugs-best-practices
                         └───────── slug ─────────┘
Enter fullscreen mode Exit fullscreen mode

It usually derives from a title, but it is not the title. It's an identifier with three jobs:

  1. Readable by humans — in the address bar, in search results, in a Slack message.
  2. Stable — it's part of a permalink. Links, bookmarks, and search engine indexes depend on it not changing.
  3. Safe — no encoding surprises across browsers, CDNs, log pipelines, and markdown parsers.

The rules that matter

1. Lowercase, always

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

2. Hyphens, not underscores

Google has been explicit about this for years: hyphens are word separators, underscores are not. url_slug_guide 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).

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

4. ASCII-safe by default

cafécafe, naïvenaive. 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.

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

Where naive implementations die

The classic Stack Overflow one-liner handles English and nothing else:

// ⚠️ the naive version
const slugify = (s) =>
  s.toLowerCase().trim().replace(/[^a-z0-9]+/g, "-").replace(/^-+|-+$/g, "");
Enter fullscreen mode Exit fullscreen mode

Feed it real-world input and watch:

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?

Fixes, in order of effort:

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

const slugify = (s) =>
  s
    .normalize("NFKD")                   // é → e + ́
    .replace(/[\u0300-\u036f]/g, "")     // strip combining marks
    .toLowerCase()
    .trim()
    .replace(/[^a-z0-9]+/g, "-")
    .replace(/^-+|-+$/g, "");

slugify("Beyoncé's Résumé"); // "beyonce-s-resume"
Enter fullscreen mode Exit fullscreen mode

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:

  1. 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.
  2. Transliterate — pinyin/romaji via a library (pinyin, kuroshiro). Readable ASCII, but lossy and adds a dependency.
  3. 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.

Quick sanity checks

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

What's the worst slug bug you've shipped? Mine involved an emoji, a CDN, and a very confused cache key.

Top comments (1)

Collapse
 
promptshereai profile image
Fouad El Mourabit

Great framing—especially the idea that a slug is a contract. For hosted platforms like Blogger, permalink stability matters even more because redirect control can be limited. I try to set a short custom permalink before publishing, keep it stable even if the title changes, and update internal links whenever a migration is unavoidable. It’s also worth testing slug generation with accents, Arabic text, and empty results instead of letting a generic regex decide silently.