Official statement
Other statements from this video 14 ▾
- 6:23 Is it true that Google rewrites your title tags without warning?
- 14:00 How can you protect your UGC site from malware without harming your SEO?
- 18:58 Do noindex pages in the XML sitemap really penalize the entire site?
- 19:58 Are mobile and desktop search results really the same in Google?
- 23:05 Is temporarily blocking Googlebot in robots.txt really a reversible mistake?
- 25:15 Are small sites really treated the same as web giants by Google?
- 31:30 What could be holding your site back even after a manual penalty has been lifted?
- 38:29 Should you really noindex low-quality pages to boost your SEO?
- 40:04 Does a poor implementation of rel=prev/next really affect your ranking?
- 40:31 Should you really disavow spam links at the domain level instead of page by page?
- 43:05 Why doesn't Google index all the URLs in your Sitemap at once?
- 49:09 Can a slow server really kill your Google ranking?
- 50:54 Do the prices displayed on your product listings really affect your organic ranking?
- 55:02 Does Google News really operate without any human editorial intervention?
Google recommends using pushState alongside static HTML links for backup. The goal is to ensure that your content remains crawlable even if JavaScript fails or is not executed. In practice, every change of URL via pushState should rely on a functional <a href> link, not just on a JS event listener. This is a double-layer security that prevents losing pages in indexing.
What you need to understand
Why does Google emphasize this dual approach?
Google has been urging developers for years to stop relying solely on JavaScript. The reasoning lies in the very architecture of crawling and indexing: Googlebot operates in two stages. First, it fetches the raw HTML. Then, it queues the pages for JS rendering, which can take hours or even days.
If your links only exist in JavaScript (via onClick event listeners without a real href), Googlebot will not discover them during the first pass. You waste time, crawl budget, and risk certain pages never being discovered if the rendering queue is saturated.
What is pushState and why do we use it?
The History.pushState() API allows modifying the visible URL in the browser without reloading the entire page. It is the foundation of Single Page Applications (SPA) and sites that want to provide smooth and fast navigation.
The problem? pushState creates no visible HTML signal in the source. If you change the URL from /page-a to /page-b with pushState, but there is no static link pointing to /page-b, Googlebot will never find that page unless it executes your JS—and even then, there's no guarantee it will click on your virtual button.
How do static links change the game?
A static link is a good old <a href="/page-b"> in the HTML. No JS dependency, no conditions, just a crawlable link immediately.
When you combine pushState and static links, you maintain the modern user experience (fast navigation without reloading) while offering Googlebot a direct fallback path. If JS crashes, if rendering is delayed, if an older bot crawls, the link remains discoverable.
- pushState handles smooth user experience and URL consistency on the client side
- Static links ensure discoverability for crawlers and accessibility without JS
- This dual approach covers all scenarios: active JS, disabled JS, delayed rendering, third-party bots
- Crawl budget is better utilized since Googlebot discovers everything on the first HTML pass
- Pages become indexable faster, without waiting for the JavaScript rendering queue
SEO Expert opinion
Is this recommendation really followed in practice?
Let’s be honest: the majority of modern JS frameworks (React Router, Vue Router, Next.js in SPA mode) generate static links by default. If you use a <Link> or <router-link> component, it typically produces a valid HTML href, even if it intercepts the click in JS to perform pushState.
The problem arises on custom sites or older projects where developers manually coded the navigation with clickable divs, buttons without href, or data attributes parsed in JS. These implementations completely slip under the radar of Googlebot during the first pass.
When does this rule not suffice?
Having static links is good. But if your content itself is only loaded in JS after the click, you've only addressed part of the problem. Google will discover the URL, yes, but it will still need to execute the JavaScript to see the actual content.
In this case, you remain dependent on the rendering queue. True strength is having critical content in the initial HTML (SSR or SSG), and progressively enriching it with JS. [To be verified] on very large sites: even with perfect static links, if you have 500,000 pages and all content requires JS rendering, you will saturate your crawl budget and slow down indexing.
Does Google really need this crutch in 2025?
Googlebot has been executing JavaScript for years, and its rendering engine has improved. So why continue to insist on static links? Because JS rendering costs Google money in server resources.
Every page that requires rendering consumes CPU, memory, and adds latency. Google will always prefer a site that does the work for it with raw HTML. Additionally, not all bots are Googlebot: Bing, Yandex, social media scrapers, accessibility tools… many do not execute JavaScript or do so poorly. Static links are a universal insurance policy.
Practical impact and recommendations
How can you check that your site complies with this rule?
First step: inspect the raw HTML source. Right-click > View page source (not the inspector, which shows the DOM after JS). Look for your navigation links. If they appear with a valid href pointing to the correct URLs, that's a good sign.
Second step: completely disable JavaScript in your browser (Chrome DevTools > Settings > Preferences > Debugger > Disable JavaScript). Navigate your site. If the links work and lead to the correct pages (even if the design is broken or the content is incomplete), it means the link structure is solid.
What critical errors should you avoid?
Never rely on navigation solely through onClick event listeners without href. A button or div with onclick="navigateTo('/page')" without an associated <a href> is invisible to Googlebot during the first pass.
Another classic pitfall: links with href="#" or href="javascript:void(0)". Technically, it is a link, but it points nowhere. Googlebot will never follow that. If you use pushState, the href must contain the real destination URL, even if you intercept the click in JS to load content dynamically.
What architecture should you adopt to combine the two?
The ideal is server-side rendering (SSR) or static site generation (SSG). Next.js, Nuxt, SvelteKit, and their peers do this natively: they generate full HTML with all links, then hydrate with JS to activate pushState and client-side navigation.
If you are stuck with pure SPA, at a minimum, set up a prerendering system for key pages, or use dynamic rendering (serving static HTML to bots, JS to users). And ensure that every route accessible via pushState has an <a href> link somewhere in your initial HTML or in a menu.
- Check that all your navigation links use <a href> tags with complete URLs
- Test navigation with JavaScript disabled: the links should lead to the correct pages
- Inspect the raw HTML source (not the DOM after JS) to confirm the presence of href
- Avoid href="#" or href="javascript:void(0)": they break discoverability
- If you use a JS framework, ensure that Link components generate valid HTML hrefs
- For SPAs, set up SSR, SSG, or at least prerendering for strategic pages
❓ Frequently Asked Questions
Peut-on utiliser pushState sans liens statiques si on fait du SSR ?
Les frameworks comme Next.js gèrent-ils automatiquement cette double approche ?
Que se passe-t-il si mes liens statiques pointent vers des pages vides sans JS ?
Est-ce que cette règle s'applique aussi aux filtres et facettes e-commerce ?
Comment tester rapidement si mes liens sont crawlables sans JS ?
🎥 From the same video 14
Other SEO insights extracted from this same Google Search Central video · duration 58 min · published on 25/04/2014
🎥 Watch the full video on YouTube →
💬 Comments (0)
Be the first to comment.