JAVASCRIPT SEO · FIELD NOTES
JavaScript SEO: How Google Renders JS and Where It Breaks
Google renders JavaScript — that debate is over. The real question is whether the page Google indexed is the page you shipped, and most JavaScript SEO advice can't tell you.
Search “JavaScript SEO” and the results split into two stale genres: 2018-era panic (“does Google even render JavaScript?”) and framework cheerleading (“Next.js is SEO-friendly, ship it”). Both miss where the damage actually happens now. Google renders JavaScript — that debate is over. The live question is narrower and much more expensive: is the page Google indexed the same page you shipped? Client-only content, hydration failures, and JavaScript redirects all produce sites that look perfect in the browser and arrive incomplete in the index — and because the page “works,” nobody notices until the traffic report does.
This guide covers how Google actually crawls and renders JavaScript, the indexation traps that survive the render era, and why a JavaScript redirect leaks equity that a server 301 does not. The method throughout is repo-first: on a modern framework site, every one of these failures is visible in a handful of source files — next.config, middleware.ts, the client-component boundaries — before any crawler runs. This is the JavaScript layer of our agentic SEO pillar, and it follows the same rule as the technical SEO audit: no fix is done until Search Console says the number moved.
How Google actually crawls and renders JavaScript
Google processes a JavaScript page in phases, and the gap between them is where JavaScript SEO lives. First, Googlebot fetches the raw HTML — whatever the server returns before a single script runs. Links and content found there enter processing immediately. Anything that only exists after JavaScript executes waits for the second phase: the page enters a render queue, and when resources allow, Google’s Web Rendering Service loads it in a headless, evergreen Chromium — up to date with stable Chrome since Google’s 2019 Googlebot update — executes the scripts, and indexes the rendered result.
Two properties of that pipeline matter more than the pipeline itself. The queue is usually fast — Google’s Martin Splitt has put the median render wait at around five seconds, with the long tail in minutes — so “Google takes weeks to render JS” is mostly dead folklore. But rendering is best-effort: scripts that error, resources blocked by robots rules, API calls that time out or require credentials, and content that only appears after a click or scroll all silently fail. Googlebot does not click buttons, does not fill forms, and does not scroll the way a user does — Google’s own JavaScript SEO documentation says content loaded on interaction will not be indexed. What renders is what indexes. What fails renders as absence, and absence produces no error message.
The indexation traps that survive the render era
Because rendering mostly works, the failures that remain are the quiet kind: pages that pass every eyeball test and still index incomplete. Three patterns account for most of them, and all three are diagnosable from the source before a crawler ever runs.
Client-only content: the page that arrives empty
The classic single-page-app pattern — serve a shell, fetch the real content with client-side JavaScript after load — puts your primary content on the wrong side of the render gap. If the fetch is fast, public, and error-free, Google usually gets it. Every “if” in that sentence is a failure mode. On a site we audited, product pages looked complete in every browser while the body copy loaded from an API call that failed under Googlebot’s conditions — the indexed version was a template: navigation, boilerplate, no product. The site was not penalized. It was simply indexed as the empty thing it served. In the repo this is a one-file diagnosis: content fetched in a useEffect or behind a ’use client’ boundary is client-only; content fetched in a server component ships in the HTML and never enters the render queue at all.
Hydration failures: shipped twice, indexed once
Modern frameworks server-render the HTML, then hydrate it — client JavaScript takes over the static markup and makes it interactive. When the server markup and the client render disagree, frameworks resolve the mismatch by throwing the server version away and re-rendering on the client — which silently converts your server-rendered page back into a client-rendered one, render gap and all. Worse are hydration-adjacent errors that blank entire component subtrees: an unhandled exception in one component and everything below it vanishes from the rendered DOM. The page still returns 200. Nothing in a crawl report flags it. The tell is in Search Console: the URL Inspection tool’s rendered HTML is the ground truth for what Google actually built — diff it against your own DOM and the missing subtrees announce themselves.
Links and metadata that only exist after render
Discovery runs on the raw HTML first. Navigation rendered purely client-side, onClick handlers doing duty as links, and pagination that mutates state instead of pointing at URLs all hide your internal link graph from the first, cheapest phase of crawling — and a link Google cannot see is link equity that never flows. The same applies to metadata: titles, canonicals, and robots directives set by client-side JavaScript arrive late, and a canonical or noindex that changes between raw and rendered HTML is an ambiguity you handed to Google to resolve. The repo-first rule is blunt: real <a href> elements for every crawl path, and metadata emitted server-side — in Next.js terms, generateMetadata, never document.title.

JavaScript redirects vs 301s: the slow leak
Redirects are where JavaScript SEO stops being subtle. A server 301 happens at the HTTP layer: the crawler requests the old URL, the server answers “moved permanently, here’s where” in the response status, and Google consolidates signals to the new URL — no rendering, no interpretation, no queue. A JavaScript redirect — window.location in a script — is invisible at that layer. The old URL returns 200 with a page attached. To even discover that a redirect exists, Google has to crawl the page, queue it, render it, execute the script, and infer the move. Google’s own redirect documentation is unambiguous about the hierarchy: server-side permanent redirects are the preferred method, and JavaScript redirects are the last resort, for when nothing else is available to you.
“Last resort” undersells the cost, because Google is no longer the only reader that matters. Every consumer of your URLs that does not execute JavaScript — social scrapers building link previews, most SEO tools mapping your redirects, and, decisively, the AI crawlers — sees a JavaScript redirect as a live 200 page that never moved. The old URL keeps accumulating references. The new one starts from less than it should. That is the leak: not a penalty, just signal consolidation that arrives late for Google and never for everyone else.
| What the reader sees | Server 301 | JavaScript redirect |
|---|---|---|
| Googlebot, first fetch | Redirect known instantly from the status code | 200 + a page; redirect unknown until rendered |
| Signal consolidation | Begins immediately at the HTTP layer | Delayed by crawl + queue + render, per URL, repeatedly |
| AI crawlers (no JS execution) | Follow the move like any HTTP client | See the old URL as a live page, forever |
| Link-preview and SEO-tool scrapers | Resolve to the new URL | Attribute shares and links to the dead address |
| Verification metric after migrating | Position + impressions shift to the new URL in GSC | Old URL lingers in the index; the before/after smears |
The repo makes this audit almost embarrassingly fast. In a Next.js codebase, legitimate redirects live in exactly three places — redirects() in next.config, NextResponse.redirect in middleware, and redirect() calls in server code — all of which emit real HTTP status codes. Anything else is a leak: grep the client bundles for window.location assignments and router.replace calls standing in for moves that should be server-side, and you have found in minutes what a crawl-based redirect audit reconstructs over days.
The migration shipped as JavaScript redirects
Every old URL returns 200 and redirects in a script. Google discovers each move only after a full crawl-queue-render cycle per URL. AI crawlers and scrapers never discover them at all — the old addresses live on as phantom pages, splitting every signal the new URLs need.
The migration shipped as 301s in next.config
Every old URL answers moved-permanently at the HTTP layer. Consolidation starts on first crawl for every reader, JavaScript-executing or not. Three weeks later the verdict is readable in Search Console: impressions and position transferred to the new URLs, old ones falling out of the index.
The blind spot: AI crawlers do not render
Here is the render-era twist most JavaScript SEO guides have not caught up with: the newest readers of your site behave like the oldest ones. A 2024 analysis of AI crawler traffic by Vercel and MERJ found that the major AI crawlers — OpenAI’s GPTBot, Anthropic’s ClaudeBot, PerplexityBot — fetch HTML but do not execute JavaScript. Gemini is the exception only because it rides Googlebot’s rendering infrastructure. For everyone else, your site is exactly what the server returns: client-only content does not exist, JavaScript redirects never happened, and a client-rendered page is an empty shell wearing a 200.
This means the render gap you closed for Google in 2019 has quietly reopened for the systems deciding what gets cited in AI answers. If a growing share of your discovery runs through engines that read raw HTML — the shift our AI search pillar covers in full — then server-rendering your primary content stops being a performance nicety and becomes the difference between being quotable and being invisible. The practical rule collapses to one sentence: anything you want any machine to read must be in the HTML the server sends. Getting rendered is table stakes for Google; being readable without rendering is the bar for everything after Google.
Google spent five years learning to run your JavaScript — and then the AI crawlers arrived reading raw HTML like it is 2015 again. The page that wins both eras is the one that never needed the render.
The repo-first JavaScript SEO checklist
Crawl-first JavaScript auditing means rendering your own site page by page and reverse-engineering causes from symptoms. Repo-first inverts it: the rendering behavior of every page is determined by a small set of source files, and a defect in a shared layout or config is a defect on every page it touches — knowable from one file read. Here is where each question lives in a Next.js repository, and the Search Console metric that will judge the fix.
| Audit question | Where the answer lives in the repo | Verify metric |
|---|---|---|
| Is primary content server-rendered? | 'use client' boundaries; data fetched in server components vs useEffect | Impressions on affected URLs |
| Are all redirects HTTP redirects? | redirects() in next.config, middleware, redirect() calls — vs window.location in bundles | Position + impressions on destination URLs |
| Can links be crawled without executing JS? | Real <a href> vs onClick handlers; nav and pagination components | Impressions on deep pages |
| Is metadata emitted server-side? | generateMetadata / metadata exports — never client-set titles or canonicals | CTR on affected queries |
| Can the renderer fetch every resource? | app/robots.ts — are JS/CSS/API routes accidentally disallowed? | Impressions, site-wide |
Then confirm intent against reality once: URL Inspection in Search Console shows the rendered HTML Google actually built — the single most underused JavaScript SEO tool, and free. If a content block is in your DOM and missing from Google’s rendered copy, you have a render-gap finding with the evidence attached. The wider five-layer version of this pass — crawl, indexation, canonicals, schema, Core Web Vitals — is the technical SEO audit; this checklist is the layer of it that JavaScript specifically puts at risk.
No fix is done until Search Console says so
JavaScript SEO fixes are unusually easy to declare victory on, because the visible symptom disappears instantly: the content is in view-source, the redirect returns 301, the audit item turns green. None of that is the outcome. The outcome is Google re-crawling, re-rendering, and re-indexing the affected URLs — and the only place that becomes visible is Search Console, weeks later. So every finding from the checklist above ships with its proof contract attached before the fix does: the expected impact, the metric that will judge it (impressions for content that entered the index, position and impressions for consolidated redirects, CTR for metadata), and the target that counts as success. About three weeks after the fix lands, the metric is re-read and the verdict recorded: confirmed, regressed, or inconclusive.
This discipline pays double on JavaScript work, because render-gap fixes are among the most verifiable in all of SEO: content that was absent from the index either starts earning impressions or it does not. A migration re-shipped as 301s either consolidates in the position data or it does not. Run the loop honestly and you also learn which JavaScript fixes on your site are worth the engineering time — and which were hygiene that never moved a number, which is a result too.
If you want the find half run against your own data, the free SEO report connects your Search Console and surfaces the symptoms this article describes — pages shown far less than their siblings, URLs that should have consolidated and did not. And if you would rather the whole loop ran as a system — source files read, fixes shipped as pull requests, verdicts read from GSC three weeks later — book a call and we will show you what that looks like on your stack.
Frequently asked questions

Written by
Luke McCormackFounder, My Agentic SEO
SEO & Google specialist leading go-to-market and growth at My Agentic SEO.
More about LukePart of the cluster
Agentic SEO: What It Is and How an Agent Runs Your SEOLet the agent run the first stage on your real data
Connect your Google Search Console and get a free professional SEO report — diagnosed and prioritized on your actual site. It takes minutes.