"Links are not crawlable": anchors without a real href

Google discovers pages by following links, and it follows only one kind: an <a> element with an href attribute that resolves to a URL. A "link" built from a <span> with a click handler, an <a> with no href, or href="javascript:void(0)" works for a person with a mouse and leads nowhere for a crawler.

"Links are not crawlable" is the name of the Lighthouse audit that reports this. Run it from Chrome DevTools or PageSpeed Insights and it lists each failing element.

What it looks like

Before

html
<a onclick="goTo('/pricing/')">Pricing</a>
<a href="javascript:void(0)" data-url="/pricing/">Pricing</a>
<span class="link" onclick="location='/pricing/'">Pricing</span>

After

html
<a href="/pricing/">Pricing</a>

What Google can and cannot follow

Google's documentation on links is specific. These are followed:

html
<a href="https://example.com/pricing/">
<a href="/pricing/">
<a href="pricing.html">

These are not reliably followed, because there is no URL for the crawler to extract:

html
<a routerLink="pricing">
<span href="/pricing/">
<a onclick="goto('/pricing/')">
<a href="javascript:goTo('pricing')">

Googlebot does not click. It parses the rendered HTML, collects href values from <a> elements and queues those URLs. A destination that exists only inside a JavaScript function is invisible to that process. Anchor text matters too: a real link with no text and no image alt gives Google nothing to describe the target.

List the offenders on a page

Paste this into the browser console on the rendered page:

js
[...document.querySelectorAll('a')].filter(a => {
  const h = a.getAttribute('href');
  return !h || h.trim() === '' || h.trim() === '#' || /^javascript:/i.test(h.trim());
}).map(a => a.outerHTML.slice(0, 120));

It returns anchors with a missing, empty, bare # or javascript: href. Not every result needs fixing; the next section sorts them. To find fake links that are not anchors at all, search the DOM for elements with onclick or role="link" and check whether a real <a> exists nearby for the same destination.

Which results matter

Fix these: navigation to another page.

  • Menu items, cards and "read more" controls that change the page through script.
  • Pagination and "load more" controls with no underlying URL. If page two cannot be reached by an href, everything on it relies on other links to be found.
  • Category filters and tabs that load distinct, indexable content without changing to a linkable URL.
  • Clickable cards where the <div> has a click handler and there is no anchor inside.

Leave these: controls that are not navigation.

  • Buttons that open a modal, toggle an accordion or a mobile menu, or copy text. These should be <button> elements in the first place, which also stops Lighthouse reporting them.
  • A parent menu item that only opens a dropdown and has no page of its own.

Where they come from in WordPress

  • Placeholder menu items. A Custom Link menu item saved with # as its URL to act as a dropdown parent. Harmless for crawling if the child items are real links, but give the parent a real page when one exists.
  • Page-builder "link" settings on containers. Making a whole column or card clickable is often implemented with a script and a data- attribute. Add a normal link on the heading or button inside the card as well.
  • Sliders, carousels and tabs whose slides link through script.
  • "Load more" and infinite scroll on blog and shop archives. Keep standard pagination links in the markup, even if they are visually replaced by the button, so crawlers have a path to deeper pages.
  • Themes that open links with JavaScript to add transitions or to open pop-ups.
  • Obfuscated links, where the URL is deliberately hidden from crawlers in a data attribute. If you do not want a link followed, a real link with rel="nofollow" is the transparent way to say so.

The fix

Give every navigational element a real anchor with the destination in href. Scripts can still enhance it: attach the handler to the anchor and call preventDefault() when the script takes over. If the script fails, or a crawler arrives, the plain link still works.

html
<a href="/blog/page/2/" class="load-more">Load more posts</a>

For single-page-style interfaces, use the History API so each view has its own URL, and render links to those URLs as ordinary anchors. Avoid fragment-only routing such as /#/pricing; Google generally ignores the fragment, so all such views collapse into one URL.

Why it matters for the pages being linked to

A page that can only be reached through uncrawlable controls has no internal links as far as Google is concerned. It may still be found through the XML sitemap, but pages with no links pointing at them tend to be crawled rarely and rank poorly. In Hydrogen SEO, the internal link counter counts links stored in post content and its Orphaned filter lists posts with none, a useful cross-check for content that your templates fail to link properly.

Re-run the console snippet and the Lighthouse SEO audit; navigational elements should no longer be listed. Then confirm from Google's side: in URL Inspection, run a live test, open View tested page and search the rendered HTML for the destination URL inside an href. The free SEO audit counts the internal links it can read in a page's HTML, so a page whose count is far below what you can see on screen is a sign that much of the navigation is script-driven.

Common questions

Does Googlebot click buttons or run onclick handlers?

No. It renders the page and reads the resulting HTML, but it does not interact with it. Destinations must be present as href values.

Is href="#" a problem?

For a control that toggles something on the page, it is harmless but better written as a button. For something meant to lead to another page, it means the destination is missing.

Can Google follow links added by JavaScript?

Yes, if the script inserts real anchor elements with href attributes into the rendered HTML. The requirement is about the final markup, not how it got there.

Does this affect accessibility too?

Yes. Elements that are not real links or buttons are often unreachable by keyboard and unannounced by screen readers, so the fix helps both.