Home›Academy›JavaScript SEO›JavaScript links

JavaScript SEO · Lesson 02 · Discovery

JavaScript links

How crawlers recognize links, why onclick handlers and hash routes block discovery, and how to make JavaScript navigation crawlable.

05 JavaScript SEO10 min Read timeIntermediate Level

02 / Discovery

Search engines discover pages by following links, and they only follow links they can recognize. A JavaScript link that looks and works like a normal link for users can be invisible to crawlers if it is not a real anchor element with an href attribute.

This lesson explains which link patterns are crawlable, how client-side routing and the History API affect discovery, why onclick handlers and fragment URLs cause problems, and how to audit links that only appear after rendering. It builds on the Internal links lesson.

Google has stated that it reliably follows links in an <a> element with an href attribute that contains a resolvable URL. Other patterns may work for users but are not treated as links.

PatternCrawlable?Why
<a href="/shoes/">Shoes</a>YesAnchor with a real URL
<a href="https://example.com/shoes/">YesAbsolute URL
<a onclick="go('/shoes')">NoNo href, navigation only happens in script
<span data-url="/shoes/">NoNot a link element
<button onclick="router.push('/shoes')">NoButtons are actions, not links
<a href="javascript:void(0)">NoThe href is not a URL
<a href="#/shoes">NoFragments are not treated as separate pages
Links are for navigation, buttons are for actions

If an element takes the user to a different URL, it should be an anchor with an href. This also improves accessibility, because screen readers, keyboard navigation, and middle click behaviour all depend on real links.

2. Client-side routing and the History API

Single-page applications change the URL with the History API (pushState) instead of loading a new document. This is compatible with SEO when two conditions are met: each view has a unique, real URL, and that URL returns the correct content when requested directly.

Router links done right

Problem<div onClick={() => navigate('/pricing')}>Users can navigate. Crawlers see no link.
Fix<Link href="/pricing">Pricing</Link>The framework renders a real <a href> and intercepts the click.

Router components in Next.js, Nuxt, React Router, and Vue Router output anchor elements when used correctly.

3. Hash and fragment URLs

Older single-page apps used hash routing, such as /#/products/42. Everything after # is a fragment, which search engines generally ignore when identifying pages. All hash routes collapse into one URL, so only the homepage can be indexed. Moving to path-based routing with real URLs, plus redirects where old links exist, fixes the problem.

Even crawlable anchors can be delayed if they only appear in the rendered DOM. Google must render the page before it discovers them, and crawlers that do not execute JavaScript never find them.

JavaScript navigation menus

The main menu is built from an API response after load.

Infinite scroll

Further items load on scroll, with no paginated URLs to follow.

Load more buttons

Products beyond the first batch have no crawlable path.

Tabs and accordions

Links injected only after a click event.

Related content widgets

Recommendations loaded from a third-party script.

For infinite scroll and load more patterns, provide paginated URLs such as /shoes/?page=2 with real anchor links, so every item is reachable without user interaction.

JavaScript can change link attributes after rendering. Check that anchor text is present in the rendered link, that scripts do not add rel="nofollow" to internal links, and that tracking scripts do not rewrite hrefs to redirect URLs. See the issues for empty anchors and links to redirects.

6. JavaScript links and orphan pages

When links exist only in script, crawlers may find pages through the XML sitemap but not through internal links. These pages look like orphan pages, receive little internal authority, and are crawled less often. Comparing the sitemap against URLs discovered by the crawl is a quick way to detect this.

  1. Crawl without renderingRecord which URLs are discoverable from raw HTML links alone.
  2. Crawl with renderingRecord links discovered after JavaScript runs.
  3. Compare discovered URLsURLs found only in the rendered crawl depend on JavaScript for discovery.
  4. Check the sitemapURLs in the sitemap but missing from both crawls have no crawlable internal links.
  5. Inspect navigation markupConfirm menus, pagination, and filters use anchor elements with href.
Crawl resultInterpretation
Found in raw and rendered crawlReliably discoverable
Found only in rendered crawlDiscovered by Google after rendering, invisible to non-rendering bots
Found only in the sitemapEffectively orphaned, needs crawlable internal links

8. Practical exercise: Inspect your navigation

Open developer tools on your homepage, a category page, and an article. Inspect the main navigation, footer, pagination, and any load more controls.

JavaScript links checklist

0 of 8 tasks completed

Key takeaways

  • Crawlers reliably follow anchor elements with an href containing a real URL.
  • onclick handlers, buttons, and javascript: hrefs are not crawlable links.
  • Client-side routing works when every view has a real URL that loads directly.
  • Hash fragments collapse into one URL for search engines.
  • Infinite scroll needs paginated URLs to keep items discoverable.
  • Compare raw and rendered crawls to find links that depend on JavaScript.

Knowledge check

1. Which link can crawlers reliably follow?
2. Why are hash routes like /#/products a problem?
3. How should infinite scroll lists be made crawlable?
4. A URL appears only in the rendered crawl. What does that mean?

Next, learn how content loaded after the initial response is handled in the Dynamic content lesson.