Managed IT · October 6, 2026 · intSignal Team

Your Internal Links Point at Redirects. Here's How to Find All of Them.

Share this article

While adding cross-links between two product pages we noticed something embarrassing: a link we had just written pointed at a URL that immediately redirected somewhere else. The destination was correct — eventually. It just took an extra hop that nobody needed.

We went looking for others. There were 84.

Most were historical: a page had moved, a redirect was added so nothing broke, and the links pointing at the old address were never updated. That is exactly what redirects are for, and it is the right first response to a URL change. The mistake is treating the redirect as the end of the job rather than the first half of it.

Why it is worth fixing

For visitors, an extra round trip before anything renders. On a fast connection it is imperceptible. On a phone with high latency, each redirect is a full request-response cycle before the browser can even begin fetching the real page.

For search engines, a redirect is a signal of indirection. Crawlers follow them, but a site whose internal navigation consistently points at non-canonical URLs is describing its own structure inaccurately. Your internal links are one of the strongest statements you make about which URL is the real one — and every one aimed at a redirect contradicts that statement.

For maintenance, redirect chains accumulate. A page moves twice and you have a two-hop chain. Move it again and you have three. Eventually someone prunes an "unused" redirect in the middle and breaks links nobody realised still existed.

For audits, the noise hides the signal. When a crawl reports hundreds of redirected internal links, nobody reads the list — so the day one of those redirects is actually broken, it is invisible in the pile.

Finding every one

Crawl your own built output rather than reasoning about the source. What matters is the HTML that ships, including links inside legacy templates, generated navigation and content files.

# 1. Every internal href in the built site.
grep -rhoE 'href="/[^"]*"' out/ \
  | sed 's/href="//; s/"$//' \
  | sed 's/[?#].*//' \
  | sort -u > /tmp/links.txt

# 2. Ask the live site what each one does.
while read -r url; do
  code=$(curl -s -o /dev/null -w '%{http_code}' -I "https://example.com$url")
  if [ "$code" != "200" ]; then
    final=$(curl -s -o /dev/null -w '%{url_effective}' -L -I "https://example.com$url")
    echo "$code  $url  ->  $final"
  fi
done < /tmp/links.txt | tee /tmp/redirects.txt

You now have every internal link that is not a direct 200, with where it actually ends up. Sort by destination and you will usually find a handful of moved pages explaining most of the list — ours collapsed into three: an old cloud-security path accounting for 52 links, an SD-WAN path with 20, and a renamed SIEM page with 12.

Then rewrite them to the final destination:

grep -rl '/cloud/cloud-security' app content | while read -r f; do
  sed -i 's|/cloud/cloud-security|/security/cloud-security|g' "$f"
done

Rebuild, re-run the crawl, confirm the count drops to zero. Keep the redirects — external links and bookmarks still rely on them. The goal is that nothing you control depends on them.

The worst case is circular. We had added a card on /products/siem linking to /security/security-information-and-event-management-siem, which 301s straight back to /products/siem. A visitor clicking it goes nowhere, slowly.

These are easy to create when two sections cover related ground and one was consolidated into the other. They are also easy to catch — flag any link whose final destination equals the page it appears on:

# For each page, flag links whose resolved destination is the page itself.

Add that check to the same crawl and it costs nothing.

Stop them coming back

A one-off cleanup decays. Three habits keep it fixed:

  1. When you add a redirect, grep for the old URL in the same commit and update every internal reference. The redirect is for the outside world; your own links should already point at the new address.
  2. Run the crawl in CI against the built output, and fail the build on any internal link that is not a 200. Start it as a warning with the current count as a baseline so you are not blocked by the existing backlog, then ratchet down.
  3. Centralise URLs that appear in many places. Ours were scattered through navigation data, category definitions and legacy HTML. Section paths defined in one module are updated once; paths typed by hand in forty files are updated never.

What we actually found

Worth stating plainly, because the distribution is typical:

  • 52 links to a cloud-security path that had moved sections
  • 20 to a legacy SD-WAN URL
  • 12 to a pre-consolidation SIEM page
  • the rest scattered, including the circular one we had just introduced

Three decisions made years apart, each individually sensible, each leaving behind links that nobody had a reason to revisit. That is how this always looks. It is not negligence — it is the ordinary residue of a site that has changed shape, and it needs a periodic sweep rather than a culprit.

Share this article