JavaScript SEO: Getting Crawlers to Render Your SPA Correctly

Single-page apps (SPAs) built with React or Vue often serve an empty HTML shell, with content filled in only after JavaScript runs. That’s fine for users, but it can be a disaster for crawlers: the HTML they receive contains no body text, so there’s nothing to index, you never show up in ranking pages, your carefully produced content sinks without a trace, and traffic never picks up.

That’s the core tension in JavaScript SEO: modern frameworks deliver a great experience but hide your content behind execution. This article walks through the trade-offs among CSR, SSR, and pre-rendering, and how to verify your content is actually being seen by crawlers. When crawl resources are tight, bad rendering wastes budget — check the crawl budget optimization guide so crawlers don’t waste effort on empty shells.

Let me start with my recommendation: pure client-side rendering (CSR) is unfriendly to crawlers by default, and critical content may never enter the index — rankings are out of the question. Server-side rendering (SSR) or static generation (SSG) outputs content directly in the HTML, the most stable and least effort. If you can’t change the architecture, use dynamic rendering/pre-rendering to hand crawlers a complete HTML as a transitional fallback. The verification method is comparing “raw HTML” against “rendered” — look only at whether the former contains body text, not the latter.

Does the crawler actually execute JS?

Google does execute JS, but with delays, budget limits, and not every engine can do it (Bing, Baidu, and various aggregator crawlers differ widely in capability). Betting your indexing fate on “the crawler will run JS” is too risky. The right approach: have the initial HTML already contain the core content, and let JS only enhance interactivity rather than deliver content. That’s the most stable path.

Here’s a quick judgment test: if your core content exists only after JS runs, then any “non-JS client” — RSS readers, some social scrapers, lightweight crawlers — can’t get it. SEO is only one casualty; accessibility and content distribution suffer too. So serving content directly in the HTML is the safer baseline and treats all users the same.

How to choose among the three rendering strategies

Option one, SSR: the server renders the complete HTML on the response, so both crawlers and people get the content. It suits dynamic pages that still need indexing — for example, lists that are personalized but structurally fixed. Option two, SSG: generate static HTML at build time; performance and crawlability are both good, and it fits content that stays relatively fixed, like docs and blogs. Option three, dynamic rendering: normal users get CSR; when a crawler user-agent is detected, return a pre-rendered complete version.

The three strategies aren’t mutually exclusive and can be mixed: core pages that need indexing use SSR/SSG; admin and interactive pages that don’t use CSR. The full technical framework is in the technical SEO handbook. When choosing, remember one rule: any page meant to be indexed by search engines must have body text in its raw HTML — that’s a non-negotiable red line, and any “we’ll fix it later” becomes technical debt.

Crawl strategies for JS sitesCSRClient-side rendering, hard to crawlSSRServer outputs HTML directlyPre-renderingDynamic rendering as fallbackVerifyCompare before/after rendering

Figure: JS and SPA rendering — key points (compiled by Operations GO)

Strategy Crawlability Best for
CSR pure client-side Weak Admin / non-indexed pages
SSR server-side Strong Dynamic pages that need indexing
SSG static generation Strong Relatively fixed content
Dynamic rendering Strong (fallback) Architecture can’t change

How to verify your content is being seen

Use GSC’s URL Inspection tool to check whether the “crawled HTML” contains body text; then pull the raw HTML directly with curl to confirm the key text is in the source, not only in JS. If both agree, your rendering pipeline is fine. If the raw HTML is an empty shell, prioritize switching to SSR/SSG, then dynamic rendering — don’t count on Google’s second render to cover every page.

Verification should cover “important but unpopular” pages — the homepage is usually handled specially and can mask problems on deeper pages. Run a few category and detail pages each, so you confirm the full-site rendering strategy actually works, not just a special case on the homepage while deep pages remain empty shells. Add these pages to monitoring and re-run monthly to be safe.

Three typical symptoms

Symptom one: indexed count is far below total pages, and the indexed ones are all empty shells — the crawler never got the body text. Symptom two: your own content ranks, but the snippet shows “loading” or a bunch of JS variable names, zero readability. Symptom three: traffic cliffs after switching engines (like Baidu) because it doesn’t run JS and reads the shell directly. Any one of these means you should go back to reviewing the rendering strategy.

Turn these symptoms into a monitoring dashboard: each week compare “indexed pages / total pages” and “does the indexed page’s first screen contain body text”. A persistently low ratio means the rendering problem is getting worse — early intervention beats putting out fires late. Writing “raw HTML must contain core content” into your frontend standards kills the problem at the root and gets new team members off on the right foot.

Integrating a pre-rendering service

When you can’t change the frontend architecture, pre-rendering is the cheapest fallback: run a separate service that returns pre-rendered static HTML for requests detected as crawler user-agents, while normal users still get CSR. Without touching business code, crawlers get the complete body text and the indexing problem resolves itself.

The key integration point is caching: cache pre-rendered results by URL with a sensible TTL so each request doesn’t render on the fly and slow the response. Cache invalidation must be linked to releases — refresh the pre-render for affected pages after content updates, or crawlers will keep getting stale versions for a long time, which is worse than not pre-rendering at all. For more complete practice, see the technical SEO handbook.

Non-indexed pages can safely use CSR

On the flip side, admin backends, logged-in areas, and purely interactive tool pages shouldn’t be indexed anyway — pure CSR is perfectly fine for them. Don’t give them SSR just for “consistency” and add unnecessary server load. There’s exactly one judgment criterion: should this page appear in search results? If yes, serve the HTML directly; if no, render however you like.

Make “needs indexing or not” the first switch in frontend rendering selection, and you save a huge amount of unnecessary SSR refactoring. Write clearly in your team standards which routes use SSR and which use CSR, and follow the table when new pages launch — then rendering strategy stops tangling with system architecture.

When it comes to execution, the order looks like this: first list all SPA pages and pull the raw HTML with curl to confirm whether core content is in the source; prioritize converting empty-shell pages to SSR or SSG so HTML outputs the body directly; for architecture you can’t change, integrate dynamic rendering to return a pre-rendered full version to crawlers; then verify page by page with GSC’s URL Inspection tool that the “crawled HTML” contains body text; finally, write “raw HTML must contain core content” into your frontend release standards to prevent regression. Rendering has no magic — hold onto the single rule “if it needs indexing, serve the HTML directly”, and SPAs will show up in search results just fine.

FAQ

Google executes JS, so why worry?

Google does execute it, but with delays and budget limits, and engines like Bing and Baidu are far weaker. Don’t bet your indexing on “the crawler will run JS” — serving core content in the initial HTML is the most stable approach.

Which rendering strategy should I choose?

Core pages that need indexing use SSR or SSG so HTML outputs the body directly; admin and non-indexed interactive pages use pure CSR. If you can’t change the architecture, use dynamic rendering to hand crawlers a pre-rendered HTML as a fallback.

How do I verify content is actually seen by crawlers?

Use GSC’s URL Inspection tool to check whether the “crawled HTML” has body text, then pull the raw HTML with curl to confirm key text is in the source. If both agree, the rendering pipeline is fine.

All my indexed pages are empty shells — what’s wrong?

It means the crawler never got the body text — the raw HTML has no content. Prioritize switching to SSR/SSG, then dynamic rendering; don’t count on Google’s second render to cover every page.

Can admin pages use pure CSR?

Yes. The criterion is exactly one: should this page appear in search results? If not, use CSR freely — don’t add server load for “consistency”.

Popular Tags
Scroll to Top