JavaScript SEO: what Googlebot actually sees
How Google crawls, queues and renders JavaScript pages, what commonly breaks, which rendering strategy to choose, and how to test what Googlebot really sees.
Google can render JavaScript, but rendering is a separate step that comes after crawling, and a lot can go wrong between the HTML your server sends and the page a browser shows. Many other crawlers, including many AI crawlers, don’t run JavaScript at all. The sites that do well put their content, links and key signals in the initial HTML, and they test what crawlers actually receive.
How Google processes JavaScript
Googlebot fetches a URL, if robots.txt allows it, and reads the HTML response. Pages that return a 200 status join a queue for rendering, where an evergreen (always current) version of Chromium runs the JavaScript. Pages with other status codes may not be rendered at all. Only then does Google index the rendered content and pick up any links that appeared during rendering.
How Google processes a JavaScript page
- DiscoverLinks and sitemaps lead to the URL
- CrawlFetch the URL and read the HTML response
- QueuePages that return 200 wait for rendering
- RenderEvergreen Chromium runs the JavaScript
- IndexRendered content, links and signals are processed
Two consequences follow. Rendering can lag behind crawling, so content that exists only after JavaScript runs may be picked up later than content in the HTML. And if Google finds noindex in the initial HTML, it may skip rendering altogether, so a script that removes the tag later won’t help.
Once a page is rendered, Google can read the links, text and structured data that JavaScript added. But “can” isn’t the same as “will, reliably and quickly”, and most JavaScript SEO problems live in that gap.
What breaks most often
- Content that appears only after an interaction. Google doesn’t click, type or scroll. Tabs, accordions and “load more” buttons that fetch content on click keep that content from Google. Put it in the HTML and show or hide it with CSS.
- Links without an
href, and hash routing. Google reliably follows<a>elements with anhref. Buttons with click handlers aren’t links, and routes like/#/pricingare fragments, not separate URLs. Use the History API with real paths. noindexor canonicals changed by JavaScript. Google may never render the version in which a script removesnoindex. If JavaScript sets a canonical, it should match the one in the HTML, and the page should only have one.- Soft 404s in single-page apps. Many apps return 200 for routes that don’t exist and show a “not found” view. Have the server return a real 404. If you can’t, redirect to a URL that does, or add
noindexto the error view. - Blocked JavaScript or CSS. If robots.txt disallows the files a page needs, Google renders the page without them.
- Lazy loading triggered by scroll events. Content that waits for a scroll may never load for Google. Use native lazy loading or
IntersectionObserver, which load content as it enters the viewport.
Choosing a rendering strategy
Your rendering strategy decides what’s in the initial HTML, and therefore what every crawler can see without running JavaScript.
Rendering strategies for public pages
| Strategy | What crawlers receive | Verdict |
|---|---|---|
| Static generation (SSG) | Complete HTML, built in advance | Best default |
| Server-side rendering (SSR) | Complete HTML, built for each request | Recommended |
| Hybrid: server HTML plus hydration | Complete HTML, with JavaScript only where it’s needed | Recommended |
| Client-side rendering (CSR) | A near-empty shell until JavaScript runs | Avoid for SEO |
| Dynamic rendering | Pre-rendered HTML for bots, the full app for users | Workaround |
Static generation suits pages that change rarely, such as service pages and articles. Server-side rendering suits pages that change often or depend on the request, such as stock levels. Most modern frameworks support both and let you mix them route by route. Client-side rendering is fine behind a login, but risky for anything you want found. If a full rebuild isn’t possible yet, start by moving the templates that earn search traffic, such as category and service pages, to server-rendered or static HTML. Google calls dynamic rendering a workaround rather than a long-term solution: you maintain two outputs, and they can drift apart.
Hydration has a cost too. Shipping and running a large JavaScript bundle can make pages slow to respond, which shows up in INP. The sites we build in our web design and development work send complete HTML and keep JavaScript to what each page needs.
How to test what Googlebot sees
- URL Inspection in Search Console. View crawled page shows the HTML Google stored at its last crawl. The live test fetches and renders the page now, with a screenshot, the page resources and any JavaScript console messages. Check that your main content, links, canonical and meta robots are all there. A blank or half-built screenshot usually means a script failed or a resource was blocked.
- Raw HTML against the rendered DOM. Compare view-source (or a
curlfetch) with the DOM in your browser’s developer tools. Anything that appears only in the second depends on JavaScript. - Crawl twice. Run your crawler with JavaScript rendering off, then on, and compare word counts, links, titles and canonicals by template. The differences are your JavaScript dependencies.
The Rich Results Test renders pages too, which helps when structured data is injected by JavaScript. If you’re moving to a JavaScript framework, treat it as a migration: everything in redesigning or migrating without losing traffic applies.
Beyond Google: crawlers that don’t render
Google renders. Many other crawlers don’t. In our log reviews, many AI crawlers fetch the HTML and never request the JavaScript files a browser would. If your content exists only after rendering, those systems may not see it at all, which makes server-rendered HTML part of AI search optimisation as much as SEO.
Where your important content should live
Only after JavaScript
- Main copy loaded by client-side API calls
- Navigation built from click handlers
- Canonical or noindex set by a script
- Prices and stock added after load
- Structured data injected by a tag manager
In the initial HTML
- Headings and main copy
- Real links with an href
- Title, canonical and meta robots
- Prices and key product details
- JSON-LD for the main entity
None of this means a JavaScript site can’t rank. It means the important parts need to be in place before any script runs, so every crawler starts from the same complete page.
Running a React, Vue or headless build? Our technical SEO team can audit what Googlebot and other crawlers really receive, template by template.