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.

First published: Jun 23, 2026 · Last updated: Jul 26, 2026 · Advanced
demand #1 in JavaScript SEO#40 in Technical SEO#54 on the site
1 evidence signal on this page

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.

Explore this topic

Loading local progress…

JavaScript SEO

How rendering, discoverability, and indexation fit together.

View the full learning path
Follow the guided pathStart with the first guide →
  1. 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.
  2. 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.
  3. 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.
  4. 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.

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 raw noindex may 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

Google can run your JavaScript — the real risks are parity, interaction, state, and timing. Source: /technical-seo/javascript-seo/

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 basics

So 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.

Evidence for this claim Google queues crawled pages with a 200 response for rendering; the page may stay in that queue for a few seconds but can take longer, with no published fixed delay or timeout, and non-200 or initial noindex responses may skip rendering. Scope: Google Search's documented render-queue behavior; not a guaranteed or universal timing figure. Confidence: high · Verified: Google Search Central: Understand the JavaScript SEO basics

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.

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.

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

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.

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

Infinite scroll: when two pages get indexed as one

Google's tall render viewport can trigger an infinite-scroll loader and merge two pages into one indexed URL. Source: /technical-seo/javascript-seo/

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:

FieldWhat the raw/rendered comparison can establish
Main content and linksGoogle can use content and real <a href> links produced during rendering if rendering succeeds. Raw availability reduces that dependency.
Title and descriptionGoogle 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 directivesA 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.
CanonicalGoogle’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 redirectJavaScript 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/image and 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/seo module 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>, the adapter-static + ssr: false trap, 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.

Add an expert note

Pin an expert quote

New person? Create their unclaimed profile at /admin/experts/ → Pin a quote first.