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.
1. What makes a link crawlable
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.
| Pattern | Crawlable? | Why |
|---|---|---|
<a href="/shoes/">Shoes</a> | Yes | Anchor with a real URL |
<a href="https://example.com/shoes/"> | Yes | Absolute URL |
<a onclick="go('/shoes')"> | No | No href, navigation only happens in script |
<span data-url="/shoes/"> | No | Not a link element |
<button onclick="router.push('/shoes')"> | No | Buttons are actions, not links |
<a href="javascript:void(0)"> | No | The href is not a URL |
<a href="#/shoes"> | No | Fragments are not treated as separate pages |
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
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.
4. Links that only exist after rendering
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.
5. Anchor text, rel attributes, and link equity
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.
7. Auditing JavaScript links in SiteAuditLint
- Crawl without renderingRecord which URLs are discoverable from raw HTML links alone.
- Crawl with renderingRecord links discovered after JavaScript runs.
- Compare discovered URLsURLs found only in the rendered crawl depend on JavaScript for discovery.
- Check the sitemapURLs in the sitemap but missing from both crawls have no crawlable internal links.
- Inspect navigation markupConfirm menus, pagination, and filters use anchor elements with href.
| Crawl result | Interpretation |
|---|---|
| Found in raw and rendered crawl | Reliably discoverable |
| Found only in rendered crawl | Discovered by Google after rendering, invisible to non-rendering bots |
| Found only in the sitemap | Effectively 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
Next, learn how content loaded after the initial response is handled in the Dynamic content lesson.