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 SEOMaking sure search engines can crawl, render, and index content that depends on JavaScript. 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 indexStoring a crawled page in the search index so it can appear in results. Crawled is not the same as indexed — Google selects what to keep, and indexing isn't guaranteed. 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 renderingTurning HTML, CSS, and JavaScript into the final visual page and DOM. 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.
Expose navigation as real links
HTMLBefore
removed
<nav aria-label="Product categories">
Removed line.
<button onclick="goTo('/shoes')">Shoes</button>
Removed line.
<button onclick="goTo('/boots')">Boots</button>
</nav>
Fixed
added
<nav aria-label="Product categories">
Added line.
<a href="/shoes/">Shoes</a>
Added line.
<a href="/boots/">Boots</a>
</nav>
- Replaced script-dependent navigation controls with crawlable anchors.
- Kept the navigation label and descriptive link text.
The simple checklist
- Compare your raw HTML (right-click → View Source) with the rendered HTML (Google Search ConsoleA free Google service that reports how a site performs in Google Search and surfaces problems with how Google crawls, indexes, and serves it. It's first-party data straight from Google — but you don't need it to appear in results.’s URL Inspection toolA Google Search Console feature that reports how Google sees one specific URL on a property you own. By default it shows the last-indexed snapshot; a separate \"Test live URL\" mode fetches the current version.). 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 renderingTurning HTML, CSS, and JavaScript into the final visual page and DOM. 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 renderingTurning HTML, CSS, and JavaScript into the final visual page and DOM. setup to choose, see the renderingTurning HTML, CSS, and JavaScript into the final visual page and DOM. 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 renderingTurning HTML, CSS, and JavaScript into the final visual page and DOM. before JavaScript can remove it. Combine directives that were actually processed, but do not assume a universal raw/rendered winner. Watch infinite scrollInfinite scroll is a loading pattern where content appears automatically as a user scrolls, instead of via numbered pages. Because Googlebot doesn't scroll or click, indexable infinite scroll needs real per-chunk URLs (paginated loading) that update via the History API — otherwise deep content may never be crawled, or worse, get merged into the wrong page. especially: a tall render viewport can trigger the loader and merge two URLs into one indexedStoring a crawled page in the search index so it can appear in results. Crawled is not the same as indexed — Google selects what to keep, and indexing isn't guaranteed. page. For the renderer internals and which renderingTurning HTML, CSS, and JavaScript into the final visual page and DOM. mode to pick, see renderingTurning HTML, CSS, and JavaScript into the final visual page and DOM..
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. CrawlingCrawling is how search engines use automated bots (like Googlebot and Bingbot) to discover URLs and download pages. A page has to be crawlable to be indexed, but crawling on its own isn't a ranking factor. 2. Rendering 3. IndexingStoring a crawled page in the search index so it can appear in results. Crawled is not the same as indexed — Google selects what to keep, and indexing isn't guaranteed..” 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, cachingCaching stores a copy of a page or resource — in a browser, a CDN edge node, or a search crawler's own cache — so it can be served again without regenerating or re-downloading it. It isn't a direct ranking factor, but it feeds page speed and crawl efficiency., the “two waves” myth — live on the renderingTurning HTML, CSS, and JavaScript into the final visual page and DOM. 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.
GooglebotGooglebot is Google's web crawler — the software that fetches pages so Google can index and rank them. It comes in two variants, Googlebot Smartphone (primary, under mobile-first indexing) and Googlebot Desktop, and runs an evergreen Chromium renderer. 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 InspectionA Google Search Console feature that reports how Google sees one specific URL on a property you own. By default it shows the last-indexed snapshot; a separate \"Test live URL\" mode fetches the current version.’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 scrollInfinite scroll is a loading pattern where content appears automatically as a user scrolls, instead of via numbered pages. Because Googlebot doesn't scroll or click, indexable infinite scroll needs real per-chunk URLs (paginated loading) that update via the History API — otherwise deep content may never be crawled, or worse, get merged into the wrong page. 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> paginationPagination splits a large set of content — product listings, blog archives, search results — across multiple sequentially numbered URLs. For SEO, each paginated page should be crawlable, indexable, and self-canonical; Google no longer uses rel=prev/next, but Bing still does. 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 codeAn HTTP status code is the three-digit number a server returns with every response to tell a browser or crawler what happened to its request — success, redirect, client error, or server error. For SEO the code matters as much as the content: it tells Google and Bing whether to index a page, follow a redirect, retry later, or drop the URL from the index., so a
“not found” view can still return 200. Google may classify that response as a
soft 404A soft 404 is a URL that returns a success status code (usually 200 OK) even though the page is empty, missing, or shows a 'not found' message. It isn't a status code a server sends — it's a label search engines apply after comparing the response code against the rendered content, and they treat the page like a 404 for indexing. 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-crawlingCrawling is how search engines use automated bots (like Googlebot and Bingbot) to discover URLs and download pages. A page has to be crawlable to be indexed, but crawling on its own isn't a ranking factor. scheme has been deprecated since 2015, so
you can’t rely on URL fragments to work with GooglebotGooglebot is Google's web crawler — the software that fetches pages so Google can index and rank them. It comes in two variants, Googlebot Smartphone (primary, under mobile-first indexing) and Googlebot Desktop, and runs an evergreen Chromium renderer..”
A client-side redirectA redirect sends browsers and crawlers from a requested URL to a different one. An HTTP redirect specifically is a 3xx status code paired with a Location header; meta refresh and JavaScript redirects achieve a similar navigation without being a 3xx response themselves. Permanent redirects (301/308) are Google's signal the target should be canonical; temporary ones (302/303/307) aren't. 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
noindexNoindex is a directive that tells search engines to keep a page out of their index, so it won't appear in search results. It works only on pages a crawler can actually fetch — a page blocked in robots.txt can never be noindexed. tag, it may skip rendering and JavaScript execution, which means using
JavaScript to change or remove the robots meta tagThe robots meta tag is an HTML element in a page's head — <meta name=\"robots\" content=\"noindex\"> — that tells search engines how to index and serve that page. It's crawl-then-obey: a page blocked in robots.txt is never fetched, so the tag is never seen. 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 hydrationActivating server-rendered HTML in the browser by attaching JavaScript handlers. 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 renderingTurning HTML, CSS, and JavaScript into the final visual page and DOM. page. Google separately describes server-side rendering or pre-rendering as a good idea for users and crawlersA crawler — also called a spider or bot — is an automated program that fetches web pages, extracts their links, and queues new URLs to visit. Search engines use crawlers to discover and download content for their index.. 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 ConsoleGoogle's free tool for monitoring crawling, indexing, and search performance. 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 ResultsRich results (formerly 'rich snippets') are enhanced search listings — stars, images, prices, breadcrumbs, video thumbnails, and more — that Google and Bing build from structured data. They're a display feature, not a ranking factor, and eligibility never guarantees they'll show. 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.
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 CMSA content management system that separates the content repository from the presentation layer, delivering content via API to any front-end framework rather than rendering HTML server-side itself. It doesn't specify the rendering mode, hosting, cache, preview security, or publishing workflow — those are separate decisions. — how decoupled frontends affect crawling, rendering, metadata, sitemapsA sitemap is a file that lists the pages, images, videos, and other files on your site so search engines can discover them. It helps discovery, but submitting a sitemap doesn't guarantee crawling or indexing., and canonical tagsA rel=\"canonical\" annotation — in the HTML <head> or an HTTP Link header — that tells search engines which URL is the preferred version of duplicate or near-duplicate content.; which rendering mode to pick; and the headless-specific failure modes you need to know.
Framework-specific guides
- React SEOReact SEO is the practice of making React apps crawlable and indexable. React renders client-side by default, so the raw HTML is near-empty until JavaScript runs — SSR or SSG puts the content back in the initial response where crawlers (and AI bots) can reliably see it. — why CSR-first React creates indexing risk, how Google renders React apps, React Router and History API, react-helmet-async for meta tagsMeta tags are HTML elements in a page's head that pass metadata about the page to search engines and browsers. For SEO only a few matter — the title element, the meta description, and the robots meta tag — while meta keywords and most others are ignored., and when to reach for Next.js.
- Angular SEOAngular SEO is the practice of making Angular single-page apps crawlable, renderable, and indexable — chiefly by serving real HTML through server-side rendering (@angular/ssr) or prerendering instead of relying on client-side rendering, plus managing titles, meta tags, and clean URLs. — 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 SEONext.js SEO is the set of practices and built-in features that make a Next.js site crawlable, indexable, and rankable — rendering mode (SSG/SSR/ISR/Server Components), the App Router Metadata API, sitemap.ts/robots.ts conventions, next/image, and next/link. — 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 SEONuxt SEO is the practice of optimizing Nuxt.js (Vue's meta-framework) apps so search engines and AI crawlers can crawl, render, and index them. Nuxt's big advantage is that it ships server-side rendering by default, so pages arrive as fully-formed HTML instead of an empty Vue SPA shell. — SSR by default,
useSeoMeta(), Nuxt’s rendering modes, the@nuxtjs/seomodule ecosystem, and how Nuxt compares to plain Vue for indexability. - Vue SEOVue SEO is the practice of making web apps built with Vue.js crawlable, renderable, and indexable. Because Vue 3 defaults to client-side rendering, the content isn't in the raw HTML — so router mode, head management, and a rendering strategy (CSR, prerendering, SSR, or SSG) all matter. — 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 SEOSvelte SEO is the practice of making sure search engines and AI crawlers can see content built with Svelte. Plain Svelte is client-side rendered (a blank shell), so the key is to use SvelteKit, which renders pages on the server by default. — Svelte vs SvelteKit, SSR by default in SvelteKit,
<svelte:head>, theadapter-static+ssr: falsetrap, adapter choices, and AI crawler implications. - Astro SEOAstro SEO is the practice of optimizing sites built with the Astro web framework for search. Astro prerenders to static HTML by default, so content is in the raw HTML on first crawl for that route — no rendering queue — which makes it a strong starting point for SEO, though the default can be overridden per route and doesn't guarantee crawlability or rankings on its own. — zero-JS by default, islands architecture,
@astrojs/sitemap,astro:assets, View Transitions and History API, Server Islands fallback behavior, and Astro’s Core Web VitalsGoogle's three real-user UX metrics — LCP (loading), INP (responsiveness), and CLS (visual stability) — used by Google's ranking systems, with no official weight attached, measured on field data. 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 renderingTurning HTML, CSS, and JavaScript into the final visual page and DOM. 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/indexStoring a crawled page in the search index so it can appear in results. Crawled is not the same as indexed — Google selects what to keep, and indexing isn't guaranteed. 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 scrollInfinite scroll is a loading pattern where content appears automatically as a user scrolls, instead of via numbered pages. Because Googlebot doesn't scroll or click, indexable infinite scroll needs real per-chunk URLs (paginated loading) that update via the History API — otherwise deep content may never be crawled, or worse, get merged into the wrong page. can merge two pages into one — GooglebotGooglebot is Google's web crawler — the software that fetches pages so Google can index and rank them. It comes in two variants, Googlebot Smartphone (primary, under mobile-first indexing) and Googlebot Desktop, and runs an evergreen Chromium renderer. 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-crawlingCrawling is how search engines use automated bots (like Googlebot and Bingbot) to discover URLs and download pages. A page has to be crawlable to be indexed, but crawling on its own isn't a ranking factor. deprecated 2015). - Don’t block JS/CSS in robots.txtA plain-text file at the root of a host that tells crawlers which URLs they may and may not request. It controls crawling, not indexing — a blocked URL can still be indexed if it's linked from elsewhere. — Google won’t render from blocked files.
noindexcan block its own removal — Google may skip renderingTurning HTML, CSS, and JavaScript into the final visual page and DOM. when it sees an initialnoindex, so JS meant to remove it may never run.- DOM parityWhether the rendered DOM matches what you expect the raw HTML to become. + stage order (robots meta tagsThe robots meta tag is an HTML element in a page's head — <meta name=\"robots\" content=\"noindex\"> — that tells search engines how to index and serve that page. It's crawl-then-obey: a page blocked in robots.txt is never fetched, so the tag is never seen.) — 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/prerenderThe Speculation Rules API is a Chromium browser API (Chrome/Edge 109+) that lets a site tell the browser which same-site pages to prefetch (download the HTML document) or prerender (fully load and render in an invisible tab) before a visitor clicks — so the next navigation can be near-instant. It's a browser-side performance feature for real users, not a crawling, indexing, or ranking signal./hydrationActivating server-rendered HTML in the browser by attaching JavaScript handlers. 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 ResultsRich results (formerly 'rich snippets') are enhanced search listings — stars, images, prices, breadcrumbs, video thumbnails, and more — that Google and Bing build from structured data. They're a display feature, not a ranking factor, and eligibility never guarantees they'll show. Test, and a JS-rendering crawlerA crawler — also called a spider or bot — is an automated program that fetches web pages, extracts their links, and queues new URLs to visit. Search engines use crawlers to discover and download content for their index..
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 scrollInfinite scroll is a loading pattern where content appears automatically as a user scrolls, instead of via numbered pages. Because Googlebot doesn't scroll or click, indexable infinite scroll needs real per-chunk URLs (paginated loading) that update via the History API — otherwise deep content may never be crawled, or worse, get merged into the wrong page..
- 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 renderingTurning HTML, CSS, and JavaScript into the final visual page and DOM. sits in crawl → indexStoring a crawled page in the search index so it can appear in results. Crawled is not the same as indexed — Google selects what to keep, and indexing isn't guaranteed. → serve.
Bing / Microsoft
- bingbot Series: JavaScript, Dynamic Rendering, and Cloaking. Oh My! — Bing’s take on renderingTurning HTML, CSS, and JavaScript into the final visual page and DOM. 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 — renderingTurning HTML, CSS, and JavaScript into the final visual page and DOM. & links
- “Google processes JavaScript web apps in three main phases: 1. CrawlingCrawling is how search engines use automated bots (like Googlebot and Bingbot) to discover URLs and download pages. A page has to be crawlable to be indexed, but crawling on its own isn't a ranking factor. 2. RenderingTurning HTML, CSS, and JavaScript into the final visual page and DOM. 3. IndexingStoring a crawled page in the search index so it can appear in results. Crawled is not the same as indexed — Google selects what to keep, and indexing isn't guaranteed..” 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 tagNoindex is a directive that tells search engines to keep a page out of their index, so it won't appear in search results. It works only on pages a crawler can actually fetch — a page blocked in robots.txt can never be noindexed., it may skip rendering and JavaScript execution, which means using JavaScript to change or remove the robots meta tagThe robots meta tag is an HTML element in a page's head — <meta name=\"robots\" content=\"noindex\"> — that tells search engines how to index and serve that page. It's crawl-then-obey: a page blocked in robots.txt is never fetched, so the tag is never seen. from noindexNoindex is a directive that tells search engines to keep a page out of their index, so it won't appear in search results. It works only on pages a crawler can actually fetch — a page blocked in robots.txt can never be noindexed. may not work as expected.” Jump to quote
Google — interaction, lazy-load & infinite scrollInfinite scroll is a loading pattern where content appears automatically as a user scrolls, instead of via numbered pages. Because Googlebot doesn't scroll or click, indexable infinite scroll needs real per-chunk URLs (paginated loading) that update via the History API — otherwise deep content may never be crawled, or worse, get merged into the wrong page.
- “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 scrollInfinite scroll is a loading pattern where content appears automatically as a user scrolls, instead of via numbered pages. Because Googlebot doesn't scroll or click, indexable infinite scroll needs real per-chunk URLs (paginated loading) that update via the History API — otherwise deep content may never be crawled, or worse, get merged into the wrong page.. 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 indexStoring a crawled page in the search index so it can appear in results. Crawled is not the same as indexed — Google selects what to keep, and indexing isn't guaranteed. your JS-dependent content:
- Important content appears in the rendered HTML (check URL InspectionA Google Search Console feature that reports how Google sees one specific URL on a property you own. By default it shows the last-indexed snapshot; a separate \"Test live URL\" mode fetches the current version., 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 scrollInfinite scroll is a loading pattern where content appears automatically as a user scrolls, instead of via numbered pages. Because Googlebot doesn't scroll or click, indexable infinite scroll needs real per-chunk URLs (paginated loading) that update via the History API — otherwise deep content may never be crawled, or worse, get merged into the wrong page. 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 / prerenderingTurning HTML, CSS, and JavaScript into the final visual page and DOM., not full CSR (see the renderingTurning HTML, CSS, and JavaScript into the final visual page and DOM. 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 InspectionA Google Search Console feature that reports how Google sees one specific URL on a property you own. By default it shows the last-indexed snapshot; a separate \"Test live URL\" mode fetches the current version. shows the rendered DOM. IndexStoring a crawled page in the search index so it can appear in results. Crawled is not the same as indexed — Google selects what to keep, and indexing isn't guaranteed. 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 crawlingCrawling is how search engines use automated bots (like Googlebot and Bingbot) to discover URLs and download pages. A page has to be crawlable to be indexed, but crawling on its own isn't a ranking factor..
4. Design for a botA crawler — also called a spider or bot — is an automated program that fetches web pages, extracts their links, and queues new URLs to visit. Search engines use crawlers to discover and download content for their index. 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 scrollInfinite scroll is a loading pattern where content appears automatically as a user scrolls, instead of via numbered pages. Because Googlebot doesn't scroll or click, indexable infinite scroll needs real per-chunk URLs (paginated loading) that update via the History API — otherwise deep content may never be crawled, or worse, get merged into the wrong page.. GooglebotGooglebot is Google's web crawler — the software that fetches pages so Google can index and rank them. It comes in two variants, Googlebot Smartphone (primary, under mobile-first indexing) and Googlebot Desktop, and runs an evergreen Chromium renderer. 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 GooglebotGooglebot is Google's web crawler — the software that fetches pages so Google can index and rank them. It comes in two variants, Googlebot Smartphone (primary, under mobile-first indexing) and Googlebot Desktop, and runs an evergreen Chromium renderer.. 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 paginationPagination splits a large set of content — product listings, blog archives, search results — across multiple sequentially numbered URLs. For SEO, each paginated page should be crawlable, indexable, and self-canonical; Google no longer uses rel=prev/next, but Bing still does. | Ignored — use a real unique URL |
Client-side 404 with 200 status | Soft-404 risk — return real 404 or noindex |
| Infinite scrollInfinite scroll is a loading pattern where content appears automatically as a user scrolls, instead of via numbered pages. Because Googlebot doesn't scroll or click, indexable infinite scroll needs real per-chunk URLs (paginated loading) that update via the History API — otherwise deep content may never be crawled, or worse, get merged into the wrong page. at a tall viewport | Can merge two URLs into one indexedStoring a crawled page in the search index so it can appear in results. Crawled is not the same as indexed — Google selects what to keep, and indexing isn't guaranteed. page — paginate + block loader if needed |
Which rendering mode? (dependency on rendering succeeding)
| Mode | Dependency |
|---|---|
| Static / prerenderThe Speculation Rules API is a Chromium browser API (Chrome/Edge 109+) that lets a site tell the browser which same-site pages to prefetch (download the HTML document) or prerender (fully load and render in an invisible tab) before a visitor clicks — so the next navigation can be near-instant. It's a browser-side performance feature for real users, not a crawling, indexing, or ranking signal. (SSG) | Lowest — content is already in the HTML |
| Server-side renderingTurning HTML, CSS, and JavaScript into the final visual page and DOM. (SSR) | Low — content is in the HTML per request |
| HydrationActivating server-rendered HTML in the browser by attaching JavaScript handlers. (isomorphic) | Low — content lands in the DOM quickly |
| Full client-side renderingTurning HTML, CSS, and JavaScript into the final visual page and DOM. (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 renderingTurning HTML, CSS, and JavaScript into the final visual page and DOM. page.
See what Googlebot sees
RenderingTurning HTML, CSS, and JavaScript into the final visual page and DOM. 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 crawlerA crawler — also called a spider or bot — is an automated program that fetches web pages, extracts their links, and queues new URLs to visit. Search engines use crawlers to discover and download content for their index..
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 renderingTurning HTML, CSS, and JavaScript into the final visual page and DOM.. (For the rendered HTML, use URL
Inspection’s “View Crawled Page → rendered HTML,” or a headless-Chrome crawlerA crawler — also called a spider or bot — is an automated program that fetches web pages, extracts their links, and queues new URLs to visit. Search engines use crawlers to discover and download content for their index. — a
plain curl can’t run JS.)
Confirm you aren’t blocking JS/CSS in robots.txtA plain-text file at the root of a host that tells crawlers which URLs they may and may not request. It controls crawling, not indexing — a blocked URL can still be indexed if it's linked from elsewhere.
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 InspectionA Google Search Console feature that reports how Google sees one specific URL on a property you own. By default it shows the last-indexed snapshot; a separate \"Test live URL\" mode fetches the current version. 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 InspectionA Google Search Console feature that reports how Google sees one specific URL on a property you own. By default it shows the last-indexed snapshot; a separate \"Test live URL\" mode fetches the current version. (Google Search ConsoleA free Google service that reports how a site performs in Google Search and surfaces problems with how Google crawls, indexes, and serves it. It's first-party data straight from Google — but you don't need it to appear in results.) — 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 ResultsRich results (formerly 'rich snippets') are enhanced search listings — stars, images, prices, breadcrumbs, video thumbnails, and more — that Google and Bing build from structured data. They're a display feature, not a ranking factor, and eligibility never guarantees they'll show. Test — a fast way to check rendered HTML and structured dataStructured data is a standardized way of labeling page content (using the schema.org vocabulary in JSON-LD, Microdata, or RDFa) so search engines can understand its meaning. It's not a direct ranking factor — its value is rich results and entity understanding. 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-renderingTurning HTML, CSS, and JavaScript into the final visual page and DOM. crawlersA crawler — also called a spider or bot — is an automated program that fetches web pages, extracts their links, and queues new URLs to visit. Search engines use crawlers to discover and download content for their index. — Ahrefs Site Audit and Screaming Frog SEO Spider (JS-renderingTurning HTML, CSS, and JavaScript into the final visual page and DOM. 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 GooglebotGooglebot is Google's web crawler — the software that fetches pages so Google can index and rank them. It comes in two variants, Googlebot Smartphone (primary, under mobile-first indexing) and Googlebot Desktop, and runs an evergreen Chromium renderer. is actually fetching your JS/CSS resources (see log file analysisLog file analysis is reading a web server's raw access logs to see exactly which URLs search engine crawlers actually requested, when, how often, and what status code they got. Unlike crawl tools or Search Console, logs are the unsampled, ground-truth record of what really happened.).
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 tokensA token is the smallest unit of text (or image/audio/video) an LLM processes — roughly 4 characters, or about ¾ of an English word. A context window is the maximum number of tokens (input plus output) a model can hold at once, like its short-term memory. 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 renderingTurning HTML, CSS, and JavaScript into the final visual page and DOM..
Prove directives agree across processing stages
Test to run: Compare raw HTML with URL InspectionA Google Search Console feature that reports how Google sees one specific URL on a property you own. By default it shows the last-indexed snapshot; a separate \"Test live URL\" mode fetches the current version.’s rendered HTML for robots and
canonical tagsA rel=\"canonical\" annotation — in the HTML <head> or an HTTP Link header — that tells search engines which URL is the preferred version of duplicate or near-duplicate content.. 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 indexingStoring a crawled page in the search index so it can appear in results. Crawled is not the same as indexed — Google selects what to keep, and indexing isn't guaranteed. signal too late. Monitoring window: Immediate in
local renderingTurning HTML, CSS, and JavaScript into the final visual page and DOM.; after recrawlCrawl frequency is how often a search engine comes back to re-fetch a page it already knows about. Popular pages that change often get refreshed many times a day; stable pages can go weeks or months between crawls — and you influence it indirectly, not by setting a dial. in Search ConsoleA free Google service that reports how a site performs in Google Search and surfaces problems with how Google crawls, indexes, and serves it. It's first-party data straight from Google — but you don't need it to appear in results.. 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 renderingTurning HTML, CSS, and JavaScript into the final visual page and DOM., DOM parityWhether the rendered DOM matches what you expect the raw HTML to become., the most-restrictive-directive rule, infinite scrollInfinite scroll is a loading pattern where content appears automatically as a user scrolls, instead of via numbered pages. Because Googlebot doesn't scroll or click, indexable infinite scroll needs real per-chunk URLs (paginated loading) that update via the History API — otherwise deep content may never be crawled, or worse, get merged into the wrong page., and the two-pages-as-one problem. This article is the condensed, source-linked version.
- The Beginner’s Guide to Technical SEO — where JavaScript SEOMaking sure search engines can crawl, render, and index content that depends on JavaScript. fits in the bigger picture.
My speaking
- How Search Works (SlideShare) — my walkthrough of crawlingCrawling is how search engines use automated bots (like Googlebot and Bingbot) to discover URLs and download pages. A page has to be crawlable to be indexed, but crawling on its own isn't a ranking factor., renderingTurning HTML, CSS, and JavaScript into the final visual page and DOM., indexingStoring a crawled page in the search index so it can appear in results. Crawled is not the same as indexed — Google selects what to keep, and indexing isn't guaranteed., 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 indexingStoring a crawled page in the search index so it can appear in results. Crawled is not the same as indexed — Google selects what to keep, and indexing isn't guaranteed., 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 SEOMaking sure search engines can crawl, render, and index content that depends on JavaScript. and renderingTurning HTML, CSS, and JavaScript into the final visual page and DOM. from the inside. Listen
Videos
- Google Search Central (YouTube) — Martin Splitt’s JavaScript SEOMaking sure search engines can crawl, render, and index content that depends on JavaScript. 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 renderingTurning HTML, CSS, and JavaScript into the final visual page and DOM. 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 indexingStoring a crawled page in the search index so it can appear in results. Crawled is not the same as indexed — Google selects what to keep, and indexing isn't guaranteed.” is fading, per Martin Splitt (2019 remarks). In an August 2019 conversation with John Mueller, Splitt said two-wave indexingStoring a crawled page in the search index so it can appear in results. Crawled is not the same as indexed — Google selects what to keep, and indexing isn't guaranteed. “play[s] less and less of a role” as renderingTurning HTML, CSS, and JavaScript into the final visual page and DOM. gets cheaper and crawlingCrawling is how search engines use automated bots (like Googlebot and Bingbot) to discover URLs and download pages. A page has to be crawlable to be indexed, but crawling on its own isn't a ranking factor., 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 crawlingCrawling is how search engines use automated bots (like Googlebot and Bingbot) to discover URLs and download pages. A page has to be crawlable to be indexed, but crawling on its own isn't a ranking factor., renderingTurning HTML, CSS, and JavaScript into the final visual page and DOM., and indexingStoring a crawled page in the search index so it can appear in results. Crawled is not the same as indexed — Google selects what to keep, and indexing isn't guaranteed..
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 renderingTurning HTML, CSS, and JavaScript into the final visual page and DOM. or pre-rendering as a good idea for users and
crawlersA crawler — also called a spider or bot — is an automated program that fetches web pages, extracts their links, and queues new URLs to visit. Search engines use crawlers to discover and download content for their index.. 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 crawlersA crawler — also called a spider or bot — is an automated program that fetches web pages, extracts their links, and queues new URLs to visit. Search engines use crawlers to discover and download content for their index. can access content that only appears after scripts run. Compare raw vs. rendered HTML, keep links as real anchors, and prefer SSR/prerenderingTurning HTML, CSS, and JavaScript into the final visual page and DOM. 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
Revision history
Compare the published article with an archived editorial snapshot. Added and removed words are shown only after you open a comparison.
Updated Jul 26, 2026.
Editorial summary and recorded change details.Summary
Added a Before / Fixed HTML comparison for crawlable JavaScript navigation.
Change details
-
Shows how to preserve the same visible navigation while replacing click-handler buttons with ordinary anchor links that expose their destinations in HTML.
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.