Web

The most common broken link on a documentation site points at the same page.

External links get audited. In-page anchors do not, which is why a rewritten heading quietly breaks every deep link, table-of-contents entry and support answer that pointed at it.

What a static check can see

Given a document, a check can extract every heading, compute the slug each one produces, and list every internal fragment link it contains. Comparing the two lists gives you the dead ones.

  • Fragment links that match no heading on the page.
  • Placeholder fragments: a bare # or #content, which look like links and go nowhere.
  • Duplicate slugs: two headings with the same text, where the second silently gets a suffix.
  • Links to headings that no longer exist because the section was renamed or removed.

The cross-page version, and why it is harder

Links of the form /page#section need both pages. A single-document check cannot resolve them, so it should report them as unresolved rather than as broken.

Resolving them needs a crawl of the site, which is a different job: fetch each page, extract ids, then check fragments against the extracted set.

That is worth doing after any content migration, and it is the check that catches the long tail of anchors linked from support documentation and old campaigns.

After a rewrite, crawl before you announce

Rewrites destroy anchors in bulk, and the damage is invisible on the page: everything renders, and the links that matter are in other people's bookmarks and support threads.

So crawl the site immediately after publishing, compare the fragment set against the previous version, and either restore the ids or add redirects for the ones that cannot be.

Record the mapping. A small table of old fragment to new fragment is the artefact that makes the next rewrite survivable.

What this check does not cover

It does not tell you whether the section an anchor points at is the section the reader needed. A link can resolve perfectly and still send somebody to the wrong place.

It also does not check external destinations, which is link rot rather than anchors, and it does not see fragments that are generated by client-side JavaScript at runtime.

Those need a crawl and a renderer respectively, which is why anchors and link integrity are usually separate audit passes.

What this covers

  • Fragments resolved

    Every in-page # link matched against the heading slugs that exist.

  • Placeholder hrefs

    Bare hashes and example fragments reported rather than ignored.

  • Duplicate slugs

    Headings that produce the same id, which breaks the second link silently.

  • Cross-page fragments

    Reported as unresolved by a single-file check rather than called broken.

  • Post-rewrite crawl

    Old and new fragment sets compared, with redirects or restored ids.

Questions this page usually raises

Why did my anchor break when I only fixed a typo?
Because the anchor is derived from the heading text. Correcting the heading changes the slug, so the old fragment no longer matches.
Can one check validate anchors across the whole site?
Not from a single document. Cross-page fragments need a crawl that collects ids from every page, which is a separate pass.
Do anchors matter for search?
They help pages appear at the right section for a query, and they are how support teams send people to an exact answer. Broken ones quietly reduce both.
What about anchors created by JavaScript?
A static check cannot see them. Fragments rendered at runtime need a real browser or a crawl that executes scripts.
Does checking a page upload its content?
No. The document is parsed locally in the browser, so unpublished pages can be checked before they go live.

Broken anchor checker

Run the checks, then decide who has to sign it.

The automated pass is free, needs no account and uploads nothing. Escalate to a named reviewer only where being wrong is expensive.