Lighthouse audit crawlable-anchors · SEO

Links are not crawlable: how to fix it

View raw .md for LLMs / your notes
Quick answer: This audit fails when a link has no crawlable destination: a click handler on a div or span, an empty href="#", or href="javascript:void(0)". Googlebot follows only real href values, so those destinations may never be discovered. Fix it by giving every navigational link an actual URL in its href attribute.

This Lighthouse audit fails when the page contains links a crawler cannot follow. The link works when a person clicks it, because a JavaScript handler is doing the navigating. Googlebot never runs that handler the way a mouse does; it reads the href attribute, and if the href is missing, empty, #, or javascript:void(0), there is nothing to follow. Every page reachable only through such a link is, from a crawler's point of view, not reachable at all.

TL;DR

What does the crawlable-anchors audit check?

Lighthouse collects every <a> element in the rendered DOM and resolves its href against the page URL. An anchor fails when the resolved destination is not something a crawler can request. In practice that means:

PatternResult
<a href="/pricing">Passes
<a href="https://example.com/x">Passes
<a> with no href at allFails
<a href="">Fails
<a href="#">Fails
<a href="javascript:void(0)">Fails
<a href="javascript:goTo('/x')">Fails
<div onclick="location.href='/x'">Fails
<a href="mailto:...">, <a href="tel:...">Not flagged

Two details matter. First, the audit runs against the rendered DOM, so a link that JavaScript fills in later is judged on its final state, not its source. Second, this is a separate question from whether the destination is any good: a link pointing at a 404 passes crawlable-anchors because the href resolves. The audit asks only whether a crawler has somewhere to go.

It sits in the SEO category with a weight of 1, so the direct score movement is small. That understates it considerably, for the reason in the next section.

Crawl discovery is the thing at stake, not a point on a dashboard.

Google finds pages primarily by following links. Google's own guidance on crawlable links is explicit: a link is followable when it is an <a> element with an href attribute, and Google may not follow anything else. A collection page reachable only through a div with a click handler can sit unindexed indefinitely while the rest of the site ranks normally, and nothing in the Lighthouse report will say so, because the report grades the page in front of it, not the pages it failed to expose.

The knock-on effects:

Decide what the element actually is. If it navigates, it is a link and needs an href. If it changes state on the current page, it is a button.

<!-- FAILS: fake link built from a div -->
<div class="nav-item" onclick="location.href='/collections/sale'">Sale</div>

<!-- FIXED -->
<a class="nav-item" href="/collections/sale">Sale</a>

<!-- FAILS: the classic placeholder href -->
<a href="#" onclick="openProduct(42)">View product</a>

<!-- FIXED: real destination, handler still free to intercept -->
<a href="/products/42" onclick="openProduct(42); return false;">View product</a>

<!-- FAILS: void(0) is the single most common offender -->
<a href="javascript:void(0)" data-target="/blog/page/2">Next page</a>

<!-- FIXED -->
<a href="/blog/page/2" data-target="/blog/page/2">Next page</a>

<!-- NOT A LINK AT ALL: accordion toggle, no navigation -->
<a href="#" class="accordion">Shipping details</a>

<!-- FIXED: it is a control, so use a button -->
<button type="button" class="accordion" aria-expanded="false">Shipping details</button>

The rule that resolves almost every case: the href is the destination, the click handler is an enhancement. Keep both. A real href means the link still works when JavaScript fails, when it is opened in a new tab with a middle click, and when a crawler reads it.

Three patterns worth calling out:

  1. Progressive enhancement, not replacement. <a href="/x" onclick="spaNavigate('/x'); return false;"> gives the crawler a URL and the user a client-side transition. Both audiences served by one element.
  2. href="#" only for a genuine same-page jump, and then only with a fragment that exists: href="#reviews" pointing at id="reviews" is fine and resolves. A bare # is not.
  3. Do not paper over it with role="link". ARIA changes what assistive tech announces; it does not create an href, and it does not make the destination crawlable.

How do I fix this in React, WordPress, or Shopify?

React / Next.js

Every mainstream router already renders a real anchor. The failures come from hand-rolled navigation:

// FAILS: no href in the output HTML
<div className="card" onClick={() => router.push(`/products/${id}`)}>

// FIXED: Next.js Link renders <a href="/products/42"> and still routes client-side
<Link href={`/products/${id}`} className="card">

The same holds for react-router's <Link to="...">, Nuxt's <NuxtLink> and Vue Router's <RouterLink>: all three emit a crawlable href. If you are calling router.push, navigate() or window.location from an onClick, that is the bug. Programmatic navigation is correct after a form submit or a save; it is wrong as the only way to reach a page.

Watch for wrapper components too. A <Card onClick={...}> that renders a div is uncrawlable no matter how many times it appears; fixing the component fixes every instance.

WordPress

Themes and page builders are the usual source:

Shopify

In a scan of 269 production storefronts, crawlable-anchors failed on 30.1% of them (81 of 269). The recurring offenders:

What crawlable-anchors pitfalls should I avoid?

How do I verify the fix?

  1. Re-run Lighthouse or PageSpeed Insights. The crawlable-anchors audit should move into "Passed audits".
  2. Check the rendered DOM, not the source: in DevTools, run $$('a') in the console and inspect the href values. $$('a:not([href]), a[href=""], a[href="#"], a[href^="javascript:"]') returns exactly the failing set.
  3. Use Google Search Console's URL Inspection on a page that should be reachable through the fixed link, and confirm it reports a referring page rather than "Discovered, currently not indexed".
  4. Crawl the site with Screaming Frog or Sitebulb in JavaScript-rendering mode and compare the URLs found by crawling against the URLs in your sitemap. A large gap points at links the crawler could not follow.

Audit your URL at https://lighthouse-md.com.

Audit your page now

Paste your URL, get scores plus a CLAUDE.md plan for Claude Code.

Run audit →
Add us as a preferred source on Google

One click, and Google shows more of lighthouse-md.com in your search results.