Google discovers most pages the same way you do: by following links. Its crawler opens a page, collects the links on it, and visits those next. A page that no crawlable link points to is very hard for Google to find, even if visitors reach it every day.
The catch is that "a link" means something specific to a crawler. To a visitor, anything you can click that takes you somewhere is a link. To Google, only an <a> element with a real address in its href is. Everything else is invisible.
What Google can and can't follow
Google's own guidance is short: it follows links that are an <a> element with an href attribute containing a URL it can resolve.
| Written as | Google follows it? |
|---|---|
<a href="/pricing">Pricing</a> | Yes |
<a href="https://example.com/pricing"> | Yes |
<a href="#"> with a click handler | No: # is not a page |
<a href="javascript:goTo('pricing')"> | No |
<span onclick="location.href='/pricing'"> | No: not a link at all |
<button onclick="router.push('/pricing')"> | No |
<a routerLink="/pricing"> with no href in the HTML | No |
The page behaves the same for a visitor in every case. That's why these problems survive for years: nothing looks broken.
Where they usually come from
- Menus and cards built with click handlers. A whole product card made clickable with JavaScript, instead of wrapping it in a link.
- "Load more" and pagination buttons. Page 2 of your blog or catalogue exists, but only a button leads there. Everything past page 1 is undiscoverable.
- Single-page apps where the router renders links without an
href, or builds them only after JavaScript runs. - Filters and tabs that change the page with JavaScript, so the filtered pages have no addresses of their own.
- Placeholder links (
href="#") left in a theme and never filled in.
How to fix each one
The rule is always the same: put the destination in an href. You can keep any JavaScript you like on top.
<!-- Before: a clickable card Google can't follow -->
<div class="card" onclick="location.href='/plans/pro'">Pro plan</div>
<!-- After: the same card, now a real link -->
<a class="card" href="/plans/pro">Pro plan</a>For "load more" buttons, keep the button for visitors but also give each page its own address and a plain link to it, for example /blog?page=2, so a crawler can walk through every page.
In React and Next.js, use the framework's link component (<Link href="/pricing">), which renders a real <a href>. Avoid onClick={() => router.push(...)} on a div or button for anything that navigates.
Make sure the link is in the HTML
Google does run JavaScript, but later and not always completely. Links that only appear after scripts run may be found late or not at all, and other crawlers (including most AI assistants' crawlers) often don't run JavaScript at all. The safest links are in the HTML your server sends.
A quick test: open the page, choose View page source (not Inspect), and search for the address you expect, for example /pricing. If it isn't in the source, it isn't in the HTML.
How to find them on your site
- Search Console's Pages report. Pages listed as "Discovered, currently not indexed" or missing entirely can be a sign that nothing links to them properly.
- Click through your menus with JavaScript turned off. Whatever stops working is invisible to crawlers that don't run scripts.
- Run a crawl. A crawler that only follows real links will miss the same pages Google misses, which is exactly the point.
An AHX audit does the third for you: it crawls your site the way a search engine does, lists the pages with links written in a way Google can't follow, and flags pages in your sitemap that no page on your site links to.
Discussion
Questions, corrections or your own experience: all welcome.