Skip to content
SiteFaults

JavaScript SEO and rendering

Crawl, render, index — three separate stages. The failures they produce are partial and intermittent, which is what makes them hard to catch.

By Max8 min read

Google renders JavaScript. That answer has been true for years, and taken alone it is the reason a lot of JavaScript sites quietly fail in search. Rendering is not free, not immediate and not guaranteed — so the failures it produces are partial, intermittent, and invisible to anyone testing the site in a browser.

What actually happens when Googlebot meets your page

Three stages, and they are separate.

Crawl. Googlebot requests the URL and receives the raw HTML. It extracts links from that HTML and adds them to the crawl queue. If your navigation only exists after hydration, no links are found at this stage.

Render. The page joins a rendering queue. When it comes up, a headless Chromium executes the JavaScript and produces the rendered DOM. This is what Google actually indexes — but the wait is variable, and the renderer has limits: it will not wait indefinitely for slow requests, and it does not click things.

Index. The rendered DOM is evaluated. New links found during rendering go back into the crawl queue, which is why a JavaScript-only navigation slows discovery across the whole site — every level of depth requires another full crawl-render cycle.

Two consequences follow. Content in the initial HTML is indexed on a predictable schedule. Content that requires rendering is indexed on an unpredictable one, and sometimes not at all.

The failures that matter

Links that are not links. Googlebot follows <a href>. It does not follow a <div> with an onClick handler that calls a router, and it does not follow <a> with no href. If your navigation is built this way, entire sections of the site are undiscoverable except through the sitemap — which gets them crawled but tells Google nothing about their importance.

Content behind an interaction. The renderer does not click, scroll, hover, or submit. Tabbed content, accordions and “read more” sections are fine if the text is in the DOM and merely hidden with CSS. If the content is fetched when the tab is clicked, it does not exist as far as Google is concerned.

Infinite scroll with no paginated URLs. The renderer does not scroll, so it sees only the first batch. Anything below is unindexed. The fix is real paginated URLs behind the scroll behavior, so the content has addresses.

Metadata written by JavaScript. Titles, descriptions, canonicals and structured data injected client-side may be picked up on render — or may not be, if rendering is delayed or the script fails. This is the highest-consequence version of the problem: a client-side canonical that Google misses means the page is judged with no canonical at all.

Blocked JavaScript. If robots.txt disallows the directory your bundles live in, rendering produces a blank page. This is rare now but catastrophic and completely silent.

Failures that only happen sometimes. An API call that times out under load, a third-party script that fails, a rate limit that triggers during a crawl burst. In a browser everything works. During one particular render, the page was empty — and that empty version is what got indexed. This is the class of bug that makes JavaScript SEO genuinely hard, because you cannot reproduce it.

How to see what Google sees

Your browser is the wrong instrument. It has your cache, your cookies, your connection, and it runs every script to completion.

Search Console URL Inspection, live test, then “View crawled page” → HTML. This is the authoritative view: the rendered DOM as Google produced it. Search it for your headline, your body text, your canonical. The screenshot beside it is also worth reading — a blank or broken screenshot is unambiguous.

Check the “More info” → page resources tab too. It lists what could not be loaded during rendering, which is where blocked bundles and failed API calls show up by name.

Compare source against rendered. View source shows the initial HTML; DevTools’ Elements panel shows the post-JavaScript DOM. The gap between them is your exposure. Everything in that gap depends on the rendering queue.

Disable JavaScript and reload. Crude and informative. What remains is what is guaranteed to be seen on the first pass. If that is an empty shell, discovery of everything the page links to is delayed.

The fix, ranked

Server-side render the things that matter. Main content, title, meta description, canonical, structured data and navigation links in the initial HTML. Everything else — interactive widgets, personalization, comments — can hydrate client-side without cost. This is not “abandon your framework”; every modern framework supports it, and the ones that do not have static generation instead.

Static generation where the content allows it. Pre-rendered HTML at build time is the fastest and most reliable option available, and for content that changes on a deploy rather than per request it costs nothing.

Real <a href> for every navigational link. Frameworks’ link components emit real anchors — use them. A click handler on a div is the single most common cause of undiscoverable sections, and it is a one-line fix.

Dynamic rendering only as a stopgap. Serving pre-rendered HTML to bots and the normal app to users works, but Google now describes it as a workaround rather than a recommendation: it means maintaining two rendering paths that can silently diverge, and a divergence is indistinguishable from cloaking. If you inherit one, treat migrating off it as the goal.

Speed is part of this

The same bundle that delays rendering also delays your metrics. A large JavaScript payload has to be downloaded, parsed and executed on the main thread, which pushes out both Largest Contentful Paint and Interaction to Next Paint. The rendering constraint and the performance constraint have the same cause and largely the same fix, which is a rare piece of good news — see how to diagnose slow page speed.

What to check today

Run URL Inspection on your most important page and search the rendered HTML for your main heading. Then disable JavaScript, reload, and see whether your navigation still exists as links. Those two checks catch most of what goes wrong here.

Our free site check fetches your pages the way a crawler does, so what it reports is what is available without waiting on a rendering queue — a title or canonical it cannot find is one Google may also miss on the first pass. If pages are being read but not stored, crawled, currently not indexed covers where rendering sits among the other causes.

Frequently asked questions

Can Google index JavaScript content?
Yes, but not on the same terms as HTML. Googlebot crawls your raw HTML first, then queues the page for rendering with a headless browser, which may happen seconds or days later. Content in the initial HTML is indexed on a predictable schedule; content that requires JavaScript depends on that queue, and the resulting failures are partial and intermittent rather than total.
Why isn't my JavaScript site being indexed properly?
The most common causes are navigation built from click handlers on divs rather than real anchor tags, so Googlebot finds no links to follow; content loaded only when a tab or accordion is clicked, which the renderer never does; infinite scroll without paginated URLs; and metadata such as canonicals injected client-side that a delayed render may miss. Blocked JavaScript files in robots.txt cause total failure and are worth ruling out first.
How do I see my page the way Google renders it?
Use Search Console's URL Inspection tool, run a live test, then open 'View crawled page' and read the HTML tab — that is the rendered DOM as Google produced it. Search it for your headline and canonical. Also check the page resources tab, which names anything that failed to load during rendering, and read the screenshot: a blank one is unambiguous.
Does Googlebot click buttons or scroll the page?
No. The renderer does not click, scroll, hover or submit forms. Hidden content is fine if the text is present in the DOM and merely hidden with CSS — a collapsed accordion is indexable. Content fetched at the moment a tab is clicked is not, because that moment never arrives.
Should I use server-side rendering for SEO?
Render the things that matter server-side: main content, title, meta description, canonical, structured data and navigation links in the initial HTML. Interactive widgets, personalization and comments can hydrate client-side at no cost. Static generation is better still where the content permits it, because pre-rendered HTML removes the rendering queue from the equation entirely.
Is dynamic rendering a good solution?
Only as a stopgap. Serving pre-rendered HTML to bots and the normal application to users does work, but Google now describes it as a workaround rather than a recommendation, because it means maintaining two rendering paths that can silently diverge — and a divergence is indistinguishable from cloaking. If you have one, plan to migrate off it.
Does JavaScript hurt my Core Web Vitals?
A large bundle does, yes. It has to be downloaded, parsed and executed on the main thread, which delays both Largest Contentful Paint and Interaction to Next Paint. The useful part is that the rendering problem and the performance problem share a cause, so server-side rendering the critical content and shrinking the client bundle improves both at once.

Run a free check on your own site

Paste any URL and get your score, your Core Web Vitals and every issue we find in about 30 seconds. No signup, no card.

Free, no signup, no card. Results in about 30 seconds, and your report is deleted automatically after 30 days.