JavaScript SEO
How to make sure search engines can crawl, render, and index JavaScript-dependent content — real links, parity, lazy-loading, infinite scroll, soft-404s, and testing.
1 evidence signal on this page
- Related live toolRaw vs. Rendered HTML Checker
JavaScript SEO is about whether search engines can crawl, render, and index content that depends on JavaScript. Google can run JS — the failure modes are more specific: parity (raw vs rendered), interaction (Google doesn't scroll or click), state (the renderer is stateless), and timing. Keep links as real anchors, don't block JS/CSS, prefer SSR/prerendering for content that must rank, and watch infinite scroll — a tall render viewport can get two pages indexed as one.
JavaScript SEO
How rendering, discoverability, and indexation fit together.
View the full learning path
- How Search WorksHow Google and Bing turn the open web into an answer — the crawl → index → serve pipeline as three gates, with rendering, canonicalization, and ranking systems explained.
- CrawlingHow search engines discover and download the web — Googlebot and Bingbot, URL discovery, the crawl scheduler, rendering, and how crawling differs from indexing and ranking. The hub for everything crawl-related.
- RenderingHow Google's Web Rendering Service runs your JavaScript to build the page it indexes — plus the rendering options (CSR, SSR, SSG, hydration, ISR, edge, dynamic) and their SEO trade-offs.
- URL Inspection ToolHow Google's URL Inspection tool reports a single URL — the indexed snapshot vs the live test, reading the coverage panel, canonicals, Request Indexing, and the API.
Every published guide in this hub, grouped by the site taxonomy.
All JavaScript SEO guides28
- Rendering
- Angular SEO
- Astro SEO
- Client-Side JavaScript Frameworks
- Eleventy SEO
- Full-Stack Meta-Frameworks
- Gatsby SEO
- Hexo SEO
- Hugo SEO
- Infinite Scroll SEO
- JavaScript Redirects
- Jekyll SEO
- Next-Gen JavaScript Frameworks
- Next.js SEO
- Nuxt SEO
- PWA SEO
- Qwik SEO
- React SEO
- Remix SEO
- Sanity SEO
- SEO for a Headless CMS
- SolidJS SEO
- SPA SEO
- Storyblok SEO
- Strapi SEO
- Svelte SEO
- SvelteKit Deployment SEO: Adapters, Prerendering, and Edge Rendering
- Vue SEO
TL;DR — JavaScript SEO is about one question: can search engines see your content? Modern sites build a lot of the page in the browser with JavaScript. If your important text and links only show up after scripts run, you need to confirm Google can still reach them. Google usually can — the trouble is in the details.
What JavaScript SEO is
Lots of sites build part (or all) of the page in your browser with JavaScript. The server sends some HTML, and then scripts run to fill in content, load more items, or swap views without a full page reload. JavaScript SEO is the practice of making sure search engines can still crawl, render, and index that content.
Here’s the order things happen in for Google:
- Crawl — Google downloads the raw HTML of your URL.
- Render — Google runs the page’s JavaScript in a browser to build the finished page (the rendering step).
- Index — Google reads that finished page and files it away.
Google documents these as the three main phases for processing JavaScript web apps.
Evidence for this claim Google processes JavaScript web apps in three main phases: crawling, rendering, and indexing; without rendering, Google might not see JavaScript-provided content. Scope: Google Search's processing of JavaScript pages; successful rendering does not guarantee indexing or ranking. Confidence: high · Verified: Google Search Central: Understand the JavaScript SEO basicsIf your content only appears after JavaScript runs, Google has to render the page successfully before it can see it. Most of the time it does. When it doesn’t, your content can quietly go missing from search.
The good news first
JavaScript is not bad for SEO. Google runs an up-to-date version of Chrome and can execute the same JavaScript your visitors do. The old fear — “Google can’t read JavaScript” — just isn’t true anymore.
What can go wrong is more specific:
- Your content needs a click or a scroll to load, and Google doesn’t click or scroll. Evidence for this claim Google Search does not interact with a page, so lazy-loaded content should load when it becomes visible in the viewport rather than requiring user interaction. Scope: Google Search's documented rendering behavior for lazy-loaded content; other crawlers can behave differently. Confidence: high · Verified: Google Search Central: Fix lazy-loaded content
- Your links aren’t real links (they’re buttons or click handlers), so Google can’t follow them. Evidence for this claim Google can reliably discover links only when they are HTML anchor elements with an href attribute. Scope: Link discovery by Google Search; this does not claim that every discovered URL will be crawled or indexed. Confidence: high · Verified: Google Search Central: Make your links crawlable
- You accidentally blocked your JavaScript or CSS files in
robots.txt, so Google can’t render the page properly. - The page looks fine in your browser, but the content never shows up in Google’s rendered view.
The simple checklist
- Compare your raw HTML (right-click → View Source) with the rendered HTML (Google Search Console’s URL Inspection tool). If important content is missing from the rendered view, that’s your problem.
- Make sure links are real
<a href>links, not click handlers on a<div>. - Don’t block your JavaScript or CSS files in
robots.txt. - If a feature needs a click or scroll to load content, make sure that content is also reachable some other way.
- For content that absolutely must rank, prefer server-side rendering or a static/prerendered build, where the content is already in the raw HTML. Evidence for this claim Google describes server-side rendering or pre-rendering as a good idea because it makes a website faster for users and crawlers. Scope: Google Search guidance for JavaScript sites; the source does not prescribe one framework or guarantee indexing. Confidence: high · Verified: Google Search Central: Understand the JavaScript SEO basics
Want the deeper version — the real failure modes, the infinite-scroll trap that gets two pages indexed as one, and how to test rendered HTML? Switch to the Advanced tab. For how Google’s renderer itself works and which rendering setup to choose, see the rendering page.
TL;DR — Google can run your JavaScript, so “can Google read JS?” is the wrong question. The failure modes are parity (raw vs. rendered DOM), interaction (Google doesn’t scroll or click), state (the renderer is stateless), and timing. Keep links as real
<a href>anchors, don’t block JS/CSS, lazy-load on viewport not interaction, return real statuses for client-side 404s, and remember a rawnoindexmay prevent rendering before JavaScript can remove it. Combine directives that were actually processed, but do not assume a universal raw/rendered winner. Watch infinite scroll especially: a tall render viewport can trigger the loader and merge two URLs into one indexed page. For the renderer internals and which rendering mode to pick, see rendering.
Can Google read JavaScript? Yes — that’s not the question
Three stages run left to right: crawl, render, and index. The render stage branches into four failure modes: parity, where the rendered DOM may not match expectations; interaction, where content requires a scroll or click; state, where content relies on cookies or storage that the renderer clears; and timing, where content is deferred behind slow JavaScript.
© Patrick Stox LLC · CC BY 4.0 ·
Google processes JavaScript apps in three phases: “Google processes JavaScript web apps in three main phases: 1. Crawling 2. Rendering 3. Indexing.” The middle phase runs your JS in an evergreen, headless Chrome to build the DOM that gets indexed. “Rendering is important because websites often rely on JavaScript to bring content to the page, and without rendering Google might not see that content.”
Evidence for this claim Google processes JavaScript web apps in three main phases: crawling, rendering, and indexing; without rendering, Google might not see JavaScript-provided content. Scope: Google Search's processing of JavaScript pages; successful rendering does not guarantee indexing or ranking. Confidence: high · Verified: Google Search Central: Understand the JavaScript SEO basicsSo Google can run your JavaScript. The useful questions are more specific:
- Parity — does the rendered DOM actually contain what you think it does?
- Interaction — does anything require a scroll/click Google won’t perform?
- State — are you relying on cookies/localStorage the stateless renderer clears?
- Timing — is critical content deferred behind slow or late JavaScript?
On timing specifically: Google queues a crawled page (one that returned a 200) for
rendering, and “the page may stay on this queue for a few seconds, but it can take
longer than that.” There’s no published fixed delay or timeout — and a page that
returns a non-200 status, or that starts out with a noindex directive, can skip the
render queue rather than wait for JavaScript to change it.
The mechanics of how Google renders — the Web Rendering Service, statelessness, caching, the “two waves” myth — live on the rendering page. Here I’ll focus on the practical problems and fixes.
Links must be real <a href> anchors
This is the single most common JS-SEO bug. “Google can only discover your links if
they are <a> HTML elements with an href attribute.” A clickable <div> with an
onclick handler is invisible to Google as a link — it won’t be followed, and pages
that depend on it for discovery can go uncrawled. It’s perfectly fine to inject
links with JavaScript, as long as they end up as real <a href> anchors in the
rendered DOM. Those rendered anchors are parsed after JavaScript runs, though —
landing in the rendered DOM makes a link discoverable, it’s not a promise that the
URL gets crawled, indexed, or treated the same as a link present in the raw HTML.
Lazy-loaded and interaction-gated content
The renderer doesn’t behave like a curious user: “Google Search does not interact with your page.” No scrolling, no clicking, no hovering. So any content that only loads on one of those events won’t be seen.
Google’s guidance: load content when it enters the viewport, not when the user acts —
“make sure that your lazy-loading implementation loads all relevant content whenever
it is visible in the viewport,” and “don’t add lazy-loading to content that is
likely to be immediately visible when a user opens a page.” Use IntersectionObserver
or native loading="lazy" for images — never a scroll or click handler — so the
content loads during a normal render.
Infinite scroll: when two pages get indexed as one
A normal browser viewport stops after the first page, but Google's render viewport expands much taller. The expansion reaches an infinite-scroll trigger, fires the loader without a real user scroll, and appends the next page into the same DOM. Google then indexes both pages' content under one URL.
© Patrick Stox LLC · CC BY 4.0 ·
This is the one almost nobody explains, and it’s worth the whole section.
Googlebot can render at a viewport considerably taller than a typical browser window. Google doesn’t publish an exact render viewport size — and it can change — so don’t design against a specific number; test your own implementation instead. What matters is the mechanism: if your infinite-scroll loader fires based on scroll position or viewport height, a taller-than-expected rendering viewport can trigger the loader during rendering itself — and the next article or product page’s content gets appended into the same DOM. Now two distinct URLs’ content has been rendered together, and Google can index them as one page. In my own experience, “occasionally, two pages get indexed as one” — I’ve had pages reported as “not indexed” that were actually indexed as part of another page (usually the previous post in the feed), because “when Google resized the viewport to be longer … it triggered the infinite scroll and loaded another article in when it was rendering.” Confirm whether your own setup does this by checking URL Inspection’s rendered HTML for a page you’d expect to stop short — don’t assume a size and don’t assume you’re safe.
There are two layers to getting this right.
Make infinite scroll search-friendly in the first place. Support paginated loading
underneath the infinite scroll. Each chunk should have “its own persistent, unique
URL,” the content on each URL should stay the same each time it loads, you should
avoid relative parameters like ?date=yesterday, you should “link sequentially to
the individual URLs so that search engines can discover the URLs in a paginated set,”
and when a new chunk loads on scroll you should “update the displayed URL using the
History API.” Use real <a href> pagination links and unique URLs — “don’t use URL
fragment identifiers” (the part after a #) for page numbers, because Google ignores
them. As I say in my JavaScript SEO guide, “if you have an infinite scroll setup, I still recommend a
paginated page version so that Google can still crawl properly.”
If a buggy loader is actively merging pages, the fastest fix is blunt: “block the JavaScript file that handles the infinite scrolling so the functionality can’t trigger.” If the loader can’t run during render, it can’t append the next page’s content, and each URL renders as itself again.
Soft-404s after client-side routing
Single-page apps can swap content without changing the HTTP status code, so a
“not found” view can still return 200. Google may classify that response as a
soft 404 after evaluating the returned content, but a static fetch alone cannot prove
that Google has made that classification. Two fixes:
navigate with the History API, and for a genuine not-found state either route to a
URL that returns a real 404 status or add a noindex tag. And don’t lean on URL
fragments for routing — “the AJAX-crawling scheme has been deprecated since 2015, so
you can’t rely on URL fragments to work with Googlebot.”
A client-side redirect has the same evidence problem: the initial response can remain
200 until JavaScript runs. Google supports JavaScript redirects only as a fallback
when server-side or meta-refresh redirects are not possible. Report the static status
and the observed rendered navigation separately; do not rewrite the HTTP status in the
audit or call every rendered URL change a redirect.
Don’t block JavaScript or CSS in robots.txt
Google won’t render JavaScript from blocked files or on blocked pages. A
robots.txt rule that disallows your bundle (or the /_next/, /static/, /assets/
directory it lives in) can break rendering entirely — Google fetches the shell, can’t
run the scripts, and indexes an empty page. Check URL Inspection’s page resources
for anything blocked.
DOM parity and stage-aware robots directives
Compare your raw HTML (View Source) against the rendered HTML (URL Inspection). Content that only exists in the rendered HTML still indexes — if it renders. Content in neither doesn’t exist to Google.
There’s one stage-order risk Google documents directly: “When Google encounters the
noindex tag, it may skip rendering and JavaScript execution, which means using
JavaScript to change or remove the robots meta tag from noindex may not work as
expected.” If your raw HTML ships an initial noindex you meant to swap out with
JavaScript, Google can act on that raw noindex and never run the script that would
have removed it.
Do not turn this into a universal “rendered wins” rule. Reconciliation is field-specific:
| Field | What the raw/rendered comparison can establish |
|---|---|
| Main content and links | Google can use content and real <a href> links produced during rendering if rendering succeeds. Raw availability reduces that dependency. |
| Title and description | Google can process JavaScript-set metadata, but title links and snippets are selected from several sources. Show both states; do not claim the rendered, first, or last value is guaranteed. |
| Robots directives | A raw noindex may cause Google to skip rendering, so JavaScript removal may never be seen. Adding restrictions later is not evidence that an earlier restriction was cancelled. |
| Canonical | Google’s JavaScript guidance says not to set one value in source and then change it with JavaScript. Use one method and verify one rendered-head declaration. |
| HTTP status and redirect | JavaScript cannot change the response status already received. Record the static status and any observed rendered navigation as separate facts. |
That matrix is why an auditor should keep source, rendered, response-header, and observed-search state distinct rather than collapsing them into one “effective” value.
Pick a rendering mode that puts content in the DOM
Most JS-SEO risk comes down to how the HTML is produced. The short version: SSR, static/prerendering, and hydration all put content in (or quickly into) the DOM, which reduces how much your visibility depends on the renderer succeeding; full client-side rendering leaves more riding on rendering completing correctly, on time, every time. Dynamic rendering is a workaround, not a peer option — as of Google’s guidance last updated December 2025, it describes dynamic rendering as a workaround rather than a long-term solution and recommends server-side rendering, static rendering, or hydration instead. As I put it in my JavaScript SEO guide, “any kind of SSR, static rendering, and prerendering setup is going to be fine for search engines.” The full menu — CSR, SSR, SSG, hydration, ISR, edge, streaming, and dynamic rendering — with a trade-off table is on the rendering page. Google separately describes server-side rendering or pre-rendering as a good idea for users and crawlers. Evidence for this claim Google describes server-side rendering or pre-rendering as a good idea because it makes a website faster for users and crawlers. Scope: Google Search guidance for JavaScript sites; the source does not prescribe one framework or guarantee indexing. Confidence: high · Verified: Google Search Central: Understand the JavaScript SEO basics
How to test
URL Inspection in Search Console is the source of truth: run a live test, then look at the rendered HTML, the screenshot, and the page resources / console messages to see what loaded and what failed. The Rich Results Test gives a quick rendered-HTML check. At scale, use a crawler that executes JavaScript (Ahrefs Site Audit, Screaming Frog in JS-rendering mode) to diff raw vs. rendered across the site.
An illustrative page has 1 title in both raw and rendered HTML, 18 headings in raw HTML and 19 rendered, 42 internal links in raw HTML and 71 rendered, 0 product descriptions in raw HTML and 12 rendered, and 6 canonical tags in raw HTML but only 1 rendered. These counts are synthetic.
JavaScript is not bad for SEO, and it’s not evil. It’s just different from what many SEOs are used to. Work with your developers, get your important content into the DOM, and let Google’s own tools — not your assumptions — be the arbiter of what rendered.
Where to go next: the JavaScript SEO cluster
This hub is the map. Each topic below is its own deep dive:
Rendering and architecture
- SEO for a headless CMS — how decoupled frontends affect crawling, rendering, metadata, sitemaps, and canonical tags; which rendering mode to pick; and the headless-specific failure modes you need to know.
Framework-specific guides
- React SEO — why CSR-first React creates indexing risk, how Google renders React apps, React Router and History API, react-helmet-async for meta tags, and when to reach for Next.js.
- Angular SEO — Angular’s SPA defaults,
@angular/ssr(Angular Universal’s successor), the built-in Title and Meta services, prerendering, and incremental hydration in modern Angular. - Next.js SEO — Pages Router vs App Router, the Metadata API,
next/imageand CWV, ISR timing and Googlebot, sitemaps, and the most common Next.js SEO mistakes. - Nuxt SEO — SSR by default,
useSeoMeta(), Nuxt’s rendering modes, the@nuxtjs/seomodule ecosystem, and how Nuxt compares to plain Vue for indexability. - Vue SEO — Vue 3’s CSR default and what that means for crawlers,
createWebHistory(),@unhead/vue, prerendering options without a meta-framework, and when Nuxt is the right call. - Svelte SEO — Svelte vs SvelteKit, SSR by default in SvelteKit,
<svelte:head>, theadapter-static+ssr: falsetrap, adapter choices, and AI crawler implications. - Astro SEO — zero-JS by default, islands architecture,
@astrojs/sitemap,astro:assets, View Transitions and History API, Server Islands fallback behavior, and Astro’s Core Web Vitals advantages.
AI summary
A condensed take on the Advanced version:
- “Can Google read JS?” is the wrong question — it can. The failure modes are parity (raw vs. rendered), interaction (Google doesn’t scroll/click), state (stateless renderer), and timing.
- Timing has no fixed delay — Google queues a
200page for rendering and may wait “a few seconds” or longer, with no published timeout; a non-200 status or an initialnoindexcan skip the render queue entirely. - Links must be real
<a href>anchors —onclickon a<div>is invisible as a link. Injecting links with JS is fine if they end up as anchors, but rendered anchors are parsed after JS runs — discoverable, not a crawl/index guarantee. - Lazy-load on the viewport, not interaction — Google “does not interact with your page.” Use IntersectionObserver / native lazy-load; don’t gate content behind scroll or click.
- Infinite scroll can merge two pages into one — Googlebot can render at a taller
viewport than a typical browser (no exact published size — test your own setup),
and that gap can trigger the loader and append the next page’s content. Fix:
paginated URLs + real
<a href>links + History API; if a loader is merging pages, block its JS file. - Soft-404s after client-side routing — return a real
404ornoindex; don’t index empty shells; don’t rely on URL fragments (AJAX-crawling deprecated 2015). - Don’t block JS/CSS in robots.txt — Google won’t render from blocked files.
noindexcan block its own removal — Google may skip rendering when it sees an initialnoindex, so JS meant to remove it may never run.- DOM parity + stage order (robots meta tags) — diff raw vs. rendered, but remember
an initial raw
noindexmay stop rendering. Apply combination rules only to directives actually processed; test canonical parity under its own rules. - Rendering mode changes dependency, not outcome — SSR/static/prerender/hydration put content in the DOM sooner, reducing reliance on rendering; full CSR relies on it most; dynamic rendering is a dated Google workaround, not a peer option. Full breakdown on the rendering page.
- Test with URL Inspection (rendered HTML + screenshot + console), Rich Results Test, and a JS-rendering crawler.
Official documentation
Primary-source documentation from the search engines.
- Understand the JavaScript SEO basics — the three phases, crawlable links, and testing rendered HTML.
- Fix Search-related JavaScript problems — soft-404 handling, the History API, and renderer constraints.
- Fix lazy-loaded content — load on viewport (not interaction), and search-friendly infinite scroll.
- Ecommerce pagination and incremental page loading — unique URLs,
<a href>links, and why Google ignores fragment identifiers. - In-Depth Guide to How Google Search Works — where rendering sits in crawl → index → serve.
Bing / Microsoft
- bingbot Series: JavaScript, Dynamic Rendering, and Cloaking. Oh My! — Bing’s take on rendering JS and dynamic rendering.
Quotes from the source
On-the-record statements from Google (plus a few from my own writing). Each search-engine link is a deep link that jumps to the quoted passage on the source page.
Google — rendering & links
- “Google processes JavaScript web apps in three main phases: 1. Crawling 2. Rendering 3. Indexing.” Jump to quote
- “Google can only discover your links if they are <a> HTML elements with an href attribute.” Jump to quote
- “Rendering is important because websites often rely on JavaScript to bring content to the page, and without rendering Google might not see that content.” Jump to quote
- “The page may stay on this queue for a few seconds, but it can take longer than that.” — on render-queue timing, no fixed delay published. Jump to quote
- “When Google encounters the noindex tag, it may skip rendering and JavaScript execution, which means using JavaScript to change or remove the robots meta tag from noindex may not work as expected.” Jump to quote
Google — interaction, lazy-load & infinite scroll
- “Google Search does not interact with your page.” Jump to quote
- “…loads all relevant content whenever it is visible in the viewport.” Jump to quote
- “Give each chunk its own persistent, unique URL.” — search-friendly infinite scroll. Jump to quote
- “Don’t use URL fragment identifiers (the text after a # in a URL) for page numbers in a collection. Google ignores fragment identifiers.” Jump to quote
Google — soft-404 & routing
- “We recommend using the History API to load different views.” Jump to quote
Patrick Stox (my own work — JavaScript SEO: A Definitive Guide)
- On meta robots tags specifically: “With meta robots tags, Google is always going to take the most restrictive option it sees — no matter the location… Google will choose the most restrictive statements between HTML and the rendered version of a page.”
- “If you have an infinite scroll setup, I still recommend a paginated page version so that Google can still crawl properly.”
- On the merge: “occasionally, two pages get indexed as one” — caused when “Google resized the viewport to be longer … it triggered the infinite scroll and loaded another article in when it was rendering.” The fix: “block the JavaScript file that handles the infinite scrolling so the functionality can’t trigger.”
JavaScript-SEO checklist
A quick pass to confirm Google can render and index your JS-dependent content:
- Important content appears in the rendered HTML (check URL Inspection, not just View Source).
- Links are real
<a href>anchors — notonclickhandlers on<div>/<span>. - JavaScript and CSS files are not blocked in
robots.txt. - No content is gated behind a click, scroll, or hover (Google doesn’t
interact); lazy-load on viewport via IntersectionObserver or
loading="lazy". - Above-the-fold content is not lazy-loaded.
- Robots directives match between raw and rendered HTML (no JS injecting
noindex) — Google takes the most restrictive. - Client-side route changes that hit a missing resource return a real
404ornoindex(no soft-404 shells). - Infinite scroll has a paginated version with unique
<a href>URLs and History API updates — and isn’t merging pages at a tall viewport. - Content that must rank uses SSR / static / prerendering, not full CSR (see the rendering page).
- You’ve spot-checked the rendered screenshot and console errors in URL Inspection’s live test.
The mental models
1. “Can Google run my JS?” is the wrong question. It can. The real questions are about parity, interaction, state, and timing:
- Parity — does the rendered DOM contain what you think it does?
- Interaction — does anything require a scroll/click Google won’t perform?
- State — are you relying on cookies/localStorage the stateless renderer clears?
- Timing — is critical content deferred behind slow/late JS?
2. If it’s not in the rendered DOM, it doesn’t exist. View Source shows the raw HTML; URL Inspection shows the rendered DOM. Index decisions are made on the rendered DOM — so that’s the artifact to check, every time.
3. Real links or no links.
Discovery rides on <a href> anchors. Click handlers, buttons, and JS navigation that
never produces an anchor are dead ends for crawling.
4. Design for a bot that never touches the page. No scroll, no click, no hover. If content needs an action to appear, assume Google won’t see it — load it on viewport instead.
5. Test for the tall-viewport trap in infinite scroll. Googlebot can render at a viewport taller than a typical browser (no fixed size is published, so don’t design against a specific number), so a scroll/height-triggered loader can fire during render and merge the next page in. Design for it: paginated URLs + real links + History API; if it’s actively merging, block the loader’s JS.
6. Let the tools arbitrate. Your browser is not Googlebot. URL Inspection’s rendered HTML, screenshot, and console are the source of truth — not “it looks fine on my machine.”
JavaScript-SEO gotchas — cheat sheet
| Thing | What actually happens |
|---|---|
robots.txt blocks your JS/CSS | Google won’t render from blocked files — can break the whole page |
Raw index + JS-injected noindex | Google obeys the most restrictive → noindex wins |
Link as onclick on a <div> | Not discoverable — must be <a href> |
| Content loads on scroll/click | Not loaded — Google doesn’t interact; use viewport lazy-load |
URL fragment (#page=2) for pagination | Ignored — use a real unique URL |
Client-side 404 with 200 status | Soft-404 risk — return real 404 or noindex |
| Infinite scroll at a tall viewport | Can merge two URLs into one indexed page — paginate + block loader if needed |
Which rendering mode? (dependency on rendering succeeding)
| Mode | Dependency |
|---|---|
| Static / prerender (SSG) | Lowest — content is already in the HTML |
| Server-side rendering (SSR) | Low — content is in the HTML per request |
| Hydration (isomorphic) | Low — content lands in the DOM quickly |
| Full client-side rendering (CSR) | Highest — content only exists after rendering succeeds |
| Dynamic rendering | Workaround only — Google calls this a stopgap, not a fix |
This isn’t a guarantee of outcome — SSR/SSG/hydration still need to render correctly and pass every other check in this article. Full breakdown (ISR, edge, streaming, dynamic rendering, and the trade-offs) on the rendering page.
See what Googlebot sees
Rendering bugs hide in the gap between the raw HTML (what the server sends) and the rendered HTML (what exists after JS runs). A few quick command-line checks before you reach for a full crawler.
Fetch the raw HTML (what comes back before any JS runs)
macOS / Linux:
# Raw HTML as the server sends it — this is the "first fetch"
curl -sL -A "Mozilla/5.0 (compatible; Googlebot/2.1; +http://www.google.com/bot.html)" \
https://example.com/page/ -o raw.html
# Is your important text actually in the raw HTML? (empty result = JS-dependent)
grep -o "Your headline text" raw.htmlWindows (PowerShell):
$ua = "Mozilla/5.0 (compatible; Googlebot/2.1; +http://www.google.com/bot.html)"
Invoke-WebRequest -Uri "https://example.com/page/" -UserAgent $ua -OutFile raw.html
Select-String -Path raw.html -Pattern "Your headline text"If the text is missing from raw.html but visible in your browser, it’s being added
by JavaScript — so it depends on rendering. (For the rendered HTML, use URL
Inspection’s “View Crawled Page → rendered HTML,” or a headless-Chrome crawler — a
plain curl can’t run JS.)
Confirm you aren’t blocking JS/CSS in robots.txt
macOS / Linux:
curl -sL https://example.com/robots.txt | grep -iE "disallow.*\.(js|css)|Disallow:\s*/(_next|static|assets|dist)"Windows (PowerShell):
(Invoke-WebRequest "https://example.com/robots.txt").Content |
Select-String -Pattern "Disallow.*\.(js|css)","Disallow:\s*/(_next|static|assets|dist)"A Disallow that matches your JavaScript or CSS means Google can’t render the page
properly — almost always a mistake. (URL Inspection lists blocked page resources too;
the grep just catches the obvious ones fast.)
Patrick's relevant free tools
- Googlebot Verifier — Check whether an IP claiming to be Googlebot, Bingbot, GPTBot, ClaudeBot, or another crawler is genuine — published IP ranges plus forward-confirmed reverse DNS, with the real network owner named for spoofers. IPs are checked in memory and never stored.
- Log File Analyzer — Drop a server access log and see crawl budget by bot and section, status-code waste, an AI-vs-search breakdown, and a spoofer report that names impostors faking a crawler user-agent. Parses nginx, Apache, IIS/W3C, and JSON logs entirely in your browser — nothing is uploaded.
Tools for debugging JavaScript SEO
See the difference between raw HTML and what Google renders with Render Gap:
- Paste the full URL of the page you want to test — a JavaScript-heavy template page shows the most.
- Clear the anti-abuse check and press Test page; it fetches the raw HTML first and only renders in headless Chrome when that HTML looks like an empty shell.
- Read the colour-coded verdict, then scan the Initial HTML vs Rendered DOM table for rows flagged changed.
- Switch to the Raw vs rendered diff tab to see line-by-line what JavaScript added or removed.
- URL Inspection (Google Search Console) — the source of truth. Run a live test, then view the rendered HTML, the screenshot, the page resources (what loaded vs. what was blocked), and JavaScript console messages.
- Rich Results Test — a fast way to check rendered HTML and structured data for a URL without verifying the site.
- Chrome DevTools — compare View Source (raw HTML) with the Elements panel (rendered DOM); the Console surfaces JS errors that can blank out content.
- JavaScript-rendering crawlers — Ahrefs Site Audit and Screaming Frog SEO Spider (JS-rendering mode) execute JS so you can diff raw vs. rendered at scale.
- View-rendered-source tools — browser extensions that show the rendered DOM side-by-side with raw HTML for quick spot checks.
- Server log analysis — confirm Googlebot is actually fetching your JS/CSS resources (see log file analysis).
Prompts for JavaScript SEO diagnosis
Compare raw and rendered HTML
Paste the raw response and the rendered DOM for the same URL. Remove customer data and tokens first.
Act as a technical SEO reviewer. Compare RAW_HTML and RENDERED_HTML below. Report only
meaningful differences in title, meta robots, canonical, headings, body copy,
structured data, and crawlable <a href> links. For each difference, label its likely
indexing impact, show the exact conflicting snippets, and give a verification step.
Do not infer content that is not present.
RAW_HTML:
[paste]
RENDERED_HTML:
[paste]Triage a route sample
Review this CSV of JavaScript routes with columns URL, HTTP_STATUS, RAW_TITLE,
RENDERED_TITLE, RAW_CANONICAL, RENDERED_CANONICAL, RAW_WORDS, RENDERED_WORDS. Group
failures into shared-shell duplicates, restrictive-directive conflicts, soft 404s,
and likely render timeouts. Rank groups by affected URL count. Return the exact rows
that support each conclusion and a test to confirm it; do not invent thresholds.
[paste CSV] Validate a JavaScript SEO change
Prove route content exists before JavaScript
Test to run: Fetch representative routes with curl and inspect the response
body. Expected result: Each response contains its unique title, primary heading,
copy, and crawlable links. Failure interpretation: The deployment still serves a
shared app shell. Monitoring window: Immediate. Rollback trigger: A formerly
server-visible route becomes dependent on rendering.
Prove directives agree across processing stages
Test to run: Compare raw HTML with URL Inspection’s rendered HTML for robots and
canonical tags. Expected result: One intended directive set appears in both, with
no more-restrictive value in the raw shell. Failure interpretation: JavaScript is
trying to overwrite an indexing signal too late. Monitoring window: Immediate in
local rendering; after recrawl in Search Console. Rollback trigger: noindex or
an incorrect canonical appears at either stage.
Prove links remain crawlable
Test to run: Disable JavaScript and inspect navigation to representative routes.
Expected result: Destinations remain in real anchor href attributes. Failure
interpretation: Client handlers, not links, own discovery. Monitoring window:
Immediate. Rollback trigger: Important routes disappear from the link graph when
scripts fail.
Resources worth your time
My related writing
- JavaScript SEO: A Definitive Guide — my full guide to rendering, DOM parity, the most-restrictive-directive rule, infinite scroll, and the two-pages-as-one problem. This article is the condensed, source-linked version.
- The Beginner’s Guide to Technical SEO — where JavaScript SEO fits in the bigger picture.
My speaking
- How Search Works (SlideShare) — my walkthrough of crawling, rendering, indexing, and ranking. (My standing disclaimer applies: “This is my understanding of systems… not going to be 100% complete or accurate.”)
From others
- r/TechSEO — the community for debugging render/index problems.
- web.dev — Rendering on the Web — the canonical explainer of rendering trade-offs from the Chrome team.
- Google Search Central — JavaScript SEO — official primary-source docs on the three-phase process, crawlable links, and testing rendered HTML.
- Onely — JavaScript SEO hub — deep technical posts on rendering, two-waves indexing, and JS SEO auditing from a specialist agency.
- Martin Splitt’s JavaScript SEO playlist — the official Google video series walking through each JS SEO concept, produced by Google’s web ecosystem team.
- Search Engine Journal — JavaScript SEO coverage — industry news and practitioner guides on JS rendering issues as they emerge.
Podcasts
- Search Off the Record (Google Search Relations) — Martin Splitt, John Mueller, and Gary Illyes regularly cover JavaScript SEO and rendering from the inside. Listen
Videos
- Google Search Central (YouTube) — Martin Splitt’s JavaScript SEO series is the best official video walkthrough of how Google handles your JS. Channel
Stats worth citing
- Current official framing: no fixed delay. Google’s own documentation (updated
2026-03-04) says a crawled
200page “may stay on this queue for a few seconds, but it can take longer than that” — there’s no published fixed delay or timeout, and pages that return a non-200 status or start out withnoindexmay skip rendering entirely. Jump to quote - Historical data point (dated) — ~5 second median render delay. In earlier conference remarks, Google staff (Martin Splitt and Tom Greenaway) described pages reaching the renderer at a median of ~5 seconds, with the 90th percentile in minutes — not the “weeks” the old fear implied. I cite this in my JavaScript SEO guide. Treat it as a historical data point from that talk, not a current published metric — Google hasn’t republished it as an ongoing figure, and the queue-timing quote above is the current official framing.
- “Two waves of indexing” is fading, per Martin Splitt (2019 remarks). In an August 2019 conversation with John Mueller, Splitt said two-wave indexing “play[s] less and less of a role” as rendering gets cheaper and crawling, rendering, and indexing converge — with no timeline given for when it might stop entirely. Coverage This is Splitt’s characterization from that specific conversation, not a dated, citable Google spec — use it as directional context, not a current guarantee either way.
Make rendering an architecture decision before launch: if revenue pages depend on JavaScript for primary content or links, verify what search engines receive instead of assuming the browser experience is enough.
- Client-side rendering, interaction-gated content, and nonstandard links are structural risks that cost more to correct after launch.
- Raw-versus-rendered parity testing shows whether important content, links, and status signals survive the crawl, render, and index process.
- Server-rendered or static primary content with JavaScript used only for enhancement may require no special remediation.
A short, template-level diagnostic before a build or replatform can prevent later re-architecture and focus spending on the routes with organic traffic at risk.
Risk if ignored: Search engines may miss primary content, interaction-gated elements, or internal links, leaving revenue pages under-indexed even though they work for users in a browser.
Ask your team: What do our top revenue templates return before JavaScript runs, and have we verified their content and links in rendered output before release?
Google processes JavaScript apps through crawling, rendering, and indexing.
Evidence for this claim Google processes JavaScript web apps in three main phases: crawling, rendering, and indexing; without rendering, Google might not see JavaScript-provided content. Scope: Google Search's processing of JavaScript pages; successful rendering does not guarantee indexing or ranking. Confidence: high · Verified: Google Search Central: Understand the JavaScript SEO basics It generally crawls links when they
are anchors with href attributes. Evidence for this claim Google can reliably discover links only when they are HTML anchor elements with an href attribute. Scope: Link discovery by Google Search; this does not claim that every discovered URL will be crawled or indexed. Confidence: high · Verified: Google Search Central: Make your links crawlable
Google describes server-side rendering or pre-rendering as a good idea for users and
crawlers. Evidence for this claim Google describes server-side rendering or pre-rendering as a good idea because it makes a website faster for users and crawlers. Scope: Google Search guidance for JavaScript sites; the source does not prescribe one framework or guarantee indexing. Confidence: high · Verified: Google Search Central: Understand the JavaScript SEO basics Google Search also
does not interact with a page to trigger content. Evidence for this claim Google Search does not interact with a page, so lazy-loaded content should load when it becomes visible in the viewport rather than requiring user interaction. Scope: Google Search's documented rendering behavior for lazy-loaded content; other crawlers can behave differently. Confidence: high · Verified: Google Search Central: Fix lazy-loaded content
JavaScript SEO
Making sure search engines can crawl, render, and index content that depends on JavaScript.
Related: Rendering, Hydration
JavaScript SEO
Ensuring crawlers can access content that only appears after scripts run. Compare raw vs. rendered HTML, keep links as real anchors, and prefer SSR/prerendering for content that must rank.
Related: Rendering, Hydration
Build-time retrieval analysis plus live signals for this exact article. The automatic chunk report includes a deterministic readiness score and is ready without a model download.
Search Console
sampleGA4 traffic (28d)
sampleCloudflare traffic (7d)
sampledCrUX field data (28d, phone)
sampleGoogle NLP entities
localChangelog
Updated Jul 27, 2026.
Editorial summary and recorded change details.Summary
Added a raw-versus-rendered delta chart to make JavaScript testing outcomes concrete.
Change details
-
Added an illustrative element-count comparison to the Advanced testing section.
Full comparison unavailable — no prior snapshot was archived for this revision.
Updated Jul 18, 2026.
Editorial summary and recorded change details.Summary
Hedged the historical ~5-second render-median and 'two waves' stats as dated evidence, added Google's current no-fixed-delay render-queue guidance and its noindex-skips-rendering statement, scoped the most-restrictive-directive rule to robots meta tags, and softened absolute tall-viewport and rendering-mode risk claims into testable, dependency-based framing.
Change details
-
Added Google's current render-queue timing quote ('may stay on this queue for a few seconds, but it can take longer than that') and its noindex-skips-rendering statement, both with source links.
-
Reframed the ~5-second median render delay and Martin Splitt's 'two waves' remarks as dated historical evidence (2019 conversation) rather than current universal scheduling facts.
-
Scoped the 'most restrictive directive' rule to robots meta tags specifically (per Patrick's own sourced quote) and noted canonical-tag reconciliation isn't documented the same way.
-
Removed absolute 'very tall viewport' and 'low-risk'/'guaranteed' rendering-mode framing in favor of testable, no-exact-size and dependency-based language; date-gated dynamic rendering as Google's current (Dec 2025) workaround guidance.
Full comparison unavailable — no prior snapshot was archived for this revision.