42 min read

> "The browser you are reading this on ran the JavaScript, so the page looks finished to you. The only

Prerequisites

  • 1
  • 14

Learning Objectives

  • Explain the rendering problem: how Google crawls raw HTML first and renders JavaScript in a separate, later step, and how that delay and its failure modes put JavaScript-dependent content at risk.
  • Describe client-side rendering and name its specific SEO costs, and contrast it with server-side rendering, static site generation, and dynamic rendering.
  • Advise, at a marketer-to-developer level, on JavaScript frameworks (React, Vue, Angular; Next.js, Nuxt, Gatsby) — knowing that the rendering mode you configure, not the library you chose, decides the SEO outcome.
  • Run a repeatable 'can Google see it?' test using View Source, a disabled-JavaScript check, a site: phrase search, and the rendered HTML in Search Console's URL Inspection.
  • Diagnose the three classic JavaScript-SEO failures — links that aren't real anchors, content that loads only on interaction, and hash-fragment routing — and state the fix for each.
  • Choose the simplest safe path — important content and links present in server-rendered HTML before any JavaScript runs — and justify it honestly against the alternatives.

Chapter 19: JavaScript SEO — When Your Site Is Built with React, Vue, or Angular and Google Can't See Your Content

"The browser you are reading this on ran the JavaScript, so the page looks finished to you. The only question that matters for search is whether Google's renderer ran it too — and whether it did so in time, and without tripping." — constructed line, for this chapter

Overview

Here is a question that has quietly cost businesses millions of dollars in invisible traffic: when someone searches for exactly what your page offers, is your content actually there for Google to read — or does it only come into existence after a browser downloads and runs your JavaScript?

For a traditional website, the question sounds absurd. Of course the content is there; it is right in the HTML. But a large and growing share of the modern web does not work that way. Sites built with JavaScript frameworks — React, Vue, Angular, and their relatives — often ship a nearly empty page: a skeleton with a <div id="root"></div> and a pile of scripts, and the actual headings, paragraphs, product details, and links are assembled in the browser after the page loads. For a human, this is invisible; the browser runs the code and the page appears. For a search engine, it is a genuine and specific problem, because someone has to run that JavaScript before the content exists — and that someone, on Google's side, is a renderer that is powerful, imperfect, sometimes delayed, and occasionally defeated entirely.

We met this idea briefly in Chapter 1, where rendering was one stage of the crawl→render→index→rank→serve pipeline. This chapter is where it becomes a discipline. JavaScript SEO is the practice of making sure that a site whose content depends on JavaScript is still fully seeable, crawlable, and indexable — and, better still, of arranging things so the dependency never puts your visibility at risk in the first place. It is the developer track's deep chapter, but it is written just as much for the marketer who has to walk into a meeting and ask a development team the right four questions, because the single most valuable skill here is not writing code. It is knowing what to demand, and how to verify you got it.

In this chapter, you will learn to:

  • Explain the rendering problem — the separate, later render step, its delay, and its failure modes — in plain terms a developer will respect and a CEO will understand.
  • Name the concrete SEO costs of client-side rendering, and the trade-offs of the alternatives (SSR, SSG, dynamic rendering).
  • Talk about frameworks (React/Vue/Angular; Next.js/Nuxt/Gatsby) at the level that actually matters: not which one, but which rendering mode.
  • Run a "can Google see it?" test on any page, using only free tools, and trust the result.
  • Recognize the three failures that break JavaScript sites again and again — and fix each one.
  • Choose, and defend, the simplest safe path: content in the HTML before the JavaScript runs.

Learning Paths

🔧 Developer: this is your chapter of the book — read every section, and treat §19.3, §19.5, and §19.6 as reference you will return to. 📊 Strategist: you rarely write the code, but you own the diagnosis; master §19.1 (the mechanism), §19.4 (the conversation with developers), and §19.5 (the test), because "is our content JavaScript-dependent, and did we verify it?" belongs in every technical audit you run. 🛒 E-Commerce: you are the most exposed — storefronts, faceted navigation, and "load more" product grids are JavaScript-heavy by default; weight §19.2 and §19.6. 📝 Content Creator and 🏪 Local Business: if you are on WordPress, Squarespace, Shopify, or a similar server-rendered platform, you are mostly safe by default — but read §19.5 and the Strategy File, because mostly is not entirely, and the widgets you bolt on are exactly where the exception hides.


19.1 The rendering problem: the two-wave index, its delay, and its failures

Start with the mechanism, because every practical decision in this chapter follows from it. In Chapter 1 we said Google renders your page — executes its JavaScript, the way a browser would, to see the finished result. That is true, and it is worth repeating that it is a real and impressive capability: since 2019, Googlebot has used an "evergreen" rendering engine based on a current version of Chromium (the open-source project behind Chrome), kept reasonably up to date, so it runs modern JavaScript rather than choking on it. If someone tells you "Google can't read JavaScript," they are working from advice a decade out of date.

But can render and reliably renders your content in time are two different claims, and the gap between them is the whole subject of this chapter. Rendering is expensive. Running a full browser engine on billions of pages costs enormous computing resources, so Google does not render every page the instant it crawls it. Instead, crawling and rendering have historically been decoupled into what Google itself once described as two waves.

THE TWO-WAVE INDEX                                         [schematic — not to scale]

  WAVE 1 (immediate)                          WAVE 2 (later — the render queue)
  ─────────────────────                       ────────────────────────────────────
  Googlebot fetches the RAW HTML  ──▶  Is the content already in the HTML?
  (the server's first response)                     │
                                          ┌─────────┴──────────┐
                                         YES                   NO (needs JavaScript)
                                          │                     │
                                   index it now          put the URL in the
                                   from the HTML          RENDER QUEUE ──▶ run
                                                          headless Chromium ──▶
                                                          index the RENDERED
                                                          content ──— minutes,
                                                          or much longer, later

The two-wave index is that decoupling: in the first wave Google indexes whatever is present in the raw HTML immediately, and content that exists only after JavaScript runs waits in a render queue for a second pass. Google has, more recently, pushed back on the "two waves" framing as an oversimplification, and has said that for most pages the time between crawl and render is now far shorter than it used to be — often minutes or less rather than the days that were common years ago. Take that good news seriously. But notice what it does and does not promise. It says the typical delay shrank. It does not say the delay is zero, it does not say there is no queue, and it does not guarantee any particular page renders promptly. There is a long tail, and your important page can land in it.

🔎 How Search Sees It The component doing the rendering is Google's Web Rendering Service (WRS) — essentially a headless, automated Chromium. Understanding how it differs from the browser on your desk explains most JavaScript SEO bugs at a stroke. The WRS loads your page and runs its JavaScript, but it does not behave like a user: it does not scroll, it does not click, it does not hover, it does not type, it does not accept cookie prompts or log in, and it does not wait indefinitely. It renders the page roughly as it is on arrival, with whatever content the scripts produce on their own within a bounded amount of time and resource. It also renders statelessly — it does not carry localStorage, sessionStorage, or a login session from one page to the next. So the mental test for "will Google see this?" is not "does it appear when I visit the page?" It is "does it appear for a fresh, impatient, non-interacting visitor who never scrolls, clicks, or signs in?" A surprising amount of content fails that test without anyone noticing.

There are three distinct ways the render step hurts you, and it is worth separating them because they have different fixes.

Delay. Content that only exists after rendering is indexed after the render pass, whenever that comes. For an evergreen article, a few minutes' or hours' delay is nothing. For a news publisher racing to rank on a breaking story, or an e-commerce site launching a time-limited sale page, a rendering delay can mean the window closes before Google ever sees the content.

Partial rendering. The WRS renders what it can within its budget. If part of your content depends on a slow database call, a third-party script that is sluggish or fails, or an interaction that never happens, the render can complete with that part simply missing — and Google indexes the incomplete version, none the wiser that anything was supposed to be there.

Failure. If a JavaScript error throws before the content renders, if a critical resource (a script file, an API) is blocked or unreachable, or if the page never signals that it is "done," rendering can fail outright, and Google is left with the empty shell it fetched in wave one.

⚖️ Evidence Check Claim: "Google renders JavaScript, but rendering is delayed and can fail." Where does this sit on our honesty scale? — Confirmed by Google: that Googlebot uses an evergreen Chromium-based renderer (announced 2019); that rendering is a distinct, resource-intensive step separate from crawling; that content requiring rendering can be indexed later than content in the raw HTML; and that blocked resources or script errors can prevent Google from seeing content. All of this is stated in Google's own Search Central documentation and public talks. — Not precisely knowable: the exact delay. Google representatives have described the typical crawl-to-render gap as much shorter than it once was, and have at times cited very short medians, but Google publishes no guarantee, the figure varies enormously by site and page, and the long tail is real. So we treat "rendering is fast enough for most pages, most of the time" as a reasonable working assumption — and we refuse to bet an important page's visibility on it when we can simply put the content in the HTML instead and remove the question entirely.

That last sentence is the thesis of the chapter in miniature, and everything else is detail and justification. The safest content is content that needs no rendering to be seen.


19.2 Client-side rendering and its SEO cost

Now let us name the pattern that creates the risk. Client-side rendering (CSR) is an architecture in which the server sends a minimal HTML shell — often little more than an empty container and a script tag — and the client (the user's browser) does the work of building the page: downloading a JavaScript bundle, running it, fetching data from an application programming interface (API), and constructing the visible content in the browser's Document Object Model (DOM, the live, in-memory representation of the page). The "single-page application" (SPA) — a site that loads once and then rewrites itself as you navigate, without full page reloads — is the classic CSR form, and React, Vue, and Angular in their default configurations all produce it.

WHAT THE SERVER SENDS FOR A CLIENT-RENDERED PAGE          [illustrative]

  <!doctype html>
  <html>
    <head><title>Loading…</title></head>
    <body>
      <div id="root"></div>          ← the content goes HERE, but it is empty on arrival
      <script src="/bundle.js"></script>   ← the browser must download and run this…
    </body>                          ← …and only THEN does the page fill in
  </html>

For a human, the sequence is seamless: the browser downloads bundle.js, runs it, the script calls an API, the data comes back, and the page paints — all in a second or two. For SEO, that same sequence is a chain of dependencies, and every link in the chain is a place where Google can end up with the empty shell. Let us be precise about the costs, because "CSR is bad for SEO" is too blunt to act on.

  • Your content's indexing depends on rendering succeeding, and on time. Everything in §19.1 applies with full force: delay, partial rendering, and outright failure all fall on CSR pages, because for a CSR page the content only exists after rendering. There is no wave-one fallback.
  • Non-Google consumers see nothing. This cost is underrated and growing. Google renders JavaScript; most other machines that read your pages do not, or do so far less reliably. Bing renders some JavaScript but is weaker at it. The scrapers behind social-media link previews (the card that appears when you paste a URL into a message) typically read only the raw HTML — so a CSR page can paste as a blank, titleless preview. And a wave of AI and large-language-model crawlers now reading the web for answer engines frequently do not execute JavaScript at all. A CSR page can be perfectly indexed by Google and still be invisible to half the ecosystem that increasingly decides who gets cited (a theme we return to in Chapter 36).
  • Performance suffers, and performance is a ranking input. Shipping a large JavaScript bundle that must download, parse, and execute before anything useful appears tends to hurt exactly the metrics Google measures as page experience — a slow largest contentful paint (LCP) because the main content paints late, and a sluggish interaction to next paint (INP) because the main thread is busy running your framework. We cover these in Chapter 16; here, note only that CSR and slow Core Web Vitals are frequent companions.
  • Links and metadata can go missing. If your navigation, your internal links, or your <title> and canonical tag are injected by JavaScript, they inherit every rendering risk above. We devote §19.6 to the specific failures.

🚫 SEO Myth: "Google can't read JavaScript, so any React/Vue/Angular site is doomed for SEO." False, and dangerously so, because it leads people to the wrong conclusion. Google does render JavaScript, and thousands of JavaScript-framework sites rank perfectly well. The library is not the problem. A React site that is server-rendered (via Next.js, say) puts its content in the HTML and sails through indexing exactly like a WordPress page. The real risk is not "using React"; it is "shipping content that exists only after client-side rendering, and never verifying Google sees it." The correct takeaway is not "avoid JavaScript frameworks." It is "if you use one, control the rendering mode and test the result." Banning React for SEO reasons is as misguided as trusting it blindly.

What CSR can do is real and worth respecting: it delivers genuinely app-like experiences, instant in-app navigation after the first load, and rich interactivity that a traditional page cannot match. For a logged-in dashboard, an interactive tool, a checkout flow — surfaces that do not need to rank in Google at all — CSR is a fine and often superior choice, precisely because Google does not need to index them. What CSR cannot do is guarantee that public, rankable content is seen promptly and reliably by search engines and the wider web. The skill is knowing which of your surfaces is which, and never letting a page that must rank depend on client-side rendering alone.

🔗 Connection The performance side of this — why a heavy JavaScript bundle hurts LCP and INP, and how to measure it — is Chapter 16 (Core Web Vitals). The reason JavaScript-injected internal links matter so much for how authority moves through a site is Chapter 15 (Site Architecture) and Chapter 22 (how PageRank flows).


19.3 The alternatives: SSR, SSG, and dynamic rendering

If client-side rendering is the risk, the remedies are the ways of getting finished HTML to a crawler without making it run your application first. There are three you need to be able to name and distinguish, plus one umbrella term.

Server-side rendering (SSR). The server runs the framework for each request and sends back fully-formed HTML with the content already in it; the browser then downloads the JavaScript and hydrates the page. Hydration is the step where the framework "attaches" to the already-rendered HTML — wiring up event handlers and making the static markup interactive — without rebuilding it from scratch. The reader gets real content immediately (good for people and crawlers alike), and gets interactivity a moment later. SSR is the natural choice when content is dynamic or personalized and must be fresh on every request — a logged-in feed, a constantly-changing catalog, prices that update by the minute.

Static site generation (SSG). The pages are rendered to plain HTML at build time — once, when you publish — and served as static files thereafter. Every visitor and every crawler gets a complete, pre-built HTML document with zero rendering dependency. SSG is the fastest, cheapest, and most robust option for content that does not change on every request: marketing pages, documentation, blogs, guides. Its limits are equally clear: the content is only as fresh as your last build, so a site with millions of pages or minute-by-minute updates can find rebuilding impractical. (Hybrids exist — "incremental" regeneration that rebuilds individual pages on a schedule or on demand — but the principle is unchanged: the HTML is generated before the request, not in the visitor's browser.)

Dynamic rendering. Here the server detects who is asking — a normal user versus a crawler — and serves client-rendered JavaScript to users but pre-rendered HTML to bots. It was, for a while, Google's own suggested stopgap for sites that could not easily adopt SSR. You should know two things about it. First, Google now treats dynamic rendering as a workaround, not a recommended long-term solution, and its documentation steers teams toward SSR or static/pre-rendered HTML instead; the maintenance burden of a separate bot pipeline, and the risk of the two versions drifting apart, are why. Second, and more important, dynamic rendering lives dangerously close to a line you must never cross.

🚫 SEO Myth: "Dynamic rendering is cloaking, so it's against the rules." Not quite — and the distinction is one every technical SEO must be able to draw cleanly. Cloaking is deliberately showing search engines different content than you show users in order to manipulate rankings — a page that serves keyword-stuffed text to Googlebot and something else to people. That is a clear violation of Google's spam policies and it gets sites penalized (we treat manipulative tactics only from the understand-and-avoid side; this is one). Dynamic rendering, done correctly, serves the same content to bots and users — it merely pre-renders it for the machine that can't run the scripts well. That equivalence is what keeps it on the right side of the line. The moment the bot version and the human version diverge in substance, "dynamic rendering" becomes cloaking, whatever you call it. So dynamic rendering is permitted but discouraged; cloaking is prohibited; and the difference between them is whether the content is genuinely the same. Because that equivalence is easy to break by accident, this is another reason Google prefers you simply server-render for everyone.

Prerendering is the umbrella term for all of this from one angle: prerendering means producing a page's finished HTML ahead of the request — at build time, or on demand and then cached — so that what gets served already contains the content, rather than leaving the browser to build it. Static generation is prerendering for everyone; some third-party "prerender" services generate a snapshot specifically for crawlers, which makes them a form of dynamic rendering. When a developer says "we prerender that page," your follow-up is always: prerendered for whom, and served from where?

Here is the comparison to keep. Read it as a set of trade-offs, not a ranking — the right answer depends on the page.

Approach Content in the raw HTML? Indexing reliability Freshness Server cost & complexity Google's stance
Client-side rendering (CSR) No — an empty shell Depends on Google rendering in time; real risk Instant (renders live) Low server cost; simple to deploy Works, but discouraged for content that must rank
Server-side rendering (SSR) Yes — rendered per request High Fresh every request Higher server cost; more complex; hydration cost Recommended
Static site generation (SSG) Yes — pre-built files Highest As fresh as the last build Lowest runtime cost; build scales with page count Recommended
Dynamic rendering Yes, for bots only High for Google; brittle pipeline Depends on the snapshot Extra bot-serving pipeline to maintain A workaround, not recommended long-term

(Constructed comparison for teaching; "reliability" and "cost" are directional, not measured.)

Notice that the two Google actually recommends — SSR and SSG — are exactly the two that put real content in the HTML for everyone, users and crawlers alike, with no special case for bots. That is not a coincidence. It is the same principle we keep circling: the durable answer is not to render specially for the machine, but to make the content plainly present so no special rendering is needed.


19.4 Frameworks, translated: what to actually ask your developers

This is the section most readers of this book need most, because most readers will never write the code — but they will sit across a table from the people who do, and the difference between a good outcome and an expensive mistake often comes down to whether the right question got asked. So let us translate the framework landscape into that conversation.

First, an essential clarification that dissolves most of the confusion. React, Vue, and Angular are rendering libraries — tools for building interfaces — and by themselves they produce client-side rendering. On top of each sits a "meta-framework" that adds server-side rendering and static generation:

  • Next.js is the dominant meta-framework for React. It supports SSR, SSG, and incremental regeneration, and its modern versions lean toward server rendering by default. A React site built on Next.js and configured to server-render its public pages puts content in the HTML and indexes cleanly.
  • Nuxt is the equivalent for Vue — the same idea, the same SSR/SSG capabilities, for the Vue ecosystem.
  • Gatsby is a React-based static-site generator: it pre-builds pages to static HTML at build time, which makes it a strong fit for content-heavy sites (blogs, docs, marketing) where the content does not change on every request. It was one of the tools that made "React, but crawlable" mainstream.

The one sentence to carry out of this section is: the framework is not what decides your SEO fate — the rendering mode you configure is. The very same React application can be a client-rendered SEO liability or a server-rendered, fully-indexable asset, depending entirely on how it is built and deployed. "We use React" tells you almost nothing. "Our public pages are server-rendered with Next.js and the content is in the initial HTML response" tells you almost everything. Your job is to get the conversation from the first sentence to the second.

🛠️ Try It on Your Site You do not need a developer to start. Open one of your important pages in Chrome, right-click, and choose "View Page Source" (or press Ctrl+U / Cmd+Option+U). This shows the raw HTML the server sent, before any JavaScript ran. Now use your browser's find (Ctrl+F) to search that source for a distinctive sentence from your page's main content — a specific phrase you can see on the screen. If you find it, that content is in the HTML: safe. If the "source" is mostly script tags and an empty <div>, and your visible content is nowhere in it, your content is being built by JavaScript — and §19.5 is where you find out whether Google is keeping up. Do this once and you will never again be vague about whether your site has a JavaScript-rendering exposure.

Here is the compact toolkit — the questions that turn a vague worry into a precise, answerable checklist. Hand these to whoever owns the site's front end.

Ask your developer… A good answer sounds like… A worrying answer sounds like…
Is the main content of our indexable pages present in the initial HTML response, before JavaScript runs? "Yes — those pages are server-rendered / statically generated." "It loads in after the app mounts."
Which rendering mode do our public, rankable pages use — SSR, SSG, or client-only? "SSR for dynamic pages, SSG for the marketing site." "It's a single-page app; everything's client-side."
Are our internal links real <a href="…"> anchors in the rendered HTML? "Yes, the framework's link component outputs real anchors." "They're click handlers / buttons that route in JavaScript."
Are any JavaScript or CSS resources blocked in robots.txt? "No — Google can fetch everything it needs to render." "We block the /static/ or /assets/ folder."
Can we confirm what Google actually rendered for a given URL? "Yes — here's the rendered HTML from URL Inspection." "We assume it's fine because it looks fine in the browser."

What this toolkit can do is give a non-developer real leverage and a shared language: you can now brief, verify, and hold a standard without touching the codebase. What it cannot do is substitute for actually running the tests in §19.5 — a confident "yes, it's server-rendered" from a developer is a hypothesis, and the professional habit is to trust it and verify it, because well-meaning teams are wrong about this more often than they expect.


19.5 Testing what Google sees

Everything so far has been preparation for this: the ability to stop guessing and look at what Google actually has. There is a ladder of tests here, from the thirty-second free check to the authoritative one, and a professional runs the cheap ones constantly and the authoritative one whenever the stakes are real.

Rung 1 — View Source versus the Elements panel. As in §19.4, "View Page Source" shows the raw HTML before JavaScript. The browser's "Inspect" / Elements panel shows the rendered DOM after JavaScript. The gap between the two is precisely the content your page depends on JavaScript to create. If your main content is in View Source, you are on solid ground. If it appears only in the Elements panel, you have a rendering dependency to verify.

Rung 2 — Disable JavaScript and reload. In Chrome's developer tools you can disable JavaScript entirely, then reload the page. What remains is a rough, deliberately pessimistic picture of the page stripped of all scripts. This is not exactly what Google sees — Google does run JavaScript — so a page that is blank with JavaScript off is not necessarily invisible to Google. But it is an excellent stress test: it shows you every hard dependency at once, and anything critical that vanishes (content, links, navigation) is something you are trusting the render step to deliver. For important pages, "does the core content survive with JavaScript off?" is a bar worth clearing even though Google can clear a lower one.

Rung 3 — The site: phrase search. This one is free, fast, and underused, and it tells you what Google actually indexed rather than what it theoretically could. Take a distinctive sentence that appears only in your JavaScript-rendered content, put it in quotation marks, and search Google for it, optionally scoped to your domain:

"the exact distinctive sentence from your rendered content" site:yourdomain.example

If Google returns your page, that text made it into the index — rendering worked for that content. If Google returns nothing while the page is definitely live and indexed, that is strong evidence the rendered content did not get in. It is a real answer from Google's own index, obtained with no tools and no login.

Rung 4 — The URL Inspection Tool (the authoritative test). Google Search Console's URL Inspection Tool is the ground truth, because it shows you what Google's own systems did with the page. It has two modes: it can show the last indexed version, and it can run a live test. Crucially, it lets you see the rendered HTML Google produced — not the raw HTML, but the DOM after Googlebot ran the JavaScript — along with a screenshot of how the page rendered and a list of any page resources Google could not load. If your content is in that rendered HTML, Google saw it. If it is missing, you have found your problem, and the "more info" panel about blocked or failed resources usually points straight at the cause. The URL Inspection Tool belongs to Chapter 27, where we set up Search Console properly and make reading it a weekly ritual; here, know that it is the definitive way to answer "what did Google render for this URL?"

📄 Read the Report

text FIGURE 19.1 — "The shell and the render" [constructed teaching example] THE QUERY / PAGE URL Inspection on a product page from a client-rendered store, comparing the crawled HTML with the rendered HTML. WHAT'S THERE Raw/crawled response: a near-empty <body> with <div id="root"></div>, a "Loading…" title, and script tags — no product name, price, or description. Rendered HTML (post-JavaScript): the full product name, price, specs, and description are present. One flagged item: an "other error" on a script hosted at a third-party analytics domain. WHAT IT SHOWS Google DID render this page and DID capture the product content — the render succeeded this time. The content is client-built but currently indexable. WHAT IT DOESN'T It does not prove every page renders, or renders promptly, or will keep rendering if that third-party script slows down or the API times out. One green light is not a guarantee across a catalog of thousands. THE MOVE Confirm at scale (spot-check several templates; run the site: phrase test on a sample), and reduce the dependency: move the critical product content to SSR/SSG so indexing no longer rides on a live render succeeding. THE LESSON "It rendered when I checked" is a snapshot, not a guarantee. Verify across templates, and prefer content that needs no render at all.

That figure holds the chapter's honesty in one place. Testing tells you the truth about this page, right now. It does not convert a fragile architecture into a robust one. The value of the test is that it turns a faith-based situation ("it should be fine") into an evidence-based one ("here is exactly what Google has"), and evidence-based is the only posture this book ever recommends.

🔄 Check Your Understanding Your developer says a new client-rendered landing page "is definitely fine for SEO — I can see all the content in my browser." Name the single most authoritative free check you would run before you believe it, and one thirty-second check anyone can run without logging into anything.

Answer The authoritative check is URL Inspection in Search Console — look at the rendered HTML and confirm the landing page's actual content is present in what Google rendered (and check for blocked or failed resources). The thirty-second, no-login check is the site: phrase search: quote a distinctive sentence from the page and search Google for it — if the page comes back, that text is indexed. "I can see it in my browser" proves only that your browser rendered it, which was never in doubt.


19.6 The three failures that break JavaScript sites

Most JavaScript SEO disasters are not exotic. They are the same handful of mistakes, made again and again, each following directly from how the Web Rendering Service behaves (§19.1). Learn to spot these three and you will catch the large majority of real problems.

Failure 1 — Links that aren't links. Google discovers and follows navigation by reading real anchor elements: <a href="/some-page">. That href is the road. When a JavaScript site makes something look clickable but implements it as a <div> or a <button> with a click handler that navigates in code — onClick={() => router.push('/some-page')} — there is no href for Google to follow. To a user with a mouse it behaves like a link; to Googlebot it is a dead end.

A LINK GOOGLE CAN FOLLOW vs. ONE IT CANNOT               [illustrative]

  ✅  <a href="/services/furnace-repair">Furnace repair</a>
        └─ a real anchor with an href — Google follows it, and authority flows through it

  ❌  <div onclick="goTo('/services/furnace-repair')">Furnace repair</div>
        └─ looks clickable to a human; NO href — Google has no road to the destination

The consequences are serious and easy to miss: the destination pages can end up orphaned (nothing Google can follow points to them), so they are discovered late or not at all; and because internal links are how ranking authority (PageRank) moves through a site — the subject of Chapter 15 and Chapter 22 — a page reached only by a non-anchor click receives none of it. The fix is simple and costs nothing in user experience: use real <a href> anchors for anything that is genuinely navigation. The good news is that the framework "link" components (Next.js's <Link>, Nuxt's, and the router links in Vue and Angular) render real anchors by default — so this failure is usually a matter of a developer having hand-rolled a fake link, not of the framework forcing one.

🔎 How Search Sees It Remember the WRS does not scroll, click, hover, or wait around. That single fact predicts Failure 2 before you even see it. Content that a user reveals by doing something — clicking a "Load more" button, scrolling to trigger infinite scroll, opening a tab or accordion that fetches its text on click, hovering to expand — is content Googlebot may never trigger, because Googlebot does none of those things. It renders the page as it arrives and indexes what is there. If your reviews load only when the visitor scrolls to them, or your product description is hidden in a tab that fetches its content on click, Google may simply not have it.

Failure 2 — Content that only loads on interaction. This is the mechanism the callout just described, and it is everywhere: "Load more" pagination on a product grid or article feed, infinite scroll, tabs and accordions that lazy-fetch their contents, and content that appears only after a user action. Because Googlebot does not perform the action, the content behind it can be missing from what gets indexed. Two clarifications keep this precise. First, lazy-loading images with the native loading="lazy" attribute is fine — Google handles that correctly, and it is good for performance (Chapter 16). The danger is not lazy-loading per se; it is content that requires a user interaction or an event Googlebot won't fire. Second, the fix is not to abandon these patterns for users but to make the content also available without the interaction: render the important content on initial load (or on page load, not on click), provide real paginated URLs (/blog/page/2) alongside the "Load more" button so each set of items has a crawlable address, and put the tab and accordion contents in the HTML even if they are visually hidden until clicked (visually hidden content still counts as present). Serve the machine the whole page; let the interaction be an enhancement for the human.

Failure 3 — Hash-fragment routing. Some JavaScript apps put their "routes" after a # in the URL: example.com/#/products/123, example.com/#/about. This is a specific and severe trap, because of how URLs work at the protocol level: everything after the # is a fragment, which browsers do not send to the server and which Google treats as pointing to the same underlying page. So to Google, example.com/#/products/123 and example.com/#/about and example.com/ are all one URL — there are no distinct addresses to crawl, index, or rank. An entire catalog can collapse into a single indexable page.

WHY HASH ROUTING HIDES YOUR PAGES                         [schematic]

  example.com/#/products/123  ┐
  example.com/#/products/124  ├──▶  Google sees ONE url:  example.com/
  example.com/#/about         ┘        (the "#…" part is a fragment, not a separate page)

  example.com/products/123    ┐
  example.com/products/124    ├──▶  Google sees THREE distinct, crawlable, indexable URLs
  example.com/about           ┘

You may have heard of an old workaround — the #! "hashbang" AJAX-crawling scheme Google once supported. It is dead: Google deprecated it years ago and no longer supports it, so do not resurrect it. The correct, modern fix is to use real, path-based URLs via the browser's History API — example.com/products/123, a genuine address that returns real content — which every serious framework router supports and which is the default in properly configured setups. If you inherit a site with hash routing and organic traffic that makes no sense given the content, this is one of the first things to check.

🚫 SEO Myth: "Google renders JavaScript perfectly now, so JavaScript SEO is a solved problem." This is the mirror-image error of the "Google can't read JavaScript" myth, and it is just as costly. Yes, Google's rendering has improved enormously. No, that does not make the three failures above disappear — none of them is about whether Google can render; they are about links with no href, content behind interactions Googlebot won't perform, and URLs that aren't distinct URLs. A flawless renderer still cannot follow a road that isn't there or click a button it never touches. "Google handles JavaScript" and "JavaScript sites still routinely lose traffic to avoidable architecture mistakes" are both true at once, and a good technical SEO holds both without letting either become an excuse to stop checking.

A note on two smaller but real traps, so they are on your radar: blocked resources — if your robots.txt disallows the folders holding your JavaScript or CSS, Google cannot fetch what it needs to render, and you get the shell (this is why Chapter 14 warned against blocking resource folders); and JavaScript-injected critical tags — a <title>, canonical, or robots meta tag written or changed by JavaScript can be missed, delayed, or conflict with the raw-HTML version. A particularly nasty case: a noindex present in the initial HTML that JavaScript later removes may still get the page dropped, because Google can act on the noindex it saw first. The lesson is the same each time — the initial HTML response is the safest place for anything that matters, and metadata matters.


19.7 The simplest safe path: content in the HTML before the JavaScript runs

Step back from the mechanisms and the failure catalog, and the whole chapter reduces to a single principle you can act on without being an engineer:

Get your important content and your important links into the HTML that arrives before any JavaScript runs. Let JavaScript enhance the page from there — never let it be the only way the content exists.

This is the old idea of progressive enhancement wearing modern clothes: build the page so it works as content first, then layer interactivity on top, rather than building an application that only becomes content if a render succeeds. In practice, for anything that must rank, that means server-side rendering or static generation (or a traditional server-rendered platform), so that the content is simply there — for Google, for Bing, for social previews, for AI crawlers, for a user on a slow phone whose JavaScript hasn't finished, and for the render step that then has nothing left to get wrong.

A clean decision rule falls out of it:

DOES THIS PAGE NEED TO RANK IN SEARCH?                    [decision aid]

  YES ──▶ It must be server-rendered or statically generated.
          Content + links present in the initial HTML. Then test (§19.5).

  NO  ──▶ (a logged-in dashboard, an interactive tool, a checkout step,
           anything behind authentication that Google won't index anyway)
          Client-side rendering is perfectly fine — optimize it for users, not crawlers.

Two honest caveats keep this from becoming a slogan. First, server-rendering is necessary but not sufficient. An SSR or SSG site can still ship fake links, hide content behind interactions, block its own resources, or route on hash fragments — the failures in §19.6 are architecture bugs, not CSR bugs, and server-rendering does not immunize you against them. You still test. Second, SSR and SSG carry real costs — more moving parts, more server infrastructure, hydration work that can itself hurt interaction metrics, and a higher bar for the team to operate. They are not free, and for a genuinely static brochure site they can be overkill. The point is not "always adopt the most elaborate rendering setup." It is "match the rendering mode to whether the page needs to be seen by machines, and never let a page that must rank depend on client-side rendering alone."

Which leads to the most important, least glamorous piece of advice in the chapter: if what you have already puts content in the HTML, do not rebuild it into something that doesn't. A great many sites run on WordPress, Shopify, Squarespace, or similar platforms that are server-rendered by default and have no JavaScript-visibility problem at all. Replatforming such a site into a client-rendered single-page application, in pursuit of a snappier feel or a trendier stack, means taking on the entire risk surface of this chapter in exchange for benefits that rarely touch SEO — and any replatform is also a migration, with all the traffic risk that entails (Chapter 21). "Knowing which problems you don't have is as valuable as knowing which you do," we said in Chapter 1. Nowhere is that truer than here.

🔗 Connection The idea that rebuilding your site is a high-risk operation demanding a disciplined redirect and monitoring plan is Chapter 21 (Site Migrations). The performance cost of heavy JavaScript is Chapter 16 (Core Web Vitals). The wider point that non-Google, JavaScript-averse AI crawlers increasingly decide who gets cited is Chapter 36 (AI Search). And the tool that proves what Google rendered is Chapter 27 (Search Console).


📈 The Strategy File

Rivertown Home Services runs on WordPress, and back in Chapter 1 we noted, almost in passing, that this means Rivertown "does not have a rendering problem." That was true, and it is worth confirming properly now rather than taking on faith — and then finding the exceptions, because mostly safe is not entirely safe, and the exceptions are exactly where a family business quietly loses content it thinks Google can see. Marisa and Tony Delgado do not need to understand rendering pipelines. They need a repeatable test they (or whoever maintains the site) can run, and a clear list of what passed and what didn't.

So this chapter's contribution to the Strategy File is a "can Google see it?" test procedure — the same ladder from §19.5, written as a checklist Rivertown can run on any page, plus the results of running it across the site.

THE "CAN GOOGLE SEE IT?" TEST — for any Rivertown page    [the Strategy File]
  1. VIEW SOURCE     Open the page, View Page Source (Ctrl+U), and Ctrl+F for a
                     distinctive sentence you can see on the screen.
                       • Found in source  → it's in the HTML. Safe.
                       • Not in source    → it's JavaScript-built. Go to step 2.
  2. site: PHRASE    Quote that sentence and search Google:
                       "…distinctive sentence…" site:rivertownhome.example
                       • Page returned → Google indexed that text. Probably fine.
                       • Nothing       → Google may not have it. Go to step 3.
  3. URL INSPECTION  In Search Console, inspect the URL and read the RENDERED HTML
                     (and any blocked/failed resources). This is the ground truth.
  4. DECIDE          In-HTML content: leave it. JavaScript-only content that must
                     rank: move it into the server-rendered page (or accept it won't
                     reliably rank and don't count on it).

Now the findings from running it on Rivertown — good news first, then the exceptions.

What passes (the core of the site). Every page that matters for organic traffic is server-rendered by WordPress and passes step 1 outright: the home page, the five location pages, the service pages (AC repair, furnace repair, water-heater replacement, drain cleaning, panel upgrades, EV-charger installation), the blog posts, and the page copy generally. View Source on the water-heater page — our friend stuck at #11 — and the headings, the body text, and the internal links are all right there in the raw HTML. Rivertown's ranking problems are real, but rendering is not one of them; they live at the ranking stage (intent, comprehensiveness, internal links, authority), exactly as Chapter 1's diagnosis said. Confirmed. Cross it off the worry list with evidence, not faith.

The exceptions (where JavaScript hides content). Three bolt-ons fail the test, and they are the useful part of this exercise:

  • The third-party reviews widget. Rivertown's home page and location pages embed a star-rating-and-testimonials widget from a review platform, injected by a <script>. In the browser, the Delgados see a proud row of gold stars and glowing quotes. In View Source, that area is an empty <div> — the stars and the review text are built client-side and are not in the HTML Google first reads. Two consequences, stated honestly: the review text may not be understood as content on the page, and — more commonly misunderstood — those visible stars are not the same thing as review stars in the search result. Rich-result stars in the SERP require genuine review structured data (schema) in the page, and are governed by Google's policies on which review markup is eligible; a JavaScript widget is not that. The fix direction (server-rendered review content plus proper, policy-compliant review schema drawn from real reviews) belongs to Chapter 18 (structured data) and Chapter 25 (local SEO and reviews) — we flag it here and do not get ahead of it.
  • The booking/scheduling widget. The "Book an appointment" form is a third-party embedded app that loads entirely in JavaScript. That is fine — it is a transactional tool, not content that needs to rank, and by the §19.7 decision rule a client-rendered widget for a booking flow is exactly the right call. The note for the file: don't expect any text inside that widget to be indexed, and make sure the page it sits on still has real, server-rendered content around it so the URL has something to rank for.
  • Any "load more" on the blog listing. If the blog index uses a JavaScript "Load more" button to reveal older posts, Googlebot won't click it (Failure 2). It matters little here because the individual posts are linked and server-rendered elsewhere, but the note stands: ensure the blog has real paginated URLs so every post is reachable by a crawlable link.

What this settles, and what it doesn't. It settles that Rivertown's rendering exposure is small and bounded: the content that must rank is in the HTML, and the JavaScript-only pieces are a reviews widget and a booking tool, one of which (reviews) has an SEO implication to handle later via schema, the other of which (booking) is correctly client-side. It does not settle the reviews question itself — that is a schema and local-SEO job — and it does not touch Rivertown's actual ranking problems, which were never about rendering. The value of the test is the same as always: it replaces "the site's probably fine, it's WordPress" with a documented, evidence-backed statement of exactly what Google can and cannot see, and a short list of the exceptions to carry forward. (All Rivertown details are a constructed teaching example.)

Your Strategy-File task: run the four-step test on your own most important page. If the content is in View Source, congratulations — mark it safe and move on. If it isn't, run the site: phrase search and, if you have Search Console, look at the rendered HTML, and write one sentence recording what Google can and cannot see. That single sentence is worth more than a month of worrying about it.


Conclusion

We began with a question that sounds paranoid until you understand the machine: is your content actually there for Google, or does it only exist after a browser runs your JavaScript? The answer turns on a mechanism we can now state plainly. Google renders JavaScript with a real, evergreen, Chromium-based engine — but rendering is a separate, resource-bounded step that can be delayed, can complete partially, or can fail, and the Web Rendering Service that does it never scrolls, clicks, waits, or logs in. Client-side rendering, which puts content into existence only after that step, inherits all of that risk; server-side rendering and static generation remove it by putting the content in the HTML for everyone; dynamic rendering is a discouraged workaround that must never tip over into cloaking. The framework you chose — React, Vue, Angular — matters far less than the rendering mode you configured on top of it.

Most of all, we made the problem testable. You can now look, rather than guess: View Source and a disabled-JS check for a first read, the site: phrase search for Google's actual verdict, and URL Inspection's rendered HTML for the ground truth. And you can recognize the three failures — links without an href, content behind an interaction Googlebot won't perform, and hash-fragment routing that collapses a whole site into one URL — that account for most JavaScript-SEO disasters and each have a clean fix. The through-line, honest to the end, is that the durable answer is not a clever render-for-the-bot trick but the plainest thing imaginable: important content and links in the HTML before the JavaScript runs. For Rivertown, that principle plays out as relief — WordPress already does it — tempered by a short, specific list of JavaScript-injected exceptions to mind.

In Chapter 20, we turn to the most error-prone corner of technical SEO for a very different reason: international SEOhreflang, ccTLDs, and telling Google which version of a page belongs to which language and country — where, as with JavaScript, the honest first question is often "do you even need this?" For Rivertown, single-country and local, the answer will be no, and knowing why it's no is its own kind of technical fluency.

→ Continue to Chapter 20: International SEO.


Key Terms

  • Client-side rendering (CSR) — an architecture in which the server sends a minimal HTML shell and the user's browser builds the page by downloading and running JavaScript (often fetching data from an API); the content exists only after that render. The pattern of the single-page application.
  • Server-side rendering (SSR) — the server runs the framework for each request and returns fully-formed HTML with the content already in it; the browser then hydrates the page for interactivity.
  • Static site generation (SSG) — pages are rendered to plain HTML at build time and served as static files, giving every visitor and crawler a complete, dependency-free document; content is as fresh as the last build.
  • Dynamic rendering — serving pre-rendered HTML to crawlers but client-rendered JavaScript to users; permitted only when the content is equivalent, considered by Google a workaround rather than a recommendation, and one accidental divergence away from cloaking.
  • Hydration — the step in which a JavaScript framework attaches to already-rendered HTML, wiring up interactivity without rebuilding the markup from scratch.
  • Two-wave index — Google's decoupling of crawling from rendering: raw HTML is indexed immediately (first wave), while content that needs JavaScript waits in a render queue for a later pass (second wave); the delay is usually short now but not guaranteed.
  • Prerendering — producing a page's finished HTML ahead of the request (at build time, or on demand and cached) so the served response already contains the content; the umbrella that includes SSG and bot-focused snapshot services.

Spaced Review

Retrieval practice. Try each before revealing the answer.

  1. In your own words, what is the "two-wave index," and why does it make content that exists only after client-side rendering riskier than content in the raw HTML?
  2. A navigation menu on a React site is built with <div> elements that route via JavaScript click handlers instead of <a href> anchors. Name two distinct SEO harms this causes.
  3. (From Chapter 14.) A developer blocks the /static/js/ folder in robots.txt to "save crawl budget" on a client-rendered site. Why is this a serious mistake, and what will Google most likely end up indexing?
  4. (From Chapter 1.) Rendering is one stage of the five-stage pipeline. Name the five stages in order, and say which one JavaScript rendering belongs to.
  5. You want to know whether Google actually indexed a paragraph that your site builds with JavaScript. Give a free, thirty-second way to check without logging into any tool.
Answers 1. The two-wave index is Google's separation of crawling from rendering: it indexes whatever is in the raw HTML right away (first wave), and content that requires JavaScript waits in a render queue to be indexed in a later pass (second wave). Client-rendered content is riskier because it exists *only* after that second pass — so it is exposed to the render step's delay, partial completion, or outright failure, whereas content already in the HTML is indexed immediately with no such dependency. 2. Any two: (a) the destination pages can be **orphaned** — with no `href` for Google to follow, they may be discovered late or not at all; (b) **no ranking authority (PageRank) flows** to those pages, because authority moves through real links (Chapters 15, 22); (c) more generally, Google cannot crawl the site's structure through non-anchor "links," undermining discovery and architecture signals. 3. `robots.txt` blocks *crawling*, so Google cannot fetch the JavaScript files it needs to render the page — and on a client-rendered site the content only exists after that JavaScript runs. Google will most likely index the near-empty **shell** (the `
` and not much else), because it was prevented from rendering the real content. (Blocking resource folders is exactly the trap Chapter 14 warned about; and on a small site "saving crawl budget" was almost certainly a non-problem to begin with.) 4. Discover → crawl → **render** → index → rank (then serve the SERP). JavaScript rendering is the **render** stage — Google executing the page's code to see the finished content before it decides what to index. 5. The **site: phrase search**: put a distinctive sentence from the JavaScript-built paragraph in quotation marks and search Google for it (optionally with `site:yourdomain`). If the page comes back, that text is in Google's index; if nothing comes back for a live, indexed page, the rendered content probably didn't make it in.