How to find broken links on a page, and which ones really are broken

Short answer

A link is broken when its page answers with an error such as 404, 410 or 500, or doesn't answer at all. To check one page, open DevTools and run the snippet in method 1: it requests every link on the page's own domain and lists the failures and redirects. Links to other sites have to be opened one by one or checked with a tool outside the browser. A web page isn't allowed to read another site's status codes.

What each answer means

Every time a link is followed, the server answers with a status code. Not every error code means the link is broken, and not every working link is fine as it is.

200 Works. Nothing to do.
301 · 302 · 307 · 308 Redirects. The link works, but every visitor and crawler takes an extra hop. Point it at the final URL.
404 · 410 Broken. The page is gone. 410 says it's gone on purpose.
500 · 502 · 503 Broken, maybe for now. A server error. Check again later before you change anything.
401 · 403 · 405 · 429 · 999 Probably not broken, on other sites. There it usually means "no bots allowed", not "no page" (999 is LinkedIn's way of saying it). Open it in a browser to be sure. On your own site, treat it as broken.
No response Unknown. The domain doesn't exist, the server timed out, or the redirects loop. Try it in a browser.

Redirects deserve a closer look, because they hide inside links that seem to work:

Your page links to/sale
301
which redirects to/summer-sale
301
which ends at/offers404

The link looks fine in the template, but whoever clicks it lands on a missing page after two hops. Each old URL change left a redirect behind, and the last page was later deleted.

Why broken links matter

  • Visitors hit a dead end. A missing page in the middle of a journey (a product, a sign-up, a help article) is where people leave.
  • Search engines drop the target. Google doesn't index URLs that answer with a 4xx code (except 429, which it treats like a server error), drops them if they were indexed, and crawls them less and less often.
  • Links stop doing their job. An internal link is how search engines find and weigh your pages. A link to a dead URL helps nobody.
  • Redirects add up. Googlebot generally follows up to 10 redirect hops, but every hop is another round trip for visitors, and a chain breaks completely if any step goes missing.

One broken link won't sink a site. Broken links in a template (the header, the footer, a product card) repeat on every page that uses it, and that's where they cost the most.

How to check by hand

1Check every link on this domain from the Console 1 minute

Open the page, open DevTools (F12), go to the Console and paste:

// Checks every link on this page's own address; lists the ones that fail or redirect.
// Skips logout and sign-out links, which a request would follow like a click.
const links = [...new Set([...document.links]
  .map(a => a.href.split('#')[0])
  .filter(href => href.startsWith(location.origin + '/') && !/log-?out|sign-?out/i.test(href)))].slice(0, 200);
const found = [];
for (let i = 0; i < links.length; i += 4) {
  await Promise.all(links.slice(i, i + 4).map(async url => {
    try {
      const res = await fetch(url, { cache: 'no-store', signal: AbortSignal.timeout(10000) });
      res.body?.cancel();
      if (!res.ok) found.push({ url, result: res.status });
      else if (res.redirected) found.push({ url, result: 'redirects to ' + res.url });
    } catch (e) {
      found.push({ url, result: e.name === 'TimeoutError' ? 'timed out' : 'no response, or redirects to another site' });
    }
  }));
}
console.table(found.length ? found : [{ result: 'All ' + links.length + ' links answered OK' }]);

You get a table of every link on the page's own address that failed or redirected, with its status code or final URL. It sends four requests at a time and stops at 200 links, so it's gentle on the server.

It follows every link the way a visitor would, so run it while signed out, or on a page without links that change things (it skips logout links, but not "empty basket" or "delete" links). It only checks links on exactly the same address as the page: www.example.com and example.com count as different. It uses ordinary GET requests, because some servers answer HEAD requests with a 405 even when the page works.

2Find where a broken link sits on the page 30 seconds

A table of URLs isn't much use to whoever has to fix it. This outlines every link to a URL in red and scrolls to the first one, so you can see whether it's in the content, the navigation or the footer:

// Highlights every link to a URL and scrolls to the first one
const target = '/summer-sale';  // the broken URL, or part of it
const hits = [...document.links].filter(a => a.href.includes(target));
hits.forEach(a => a.style.outline = '3px solid red');
hits[0]?.scrollIntoView({ block: 'center' });
console.log(hits.length + ' link(s) found', hits);

3Check links to other sites a few minutes

The Console can't do this for you. Browsers stop a page from reading another site's responses unless that site allows it, and almost none do. You'll get an error or an empty answer for links that work perfectly well.

  1. List them: [...new Set([...document.links].filter(a => a.protocol.startsWith('http') && a.host !== location.host).map(a => a.href))]
  2. Open each one in a new tab, or paste the list into a crawler that checks external links.
  3. If a crawler reports 403 or a "verify you're human" page, but the link opens in your browser, it's working. That's the site blocking bots, not a missing page.

4Ask Google which links it found broken 5 minutes

For the whole site rather than one page, use Google Search Console: Indexing → Pages, then open Not found (404). Inspect a URL there: under Discovery it may name a page that links to it. This lags behind your site by days or weeks, so use it to find patterns, not to confirm a fix.

How to fix them

Internal 404 If the page moved, update the link to the new URL. If it was deleted and other sites link to it, add a 301 redirect to the closest replacement as well. Don't redirect everything to the homepage.
Internal redirect Update the link to point straight at the final URL. Keep the redirect for visitors arriving from elsewhere.
Server error Check again later. If it keeps failing, it's a bug on that page for the developers, not a link to change.
External 404 Find where the page moved and link there, link to an archived copy, or remove the link if it no longer adds anything.
In a template Fix it once in the template (header, footer, product card) and it's fixed on every page that uses it. Note the template in the ticket so it gets fixed at the source.

Fixing redirected links? See 301 vs 302 redirects for which type to use.

How crawlers report this

If a crawl sent you here, these are the labels to look for. Our guide to Screaming Frog and Sitebulb issues covers other common ones.

Screaming FrogSitebulbWhat it means
Response Codes: Internal Client Error (4XX)Broken internal URLsAn internal link leads to a page that doesn't exist.
Response Codes: Internal Redirection (3XX)Internal redirected URLsAn internal link goes through a redirect. Update the link to the final URL.

Doing this on every page?

SEOSnip checks the links for you

Press the link check button in the extension and it requests up to 100 of the page's links, including its canonical and hreflang URLs, and reports what it finds:

  • Links on the page's own domain are checked from the page itself, so they get through the site's bot protection
  • Links to other sites are checked by the extension, which the browser's cross-site rules don't block
  • A 403 or 429 from another site is counted as unverified, not reported as broken
  • A ticket listing the failing URLs (the first 10, with the total), each with its result and link text, and a "Check if fixed" button that re-runs the check