JavaScript Links

No Comments
Javascript links

Element Code: PE-020

TL;DR: Google discovers links only from <a> tags with a real URL in the href attribute. Navigation built with onclick handlers, divs, buttons, or javascript:void(0) is invisible for link discovery, passes no link equity, and quietly orphans the pages behind it. Put a crawlable href on everything that navigates.
Severity
High
Affects
Crawling, discovery, PageRank flow
Common in
SPAs, menus, filters
Detection
Screaming Frog JS crawl
Fix effort
Low to medium (dev work)

What counts as a JavaScript link

A JavaScript link is any clickable element that navigates the user through script instead of through a standard anchor. The usual shapes: a <div> or <span> with an onclick handler that sets window.location, a <button> wired to a router, an anchor with href="#" or href="javascript:void(0)" where the real destination lives only in the click handler, or a menu component that builds its navigation entirely in event listeners.

To a human with a mouse, these work perfectly. That is exactly why they survive code review. To a crawler, they mostly do not exist, and Google is unambiguous about this: its JavaScript SEO documentation states that Google can follow links only if they are <a> tags with an href attribute. Googlebot does not click things. It renders the page, extracts URLs from anchor hrefs, and moves on. A destination that only materializes when an event handler fires is a destination Googlebot never learns about from that page.

Why this hits harder than most technical issues

Most technical SEO problems degrade something. This one deletes something. Three separate losses stack up:

Discovery. If the only paths to your product detail pages run through a JavaScript-driven filter or a scripted menu, those pages are orphans in Google's link graph. They might still get found through the XML sitemap, but sitemap-only URLs crawl slower, and a page with zero internal links pointing at it starts life with a credibility problem.

Link equity. PageRank flows through anchors. A div with a click handler passes nothing. I have watched a retailer rebuild its category navigation as a scripted mega-menu and wondered why deep categories bled positions over the following weeks: the pages were still in the sitemap, still indexed, but the internal links that had been feeding them authority had effectively vanished. The damn pages looked linked to every human who checked.

Anchor text. Even when a page gets discovered another way, the descriptive anchor text that tells Google what the target is about never gets attributed. "Waterproof hiking boots" as anchor text is a relevance signal you are throwing away.

And beyond SEO: script-only navigation usually breaks middle-click to open in a new tab, breaks keyboard navigation and screen readers, and breaks the browser's link preview. The accessibility failure and the crawlability failure are the same failure.

Which patterns are crawlable

How is the link coded? <a href="/boots/"> + optional JS enhancement <a href="#" onclick=...> <a href="javascript:..."> <div onclick=...> <button onClick={route}> CRAWLABLE URL discovered, equity flows, anchor text counts DEAD END Anchor exists but points at nothing; destination invisible NOT A LINK Googlebot does not click; target page gets nothing Rule: the destination URL must exist in the href of an <a> tag in the rendered HTML.

Pattern reference

PatternGooglebot follows it?Verdict
<a href="/page/">YesThe standard. Use it.
<a href="/page/" onclick="...">YesFine. JS enhances, href remains the source of truth.
SPA router link rendering a real hrefYesReact Router Link, Next.js Link, Vue router-link all output proper anchors. Verify in rendered HTML.
<a href="#"> + handlerNo (follows to #, i.e. nowhere)Replace the # with the real destination.
<a href="javascript:void(0)">NoGoogle explicitly warns against javascript: URLs in links.
<div onclick="location.href=...">NoInvisible as a link. Rebuild as an anchor.
<button> that routesNoButtons are for actions (submit, toggle). Navigation gets an anchor.

How to detect it on your site

  1. Compare a raw crawl to a rendered crawl. In Screaming Frog, crawl once with plain HTML extraction, then again with JavaScript rendering enabled (Config, Spider, Rendering). Now compare inlinks per URL. Pages with healthy inlink counts in neither crawl, but which you know your menus "link" to, are your victims. Sitebulb runs the same comparison and visualizes the delta nicely.
  2. Disable JavaScript and click around. Toggle JS off in DevTools (Command palette, "Disable JavaScript") and try to use your main nav, faceted filters, pagination, and footer. Anything that stops being a followable link deserves scrutiny. This five-minute test finds most offenders.
  3. Inspect the rendered HTML in Search Console. URL Inspection, Test Live URL, View Tested Page. Search the rendered source for the hrefs you expect. If your category page's product links are not in that HTML as anchors, Google is not seeing them either.
  4. Grep the codebase. Searching templates for javascript:void, href="#", and onclick near navigation components is crude and effective.
  5. Watch server logs. Sections of the site Googlebot rarely requests, despite being "linked" from high-traffic templates, are a symptom worth chasing back to the markup.

How to fix it

  1. Give every navigation element a real anchor. The destination URL goes in the href. If you want client-side routing on top, attach the handler to the anchor and call preventDefault; crawlers read the href, users get the SPA experience. This is standard progressive enhancement and every major framework supports it out of the box.
  2. Use your framework's link component, not raw handlers. Next.js Link, React Router Link, Nuxt's NuxtLink all render anchors with hrefs. The bugs appear when developers bypass them with onClick navigation on divs and buttons.
  3. Make filter and pagination states real URLs where you want them indexed. A "load more" button can stay a button if a crawlable paginated alternative exists in the markup.
  4. Keep buttons for actions. Add to cart, open modal, toggle menu: buttons. Go to another URL: anchor. This split also fixes your accessibility story for free.
  5. Re-crawl after the fix. Rendered Screaming Frog crawl, confirm the target pages now show inlinks with sensible anchor text, then watch crawl stats pick up over the following weeks.

What good looks like: every URL you care about is reachable through anchor hrefs present in the rendered HTML, no javascript: or bare # hrefs anywhere in navigation, and your raw versus rendered crawls tell approximately the same link story.

DO

  • Put the real destination URL in every anchor's href
  • Layer JS behavior on top of working anchors (preventDefault pattern)
  • Use framework link components that render true anchors
  • Verify links exist in the rendered HTML, not just in the browser
  • Audit nav, filters, and pagination after every front-end redesign
DON'T

  • Navigate with divs, spans, or buttons plus click handlers
  • Ship href="#" or javascript:void(0) in navigation
  • Assume the sitemap compensates for missing internal links
  • Trust that "it works when I click it" means crawlers see it
  • Hide your only path to deep pages inside scripted mega-menus

FAQ

Google renders JavaScript now. Doesn't that make this a non-issue?
No. Rendering means Google executes your JS and reads the resulting DOM. It still only extracts links from anchor hrefs in that rendered DOM. It does not simulate clicks, so a URL that only exists inside an event handler stays invisible regardless of rendering.
My pages are indexed anyway via the sitemap. Why should I care?
Indexed is the floor, not the goal. Sitemap-only pages receive no internal link equity and no anchor text signals, and they tend to get crawled less often. They rank like the orphans they are.
Is onclick on an anchor with a real href okay?
Yes. That is the recommended pattern: the href carries the crawlable destination, the handler upgrades the experience for users. Just make sure the href and the handler agree on where the user ends up.
What about links that appear only after user interaction, like an accordion or a tab?
If the anchors are in the DOM on load and merely hidden with CSS, Google reads them. If the interaction fetches or builds the links after a click, Google never sees them, because it does not click. Render the anchors up front and toggle visibility instead.
Do JavaScript links at least pass PageRank if Google finds the URL some other way?
No. Link equity flows through the anchor itself. If the anchor does not exist as a crawlable link, there is nothing to flow through, however Google discovered the destination.
Suspect your internal linking is weaker than it looks?

An advanced audit compares your raw and rendered link graphs, finds the pages your navigation silently abandoned, and hands your developers an exact fix list.

Get an Advanced SEO Audit

Claude Vincent is a technical SEO consultant focused on crawlability, rendering, and AI-search visibility. He writes the field guides and case studies at SEO ProCheck, with a bias toward the durable, unglamorous work that decides whether search engines and AI answer engines can actually read and cite a site.

About SEO ProCheck

Technical SEO consulting and GEO strategy with 20 years of enterprise experience. Case studies, resources, and tools for search and AI visibility.

Work With Me

Technical SEO audits, GEO strategy, site migrations, and international SEO. Hourly consulting for teams who need hands-on support, not just reports.

Subscribe to our newsletter!

More from our blog