How Search Engines Render JavaScript
Somewhere between "Google can't see JavaScript at all" (true long ago) and "Google renders everything perfectly, don't worry about it" (never true) lies the actual situation: search engines can execute JavaScript, but they do it on their own schedule, with their own budgets, and with failure modes that produce a uniquely frustrating symptom — a page that looks perfect to every human and half-empty to the crawler.
If your WordPress site is a classic theme rendering HTML on the server, most of this post is reassurance. If you've added a React-powered store locator, a headless frontend, or a page builder that assembles content client-side, this is the stuff that determines whether that content ranks at all.
The two-phase reality of Googlebot
Google processes pages in two distinct stages, and the gap between them is where most JavaScript SEO problems live.
Phase one: crawl and parse HTML
Googlebot fetches your page and immediately processes the raw HTML response — the thing you see with "View Source," not the DOM you see in DevTools. From this initial HTML it extracts links to crawl next, reads meta tags (including robots directives and canonicals), and indexes whatever content is present. If your content is fully in this HTML, you're essentially done; everything after this is bonus.
Phase two: render
Pages that need JavaScript execution go into a render queue. A headless Chromium (kept reasonably current with stable Chrome, so modern JS features generally work) executes the page, produces a rendered DOM, and Google indexes that. Historically this delay was measured in days or weeks; today it's usually much shorter — often minutes to hours — but it is still a queue with a budget, not a guarantee.
Three consequences follow from this architecture:
- Anything only visible after rendering is indexed later — or not at all if the render fails or is deprioritized.
- Links that only exist after JavaScript runs are discovered late. On a large site, this can mean whole sections crawl slowly because their only paths in are JS-generated.
- Conflicting signals between phases cause weirdness. If your raw HTML says noindex and your JavaScript removes it, you lose: Google may never render a page it was told not to index. Signals in the initial HTML are the ones you can rely on.
What about the other crawlers?
Google is the best JavaScript renderer in the business, and it's worth being clear about what that implies: everyone else is worse. Bing renders JavaScript but less predictably. Many AI crawlers — the ones feeding chat assistants and answer engines that increasingly send you visitors — fetch raw HTML and do not execute JavaScript at all. Social preview bots (the things that build link cards) mostly don't render either.
The practical rule: the more of your meaning that lives in the initial HTML response, the more of the ecosystem can see it. Rendering ability is a spectrum, and raw HTML is the only format with universal support.
The common failure modes, ranked by frequency
1. Content that requires user interaction
Content behind a click — tabs, accordions, "load more" buttons — is only indexed if it's present in the DOM after initial render. Googlebot does not click, scroll-to-trigger, or hover. An accordion whose content is in the HTML but visually hidden with CSS: indexed. A "load more" button that fetches ten more products via API on click: those products effectively don't exist. If those products have no other crawlable path (like a paginated URL), they're invisible.
2. Blocked resources
If your robots.txt blocks the JavaScript or API endpoints the page needs, the renderer executes a broken page. This happens constantly on WordPress sites where someone blocked /wp-json/ or a plugin's asset directory out of caution. The renderer isn't malicious; it just can't fetch what you've forbidden.
3. Timeouts and slow hydration
The renderer waits, but not forever. There's no official published timeout, but as a working assumption, content that takes more than several seconds of script execution and network chatter to appear is at risk. A single-page app that spins up, fetches a config, then fetches auth state, then finally fetches content is playing timeout roulette on every page.
4. Errors that only happen for bots
Googlebot renders with a stateless browser: no cookies persisted between pages, no localStorage from a prior visit, sometimes with permission-based APIs stubbed out. Code that assumes a returning visitor — reading a stored preference and crashing when it's null — can throw an exception that halts rendering entirely. Your error monitoring won't show it because your error monitoring probably ignores bot traffic.
5. JS-generated meta tags and canonicals
Titles, meta descriptions, canonical tags, and robots directives injected by JavaScript are processed inconsistently. Google can pick them up at render time, but the initial-HTML values are what get used in the crucial first phase — and if the two conflict, behavior gets unpredictable. Critical directives belong in server HTML, full stop.
6. Soft 404s from client-side routing
Single-page apps often handle "not found" by rendering an error message while returning HTTP 200. Google sees a thin page with "nothing found" text and a success status code, and classifies it as a soft 404 — sometimes generalizing that pattern to other, legitimate pages on the route. Real 404s should return real 404 status codes, which requires server involvement.
How to test what search engines actually see
You don't need to speculate. Here's the diagnostic sequence, cheapest first:
- View source. Load your page, view the raw source (not Inspect Element), and search for a sentence from your main content. Present? Your content survives even the dumbest crawler. Absent? Continue down the list.
- Disable JavaScript. In Chrome DevTools: Command menu → "Disable JavaScript" → reload. What remains is what a non-rendering crawler sees. If it's a blank div and a spinner, that's your page for a large chunk of the bot ecosystem.
- URL Inspection in Google Search Console. Run "Test live URL," then look at both the rendered screenshot and — more importantly — the HTML tab. Search that rendered HTML for your key content. Also check the "More info" panel for blocked resources and JS console errors. This is the single most truthful tool available, because it's Google's actual renderer.
- Check the indexed reality. Search Google for an exact phrase from the JS-rendered part of the page, in quotes. If pages have been live for weeks and the phrases aren't findable, the rendered content isn't making it into the index, whatever the testing tools say.
- Crawl your own site with rendering on and off. Screaming Frog can crawl in both modes; diff the word counts and link counts. Pages with big deltas are your risk list.
Fixes, in order of preference
The solutions form a clear hierarchy. Reach for the earliest one you can.
Server-side render or statically generate
If content matters for search, deliver it in the initial HTML. For WordPress this is the default behavior — one of the platform's quiet strengths. For headless setups, use your framework's SSR or static generation mode (Next.js, Nuxt, Astro, and friends all support this well). Static generation is the best of all worlds for content that doesn't change per-request: fast, cacheable, and fully crawlable.
Hydrate progressively, don't client-render
A good middle ground: server-render the full content, then let JavaScript enhance it. The page works without JS; the widgets get better with it. Most interactivity — filters, carousels, calculators — can layer on top of real HTML rather than replacing it.
Keep client-side rendering for what it's good at
Logged-in dashboards, checkout flows, configurators — things that shouldn't or needn't rank — are fine as pure client-side apps. The mistake is using the same architecture for the marketing pages and the app, then wondering why the marketing pages struggle.
Dynamic rendering: the legacy escape hatch
Serving pre-rendered HTML to bots and the JS app to humans (via services like Prerender.io) still works, but Google now describes it as a workaround rather than a recommendation. It adds infrastructure, can serve stale snapshots, and you have to maintain bot detection. Use it if you're stuck with a client-rendered site you can't rebuild; plan to migrate off it.
A short checklist for WordPress site owners
- Classic theme or standard block theme? Your content is server-rendered. Verify with view-source once and move on with your life.
- Using page builder widgets that fetch content dynamically (some listing grids, some AJAX-loaded tabs)? Test those specific pages with the JS-disabled trick.
- Never block /wp-includes/js/, /wp-content/, or /wp-json/ in robots.txt unless you can articulate exactly why.
- If you go headless, make SSR/SSG a launch requirement, not a post-launch optimization. Retrofit costs are brutal.
- Keep titles, meta descriptions, canonicals, and robots directives in server HTML — never JS-injected.
The bottom line
Google's rendering is good enough that JavaScript-heavy sites can rank, and unreliable enough that you should never depend on it when you have a choice. Every layer of client-side execution between the crawler and your content is a place where budgets, timeouts, errors, and less-capable bots can lose you. Server-rendered HTML isn't nostalgia; it's the only delivery format that search engines, AI crawlers, and preview bots alike can all read. Render on the server, enhance in the browser, and verify with Search Console rather than assuming — that combination ages well no matter what the crawlers do next.
Photo: Daniil Komov on Pexels