Most broken links aren't caused by a page being deleted. They're caused by a small mistake in one part of the URL: a relative path resolved from the wrong place, a space that wasn't encoded, a query string that got mangled by an email client.

If you understand the parts of a URL, you can usually look at a broken link and say which part broke in a few seconds. Here's the tour.

https://user@shop.example.com:8443/products/blue%20chair?color=navy&size=l#reviews \___/ \__/ \______________/ \__/\__________________/ \________________/ \_____/ scheme user host port path query fragment 

You can take any URL apart with the built-in URL class in JavaScript (in browsers and Node):

const u = new URL("https://shop.example.com:8443/products/blue%20chair?color=navy&size=l#reviews"); console.log(u.protocol); // "https:" console.log(u.hostname); // "shop.example.com" console.log(u.port); // "8443" console.log(u.pathname); // "/products/blue%20chair" console.log(u.searchParams.get("color")); // "navy" console.log(u.hash); // "#reviews" 

Let's go part by part.

Scheme: https:

Tells the browser how to fetch: https, http, mailto, tel, ftp.

How it breaks links:

  • Missing scheme. href="www.example.com" isn't a link to example.com. It's a relative link to a path called www.example.com on your own site. This is one of the most common broken links in CMS content.
  • http that doesn't redirect. If the target site dropped plain HTTP, http:// links fail even though the page exists.
// what a missing scheme actually resolves to new URL("www.example.com", "https://myblog.dev/posts/hello/").href // → "https://myblog.dev/posts/hello/www.example.com" (broken!) 

Host: shop.example.com

The domain name (or IP address) of the server.

How it breaks links:

  • Domain expired or changed. The whole site is gone, or moved to a new domain without redirects. Every link to it breaks at once.
  • Subdomain removed. blog.example.com got folded into example.com/blog and nobody set up redirects.
  • Typos. exmaple.com. Some of those typo domains are even registered by someone else.

Port: :8443

Usually invisible (443 for HTTPS, 80 for HTTP). When you see an explicit port in a public link, be suspicious.

How it breaks links: URLs copied from a staging or development environment, like http://localhost:3000/pricing or https://example.com:8443/admin, get pasted into content and published. They work for whoever wrote them and nobody else.

Path: /products/blue%20chair

The location of the resource on the server. This is where most link rot happens.

How it breaks links:

  • Pages moved or renamed without redirects (the classic).
  • Case sensitivity. On many servers, /Products/ and /products/ are different paths. A link that worked on a case-insensitive development machine can 404 in production.
  • Relative paths resolved from the wrong place. This is the subtle one:
const base = "https://example.com/docs/guide/"; // note the trailing slash new URL("setup", base).href; // "https://example.com/docs/guide/setup" new URL("../setup", base).href; // "https://example.com/docs/setup" new URL("/setup", base).href; // "https://example.com/setup" const base2 = "https://example.com/docs/guide"; // no trailing slash new URL("setup", base2).href; // "https://example.com/docs/setup" (different!) 

The same relative link resolves to different URLs depending on whether the current page ends with a slash. Move a page from /guide/ to /guide, and every relative link on it can quietly shift.

Encoding: %20 and friends

URLs can only contain a limited set of characters. Spaces, many symbols and non-ASCII characters must be percent-encoded: a space becomes %20, é becomes %C3%A9.

How it breaks links:

  • Unencoded spaces in file names (/files/Annual Report.pdf). Some tools encode them, some cut the link off at the space.
  • Double encoding. %20 gets encoded again to %2520, which points somewhere that doesn't exist.
encodeURI("https://example.com/files/Annual Report.pdf"); // → "https://example.com/files/Annual%20Report.pdf" encodeURI("https://example.com/files/Annual%20Report.pdf"); // → "https://example.com/files/Annual%2520Report.pdf" (double-encoded: broken) 

The fix is boring but effective: avoid spaces and special characters in file names and slugs entirely.

Query: ?color=navy&size=l

Parameters for the server: search terms, filters, tracking tags.

How it breaks links:

  • The server ignores or rejects parameters it doesn't expect after a redesign. A link to ?id=123 stops working when the new site uses /item/123.
  • & turned into & and back. In HTML source, & should be written as &. Copying between HTML, Markdown and plain text sometimes leaves a literal & in the URL, so the server sees a parameter called amp;size.
  • Tracking parameters making "different" URLs for the same page. That's not broken, but it confuses analytics and deduplication.

Fragment: #reviews

Points to a spot within the page. Here's the key fact: the fragment is never sent to the server. The browser handles it after the page loads.

How it breaks links: if the target heading's id changes, the page still loads with a 200, but the browser lands at the top instead of the section. No status code reports it. A link checker has to load the page and look for the id to catch it.

Diagnosing a broken link, part by part

When a link fails, walk it left to right:

  1. Scheme: present? https?
  2. Host: does the domain still resolve? (dig +short example.com)
  3. Port: any explicit port that looks like dev or staging?
  4. Path: right case? Moved? Resolved relative to the right base?
  5. Encoding: spaces, %25, odd characters?
  6. Query: &, or parameters the new site ignores?
  7. Fragment: does the anchor id still exist?

Most broken links give themselves away by step 4.

Automated checkers do the same walk at scale. A simple script with new URL() catches missing schemes and localhost links, and a crawler such as linkbruno.com checks the rest against live responses.

Related reading

Which part of the URL has caused your most annoying broken link? My vote is the trailing-slash relative-path trap. Tell me yours in the comments.