Is your site built with React, Vue, Angular or another JavaScript framework? Are pages slow to get indexed, or showing up in Google with missing text? The cause is usually how the content reaches the crawler. JavaScript SEO is the work of making sure search engines can crawl, render and index content that JavaScript creates.
The short answer: Google can run JavaScript, but it does it in a separate step. Many common coding patterns still hide content or links from it. Other crawlers, including many AI crawlers, may not run your JavaScript at all.
In this guide, I explain how Google handles JavaScript, which rendering approach I’d pick, the mistakes that do the most damage, and how to test your own pages.
How Google Processes JavaScript Pages
Google’s own help pages describe three phases for JavaScript pages: crawling, rendering and indexing. Pages can wait in one queue for crawling and a separate queue for rendering. From the outside, you can’t always tell which one a page is waiting in.
1. Crawling
Googlebot asks for a URL, but first it checks robots.txt. If the URL is blocked, Googlebot doesn’t fetch it at all.
When the response comes back, Googlebot reads the raw HTML. It pulls out any links in href attributes and adds them to the crawl queue. If your server sends a fully built page, most of the work is already done here.
Some sites use an “app shell” model. Here, the first HTML response is mostly empty. It holds a <div id="root"> and a script tag, so Google has very little to work with until the JavaScript runs.
2. The Render Queue
Pages that return a 200 status code go into the render queue, whether or not they use JavaScript. The exception is when a robots meta tag or header tells Google not to index the page. Pages with other responses, such as 404 error pages, may skip rendering completely.
Google says a page may wait in this queue for a few seconds. But it can take longer, depending on Google’s resources. When resources are free, a headless version of Chromium (a browser with no screen) loads the page and runs the JavaScript.
Googlebot then reads the rendered HTML for links a second time. It passes that rendered version on for indexing.
3. Indexing
Google indexes what it finds in the rendered HTML. The rule is simple and strict. If content isn’t in the rendered HTML, Google can’t index it.
For a wider view of how this fits into crawling, ranking and serving, see our guide to how Google Search works in 2026.
What “Evergreen Googlebot” Means
Googlebot’s renderer uses an evergreen version of Chromium. That means it is kept close to the current stable version of Chrome, not frozen at an old release. Modern JavaScript and most browser features work.
That doesn’t mean everything works. Google notes that some features aren’t supported. So check support and use polyfills (small scripts that add a missing feature) or differential serving where a feature is missing. Some features can’t be polyfilled at all.
Googlebot also acts differently from a person in a browser. It doesn’t click, scroll, type or accept permission pop-ups. It also doesn’t keep things like cookies or local storage from one page load to the next, the way a returning visitor’s browser does.
Rendering Options: CSR, SSR, SSG and Hydration
Your rendering approach decides how much of your page exists before any JavaScript runs. More than anything else, that decides how risky your setup is for SEO.
| Approach | What the crawler receives first | SEO risk | Good fit for |
|---|---|---|---|
| Client-side rendering (CSR) | A near-empty shell; content appears after JavaScript runs in the browser | Highest: depends fully on rendering | Logged-in dashboards and apps that don’t need to rank |
| Server-side rendering (SSR) | Full HTML built on the server for each request | Low | Pages with content that changes often or is personalised |
| Static site generation (SSG) | Full HTML built ahead of time at deploy | Lowest | Blogs, docs, marketing pages, most product pages |
| SSR or SSG with hydration | Full HTML, then JavaScript attaches to make it interactive | Low, if the server HTML matches what users see | Most modern framework sites (Next.js, Nuxt, SvelteKit and similar) |
Hydration is the step where JavaScript loads in the browser and “wakes up” server-built HTML. It attaches event handlers so buttons and forms work. The content is already there for crawlers. The interactive parts come afterwards.
Google still recommends server-side rendering or pre-rendering, even though Googlebot can render JavaScript. Pre-built HTML is faster for users. It also works for crawlers and bots that don’t run JavaScript. For any page that needs to rank, SSR or SSG is what I recommend.
Where Dynamic Rendering Stands
Dynamic rendering means spotting crawlers and serving them a pre-built HTML version, while users get the client-side app. Many sites set it up years ago through prerendering services.
Google now calls dynamic rendering a workaround, not a long-term solution, and it does not recommend it. Its guide to dynamic rendering points to server-side rendering, static rendering or hydration instead, because dynamic rendering adds complexity and server load.
Google generally doesn’t treat it as cloaking (showing Google different content from users), as long as crawlers and users get similar content. If you already run it and it works, there’s no emergency. I’d plan a move to SSR or SSG at your next rebuild.
Common JavaScript SEO Mistakes That Hide Content
Most JavaScript SEO problems aren’t caused by Google failing to run code. In my experience, they come from patterns that need a real person to be there: clicks, scrolls, or a browser that remembers things.
Links Without an href
Google only finds links that are <a> elements with an href attribute. Click handlers on buttons, spans or divs look like links to users. But a crawler can’t see them.
<!-- Google can't follow these -->
<span onclick="goTo('/pricing')">Pricing</span>
<a onclick="router.push('/pricing')">Pricing</a>
<!-- Google can follow this -->
<a href="/pricing">Pricing</a>
Most framework link components, such as Next.js <Link> or Vue Router’s <router-link>, output a real <a href>. Problems usually come from custom menu components, so check what they output.
Also avoid hash-based routing like /#/pricing. Google can’t reliably treat URL fragments as separate pages. Use the History API so each view has a normal path. Clean, crawlable links also make your internal linking strategy actually count.
Content Behind Clicks and Tabs
Googlebot doesn’t interact with your page. Say a product description, review section or FAQ answer only loads from an API when someone clicks a tab or “Read more” button. Then Google never sees it.
The fix is to put that content in the HTML from the start. Then simply hide or show it with CSS or JavaScript. Content that is on the page but folded away can still be indexed. Content that hasn’t been loaded can’t.
Lazy-Loading Done Wrong
Lazy-loading is fine when content loads as it comes into view. Google’s advice is to use native lazy-loading for images and iframes, the IntersectionObserver API, or a library that loads content when it becomes visible.
<!-- Safe: native lazy-loading, real src in the HTML -->
<img src="/images/sofa-grey.jpg" loading="lazy"
width="800" height="600" alt="Grey three-seater sofa">
<!-- Risky: image URL only appears after a scroll event -->
<img data-src="/images/sofa-grey.jpg" class="js-load-on-scroll">
Don’t lazy-load content at the top of the page. It slows down what users see first and can hurt your Largest Contentful Paint. We cover that in Core Web Vitals in 2026.
For infinite scroll, give each chunk its own lasting URL, such as ?page=3. Link the pages in order, and update the address bar with the History API as the user scrolls. Otherwise, nothing after the first batch can be found.
Blocked JavaScript and CSS
Google won’t render JavaScript from blocked files. If robots.txt blocks your /static/, /assets/ or /_next/ folder, Google may see an empty shell. The page can still work perfectly in a browser.
# Don't do this on a JavaScript site
User-agent: *
Disallow: /assets/
Disallow: /api/
Blocking an API endpoint that your pages call to fetch content has the same effect. Check robots.txt for any path your front end depends on. This is one of the first files I open on a JavaScript site. Our guide to robots.txt and AI crawlers covers safe rule-writing in more detail.
Caching can catch you out too. Google caches files heavily and may ignore your cache headers, so it can render with an old script. Fingerprinted file names like main.2bb85551.js force a fresh fetch when the file changes.
Soft 404s in Single-Page Apps
In a single-page app with browser-side routing, the server often returns 200 OK for every URL. It lets JavaScript decide what to show. A deleted product then shows “Not found” text with a 200 status. Google treats that as a soft 404 (an error page that doesn’t say it’s an error), or worse, indexes it as a thin page.
Google suggests two fixes when the server can’t return a real 404:
// Option 1: redirect to a URL the server answers with a 404
if (!product) {
window.location.href = '/not-found';
}
// Option 2: add noindex to the error view
if (!product) {
const meta = document.createElement('meta');
meta.name = 'robots';
meta.content = 'noindex';
document.head.appendChild(meta);
}
If you can return proper status codes from the server, through SSR or edge routing, that’s still the cleaner option.
Changing Canonicals, Titles and Robots Tags With JavaScript
JavaScript can set or change the <title> and meta description, and Google will pick those up after rendering. Canonicals and robots tags need more care.
- Canonical tags: a canonical tag tells Google which URL is the main version of a page. Set it in the server HTML where you can. If JavaScript sets one, it must not point to a different URL than the canonical in the original HTML. Make sure the page ends up with only one
rel="canonical". - Noindex: if the original HTML contains
noindex, Google may skip rendering. So JavaScript that removes it may never run. Never shipnoindexin the first HTML of a page you want indexed. - Structured data: JSON-LD added by JavaScript can work, but test it. Check it in the rendered HTML, not just in your source code.
Here is a common pattern that goes wrong. A template ships noindex by default, and a script removes it once data loads. In a browser it looks fine. To Google, the page says “don’t index me” before any code runs. I see this more often than you’d think.
AI Crawlers and JavaScript
Google’s rendering is well documented. Most AI companies say far less about how their crawlers handle JavaScript. You shouldn’t assume they act like Googlebot.
The safe working assumption is that crawlers used for AI training and AI answers may read only your raw HTML. If your key content only appears after scripts run, those crawlers may see a blank or partial page. An AI assistant can’t quote what it can’t read.
Two points to keep clear:
- Google’s AI features, such as AI Overviews, draw on Google’s own index, which is built from rendered pages.
- Other AI crawlers each behave differently, and their behaviour can change. Check your own server logs to see which bots ask for your pages and whether they fetch your JavaScript files at all.
This is one more reason I treat server-built or static HTML as the safest default. It works for every crawler, whether or not it runs JavaScript.
How to Test JavaScript SEO on Your Own Pages
You don’t need to read code to find most problems. You need to compare what the server sends with what Google renders. Then check that your important content appears in the rendered version.
View Source vs the Rendered DOM
These two views show very different things on a JavaScript site:
- View source (right-click, “View page source”, or put
view-source:before the URL in Chrome) shows the raw HTML the server sent. This is roughly what crawlers that don’t run JavaScript see. - Inspect / Elements panel in DevTools shows the live page after JavaScript has run. This is closer to what Google renders.
Search view source for a sentence from your main content. If it’s missing there but shows in the Elements panel, that content depends on JavaScript rendering. This is the quickest test I know.
URL Inspection in Search Console
Run the URL Inspection tool on a page, then choose “Test live URL” and “View tested page”. You get the rendered HTML and a screenshot. You also get a “More info” tab that lists page files that failed to load and JavaScript console messages.
“View crawled page” shows what Google stored from its last crawl. That helps when a page is indexed but showing the wrong content. It also replaces the old cache link check, which I explain in my post on Google removing the cache link.
Rich Results Test
The Rich Results Test works on any public URL, including sites you don’t own in Search Console. As well as checking structured data, it shows the rendered HTML Googlebot produced. That makes it handy for quick checks on competitors, or on test sites you can make public.
A Quick Search Check
Search Google for a unique sentence from a JavaScript-loaded section of an indexed page, in quotes. If the page doesn’t come up, the content may not be indexed. It isn’t proof, but it’s a fast way to spot a problem worth checking with URL Inspection.
A Practical JavaScript SEO Audit Process
Work through these checks in order on any site that relies heavily on JavaScript. They start with the cheapest checks and move to the ones that need developer time. This is the order I use myself.
- Pick sample templates. Choose one URL each for your home page, category pages, product or service pages, articles and any filtered or paginated views.
- Compare raw and rendered HTML. Check view source against the Elements panel for each template. Note which content, links and tags only exist after JavaScript runs.
- Run URL Inspection live tests. Confirm the rendered HTML has the main content, internal links and the correct title, canonical and robots tags. Review blocked files and console errors under “More info”.
- Audit robots.txt. Make sure no script, style or API path the front end needs is blocked.
- Check links and routing. Confirm navigation uses
<a href>with real paths, not click handlers or#fragments. - Test error handling. Visit a made-up URL such as
/products/does-not-exist. Check whether the server returns a 404, or the page getsnoindex. - Prioritise fixes. Move the templates that matter most for SEO to SSR or SSG first. Start with the pages that bring the most organic traffic or revenue.
Google’s own JavaScript SEO basics guide is worth sharing with your developers. It sets out these rules in Google’s own words.
Conclusion
JavaScript SEO comes down to one question: is your important content in the HTML that crawlers actually process? Google can render JavaScript, but it does it later, and without clicking, scrolling or remembering anything. Many other crawlers may never run your scripts at all.
Serve real HTML with SSR or SSG and use proper <a href> links. Keep scripts unblocked, and handle errors and canonicals in the server response. Then check with URL Inspection. As a next step, look at your server logs to see which crawlers reach your pages and what they ask for. Start with our log file analysis guide.



