Technical SEO

    JavaScript SEO: How Google Crawls, Renders and Indexes JS Websites

    How Googlebot processes JavaScript sites, why CSR content gets missed, and how to choose between SSR, SSG and hydration — with debugging steps for React, Vue and headless builds.

    Published July 19, 202614 min readBy Mazam Khalid
    On this page

    Google does execute JavaScript, but not in one step and not with any timing guarantee. Understanding the two-pass process — crawl, then render — explains nearly every JavaScript SEO problem you will encounter, from missing content to metadata that never appears in search results.

    How Googlebot actually processes a JavaScript page

    1. Crawl. Googlebot requests the URL and receives the initial HTML — for a client-rendered app, often an empty <div id="root">.
    2. Queue for rendering. The URL enters a render queue. This can be minutes or considerably longer, depending on resources and site priority.
    3. Render. A headless Chromium instance executes the JavaScript, fetches APIs, and produces the rendered DOM.
    4. Index. Only now is the JS-generated content available for indexing, along with any links discovered during rendering.
    The practical consequence: anything that depends on rendering is discovered later and less reliably than anything present in the initial HTML. Links included.

    The failure modes, in order of frequency

    1. Content that depends on a client-side API call

    If the product description arrives from /api/products/123 after mount, an API timeout, a CORS failure or a 401 during rendering yields an indexed page with no content. Google will not retry immediately.

    2. Links that are not links

    <!-- Not crawlable: no href, discovered only if rendering succeeds -->
    <div onclick="navigate('/services/technical-seo-audits')">Audits</div>
    
    <!-- Crawlable -->
    <a href="/services/technical-seo-audits">Technical SEO audits</a>

    Router components in React and Vue generally render a real anchor with an href — but only if you use the framework's link component. Hand-rolled click handlers, buttons that call router.push, and infinite scroll without paginated URLs all create sections Google cannot reach.

    3. Blocked resources

    If robots.txt disallows the directory holding your JS bundle, rendering fails and the page is evaluated as a shell. Check the "View crawled page → More info → Page resources" list in URL Inspection for any resource Google could not load.

    4. Metadata written after hydration

    Client-side helpers mutate document.head after the app boots. Googlebot generally picks this up post-render. Facebook, LinkedIn, Slack and WhatsApp do not — they read the raw HTML response. That is why a JS-only og:image produces a blank link preview even when the page ranks fine.

    5. Soft 404s from client-side routing

    An SPA rendering a "Not found" component still returns HTTP 200. Handle unknown routes on the server so the correct status code is emitted.

    Choosing a rendering strategy

    StrategyIndexing reliabilityBest for
    Static generation (SSG)HighestMarketing sites, docs, blogs — content that changes on a deploy cadence
    Server-side rendering (SSR)HighCatalogues, listings, personalised-but-public pages
    Incremental / cached SSRHighLarge catalogues where full rebuilds are impractical
    Client-side rendering (CSR)VariableAuthenticated dashboards and app screens that should not be indexed anyway
    Dynamic renderingWorkaroundLegacy applications awaiting a rewrite

    The useful rule: public, indexable content should exist in the HTML response. Everything behind a login can be client-rendered freely.

    Debugging workflow

    1. View source, not DevTools. curl https://example.com/page shows the initial HTML. DevTools shows the post-hydration DOM and hides the problem.
    2. URL Inspection → Test live URL → View crawled page. Search the rendered HTML for a unique sentence from your main content.
    3. Check the resources tab for blocked or failed assets.
    4. Check the JavaScript console output in the live test for uncaught errors during render.
    5. Crawl with JS rendering enabled and disabled and compare URL counts. A large gap tells you how much of the site depends on rendering.
    Googlebot renders with an evergreen but stateless Chromium. No cookies persist between renders, no local storage, no service worker. Any content gated behind a stored consent flag, a session, or a geolocation prompt is invisible.

    Performance is part of the problem

    Heavy JavaScript hurts twice: it delays rendering for Google and damages Interaction to Next Paint for users. Practical measures that help both:

    • Route-level code splitting so a landing page does not ship the dashboard bundle.
    • Server-render above-the-fold content; hydrate below-the-fold components lazily.
    • Keep third-party scripts out of the critical path and load them after interaction where possible.
    • Set a bundle-size budget in CI so regressions are caught before deployment.

    Framework-specific notes

    • React: Next.js App Router with Server Components keeps content and metadata in the HTML response. A plain Vite SPA does not — for content sites, that difference matters.
    • Vue: Nuxt in universal mode is the equivalent choice.
    • Headless WordPress / Shopify: render at the edge or statically; do not let the storefront depend on client-side API calls for product copy.
    • Laravel with Inertia or Livewire: both server-render by default, which is why Laravel applications rarely have this class of problem — see the Laravel SEO guide.

    A short pre-launch checklist

    1. Main content appears in view-source, not just in DevTools.
    2. Every navigational element is an <a href>.
    3. Title, description and canonical are in the HTML response.
    4. Unknown routes return HTTP 404 from the server.
    5. JS and CSS directories are crawlable in robots.txt.
    6. Paginated and filtered states have real URLs where they should be indexable.
    7. Live test in URL Inspection shows the complete rendered content.

    Put this into practice

    Move from diagnosis to implementation with a focused engagement for your stack.

    Technical SEO Services

    Frequently asked questions

    Can Google index a client-side rendered React site?
    Usually yes, but with a delay and less reliability than server-rendered HTML. Rendering is a second pass with no guaranteed timing, and any JS error, blocked resource or timeout means the content is evaluated as an empty shell.
    Is dynamic rendering still recommended?
    Google now treats it as a workaround rather than a long-term solution. It is acceptable as a stopgap for a legacy application you cannot re-architect, but SSR or static generation is the durable answer.
    Do meta tags set by JavaScript work?
    For Googlebot, usually — it applies them after rendering. For social preview crawlers, no: most of them read only the initial HTML response, so a client-side title or og:image never appears in link previews.

    React or Vue site not ranking?

    I diagnose rendering-related indexing problems in JavaScript applications and implement the routing, metadata and rendering strategy that makes the content visible to search engines.

    Mazam Khalid — Technical SEO & AI Search Specialist

    Mazam Khalid works across technical SEO, Google Search Console, indexing and crawl optimization, AI Search Optimization, GEO/AEO, structured data, Core Web Vitals, WordPress, Shopify, Laravel, React and website performance. He also works on Google Ads, Meta Ads, analytics and conversion tracking where technical implementation affects search visibility and lead generation.

    Chat on WhatsApp