Every few months I get a variation of the same message: "We rebuilt the site on a modern framework, it's much faster, and organic traffic has dropped." The site is usually fine for users. It's often not fine for Googlebot, and the reasons are specific and fixable. This sub-guide of my technical SEO guide covers what happens between a JavaScript-heavy page and Google's index.
How Google processes a JavaScript page
Googlebot processes a page in two passes. First it reads the raw HTML as served: whatever text, links, meta tags and canonicals exist without executing any script. Those links are queued and that content can be indexed immediately. Then the page goes into a render queue, where a headless Chromium instance executes the JavaScript and Google indexes whatever the rendered DOM contains.
- Fetch raw HTML
- Index what's there, queue links
- Render queue
- Execute JavaScript
- Index rendered DOM
- Discover rendered links
Three things about the second pass matter. It is not instant; it can happen seconds or days later. It is not guaranteed to complete; scripts that error, time out, depend on blocked resources or need user interaction simply don't produce content. And it is costly, so on large sites Google renders a fraction of URLs per visit. If everything important is only in the rendered DOM, your whole site sits behind a slow, rationed, failure-prone step.
Rendering architectures, and what each means for SEO
The architecture decision is usually made by engineering before SEO is in the room. Know the trade-offs well enough to argue for the right one.
| Approach | Where HTML is produced | What Googlebot gets on first fetch | SEO risk | Typical fit |
|---|---|---|---|---|
| Client-side rendering (CSR) | In the browser, after JavaScript loads | A near-empty shell | High. Everything depends on the render queue. | Logged-in apps, dashboards, anything not meant to rank |
| Server-side rendering (SSR) | On the server, per request | Full HTML with content and links | Low, if hydration doesn't alter content | Ecommerce, marketplaces, frequently changing content |
| Static generation (SSG) | At build time | Full HTML from a CDN | Lowest. Also fastest. | Marketing sites, docs, guides, programmatic pages that change rarely |
| Hybrid (per-route or incremental) | Static where possible, server or client where needed | Depends on route | Low if ranking routes are server- or statically rendered | Most real SaaS and content sites |
| Dynamic rendering (prerendered version for bots) | A prerendering service, bots only | Full HTML, but a different build from what users see | Medium. A workaround Google now discourages. | Legacy CSR apps that can't be rebuilt yet |
My default position: anything that needs to rank should be in the raw HTML on first fetch. SSR or SSG for ranking pages, CSR only for the interactive parts behind login. Modern frameworks make this a configuration choice, not a rewrite, which is why "React is bad for SEO" is out of date. The framework isn't the problem. The rendering mode is.
Hydration: where content quietly changes
With SSR, the server sends complete HTML, then JavaScript loads and "hydrates" it, attaching interactivity. In theory the content doesn't change. In practice, I've seen hydration remove content (a component that renders empty until an API call resolves), replace it (client-side personalisation), or inject conflicting head tags. The rule: the rendered DOM should be a superset of the raw HTML, never a different page.
Where content and links go missing
Lazy-loaded content
Content loaded when it scrolls into view, or after a timer, doesn't exist for Googlebot, which doesn't scroll. Product descriptions in a tab that fetches on click, reviews that load on scroll, "load more" sections: all invisible. Native image lazy-loading with loading="lazy" is fine; content that needs an event to fetch is not. If it needs to be indexed, it needs to be in the HTML.
Links that aren't links
Googlebot follows <a href="…"> elements. It does not follow <div onclick>, buttons with router pushes, or anchors with href="#" and a click handler. Single-page-app navigation without real anchors produces a site where every page is an orphan from Google's point of view. Render proper anchors with real URLs and let the router intercept clicks for users. This is a common cause of the "Discovered – currently not indexed" problem in the crawlability guide.
Infinite scroll
Infinite scroll with no paginated fallback means Googlebot sees the first batch and nothing else. Pair it with real paginated URLs (?page=2) that are linked in the HTML and update as the user scrolls. Users get the smooth experience; Googlebot gets a path to every item.
Head tags set by JavaScript
Titles, descriptions, canonicals, robots meta and hreflang set client-side are only seen after rendering, and if the raw HTML holds a different value, Google may use either. Set them server-side. A canonical that only exists post-render is barely a hint; see the canonical tags guide.
Blocked resources
If robots.txt blocks the JavaScript bundle, the API the page calls, or the CDN serving them, the render fails silently. The page looks fine in a browser and empty to Googlebot. It's the first thing I check on a JavaScript site with indexing problems.
Testing: rendered HTML versus raw HTML
You cannot reason about JavaScript SEO; you have to look. Compare what the server sends to what the browser builds, then check what Google says it saw.
Fetch the raw HTML
Use curl or "view source" (not the DevTools inspector, which shows the rendered DOM). Look for the main heading, a paragraph of body text, the canonical tag and a few internal links. If they're absent, you're relying on rendering.
Inspect the rendered DOM
Open DevTools' Elements panel and search for the same things. Note anything present here but missing in step 1, and anything that differs (titles, canonicals).
Ask Google
Run URL Inspection in Search Console, "Test live URL", then "View tested page". Compare the HTML with your rendered DOM and check "More info" for blocked resources and console errors. The screenshot tells you if the page rendered at all.
Crawl with rendering on and off
Two crawler runs, one with JavaScript rendering disabled and one enabled, show at site scale which pages, links and tags depend on JavaScript. The diff is your issue list.
Check the index
Search for an exact sentence of rendered-only content in quotes. If Google indexed it, it rendered the page. If not, it either hasn't yet or couldn't.
I ran this comparison as a controlled experiment, measuring how quickly and completely Google indexed identical content served raw versus rendered-only, in the JavaScript rendering test. The gap is why I keep insisting ranking content belongs in the first fetch.
Framework notes
None of the major frameworks is inherently bad for SEO. Each has a well-supported path to server or static rendering.
React / Next.js. Plain React (Vite templates, Create React App) is client-rendered by default. Next.js offers static generation, server rendering and incremental regeneration per route, with server components as the default in the App Router. Common mistakes: fetching ranking content inside client components, and leaving metadata to client-side libraries.
Vue / Nuxt. Same pattern. Vue alone is client-side; Nuxt provides SSR and static generation with sensible defaults. Watch for content behind <ClientOnly> wrappers and head tags set only on the client.
Angular. Angular's SSR support (formerly Angular Universal) handles server rendering. Older builds are often fully client-rendered and need it enabled. Lazy-loaded route modules are fine; content that waits on module load is not.
For SaaS products, the pragmatic structure is often two surfaces: a statically generated marketing and docs site that ranks, and a client-rendered application behind login that doesn't need to. Mixing them in one CSR bundle is how marketing pages end up invisible.
JavaScript SEO checklist
Content in raw HTML
Headings, body text, product details and prices are present on first fetch for every page that should rank.
Real anchors with real hrefs
All navigation, pagination and contextual links are <a href> elements with crawlable URLs.
Head tags server-side
Title, description, canonical, robots and hreflang are in the raw HTML and don't change after hydration.
No blocked resources
JavaScript, CSS, fonts and API endpoints the page needs are allowed in robots.txt on every host they're served from.
No interaction-gated content
Nothing that should be indexed waits for a scroll, click, tab or timer. Infinite scroll has paginated URLs.
Status codes are real
Missing pages return a 404 or 410 at the HTTP level, not a 200 with a "not found" component rendered by JavaScript.
Frequently asked questions
Can Google index JavaScript-rendered content at all?
Yes, usually. Google renders pages in a second pass and indexes the result. The problems are delay, incompleteness and failure: rendering can be deferred, interaction-gated content is missed, and errors or blocked resources break it silently. Server or static rendering removes those risks for pages that need to rank.
Is a single-page application bad for SEO?
Not inherently. An SPA with server-side rendering, real anchor links and server-set head tags works well. One that renders everything client-side with router-driven navigation and no HTML fallback tends to index poorly. The architecture matters more than the label.
Should I use dynamic rendering or prerendering for bots?
Only as a temporary bridge for a legacy client-rendered site that can't be rebuilt yet. It adds infrastructure, creates a bot-versus-user mismatch that's easy to break, and Google now recommends proper server or static rendering instead.
How do I know if Google has rendered my page?
Use URL Inspection's live test and view the rendered HTML and screenshot. For an indexed page, search Google for an exact phrase that only exists after rendering. If it's found, rendering happened; if not, check for blocked resources and console errors.
Does lazy-loading images hurt JavaScript SEO?
Native lazy-loading via the loading attribute is fine. Custom JavaScript lazy-loading that only sets the image source on scroll can hide images from Google. Either way, never lazy-load the main above-the-fold image, which also matters for Core Web Vitals.
Where this connects
This guide sits under the technical SEO hub. Rendering checks are a dedicated section of my technical SEO audit framework; the raw-versus-rendered indexing comparison is the JavaScript rendering test; the SaaS organic growth case study shows what changes when a framework-built marketing site is made properly crawlable; and fixing it is part of my technical SEO services.