NotionCue
AI Visibility Platform
All systems live
Sign in →
AEO Guidellms.txt GeneratorRobots.txtBLUF TemplatesBlogChangelogAbout
← Blog
TechnicalAug 21, 2026·12 min read

JavaScript SEO: The Rendering Choices That Decide Whether Google Sees Your Content

Crawl delays run roughly 40 percent longer for JavaScript heavy sites, and Search Console data suggests around 80 percent of single page applications have crawl budget waste from pages queued for rendering that never get rendered. The framework you chose is making this decision for you.

SS
Sudhir Singh
Senior SEO & AEO Specialist · NotioncCue
⚛️

Google's rendering pipeline handles JavaScript. That statement is true and it creates a false sense of security, because the pipeline has a queue, the queue has delays, and a meaningful share of pages enter the queue and never come out.

Reported figures put crawl delays at roughly 40 percent longer for JavaScript heavy sites compared to server rendered equivalents. Search Console data from early 2026 suggests around 80 percent of single page applications have crawl budget waste, meaning pages queued for rendering that were never actually rendered.

This post covers the rendering architectures, when each one breaks, and the specific configurations that fix it in the frameworks most sites actually run.

Three Tiers of How Google Processes Your Pages

Immediate indexing. The server sends complete HTML. Google reads it, indexes it, done. No queue, no delay. This is what static HTML and server side rendered pages produce.

Deferred rendering. The server sends a JavaScript shell. Google queues the page for rendering, which may happen days or weeks later. When it runs, Chrome processes the JavaScript and captures the DOM.

Abandonment. The JavaScript is too slow or too complex to process within the rendering budget. The page is never fully rendered and whatever was in the initial HTML shell is all that gets indexed.

The goal of every technical decision below is keeping your pages in tier one.

SSR, SSG, and CSR: What Each Actually Sends

Server Side Rendering (SSR) generates complete HTML on every request. The server runs the JavaScript, builds the DOM, and sends the result. The browser receives a fully formed page.

Static Site Generation (SSG) does the same work at build time rather than at request time. The HTML exists as files before anyone asks for them. Fastest option, limited to content that does not change per request.

Client Side Rendering (CSR) sends a minimal HTML shell and a JavaScript bundle. The browser runs the bundle and builds the page. Google can render it too, but through the deferred queue.

The general position covered in the SSR versus CSR guide applies here with specific framework detail below.

Next.js: What to Configure and What to Stop Doing

Next.js defaults to SSR with the App Router, which is the right default. The problems appear when teams opt out of it without realising what they are giving up.

The 'use client' directive does not mean the component is client side rendered. It means the component hydrates on the client. If it sits inside a Server Component tree, the initial HTML still includes the content. The failure is when an entire route is a Client Component with no server rendered parent.

// This produces complete HTML at request time. Correct.
// app/products/[slug]/page.tsx
export default async function ProductPage({ params }) {
  const product = await getProduct(params.slug)
  return (
    <article>
      <h1>{product.name}</h1>
      <p>{product.description}</p>
      <ProductGallery images={product.images} /> {/* client component for interactivity */}
    </article>
  )
}

// ProductGallery is 'use client' for swipe/zoom, but the
// text content renders server side. Crawlers get the HTML.

For content that does not change per request, static generation at build time is faster and cheaper:

// This generates HTML at build time. Fastest option.
// app/blog/[slug]/page.tsx
export async function generateStaticParams() {
  const posts = await getAllPosts()
  return posts.map(post => ({ slug: post.slug }))
}

export default async function BlogPost({ params }) {
  const post = await getPost(params.slug)
  return <article dangerouslySetInnerHTML={{ __html: post.content }} />
}

Where teams break it: wrapping data fetching in useEffect inside a Client Component rather than using server side data fetching. That produces a page where the content arrives after hydration, which is the CSR failure pattern regardless of which framework produced it.

React Without Next.js

A vanilla Create React App or Vite based React SPA sends an empty div and a JavaScript bundle. The entire page is client side rendered and goes straight into Google's deferred rendering queue.

The options, in order of preference:

Migrate to a framework that handles SSR. Next.js, Remix, or any framework with server rendering support. This is the structural fix.

If migration is not feasible, prerendering is the middle path. Tools like react-snap or prerender.io generate static HTML snapshots that get served to crawlers. The user still gets the SPA experience and crawlers get readable HTML.

// prerender-spa-plugin configuration (webpack)
// Generates static HTML at build time for specified routes
const PrerenderSPAPlugin = require('prerender-spa-plugin')

module.exports = {
  plugins: [
    new PrerenderSPAPlugin({
      staticDir: path.join(__dirname, 'build'),
      routes: ['/', '/products', '/about', '/pricing'],
      renderer: new PrerenderSPAPlugin.PuppeteerRenderer({
        renderAfterDocumentEvent: 'render-complete'
      })
    })
  ]
}

The limitation: prerendering works for a fixed set of routes. A site with ten thousand product pages cannot practically prerender them all at build time.

Verifying What Google Actually Receives

The most reliable check is Search Console's URL Inspection tool. Inspect a live URL and view the rendered HTML. Compare that against your page source.

The faster manual check:

# Fetch what a non-rendering crawler sees
curl -s https://yoursite.com/page | grep -c "your unique content phrase"
# Returns 0 = content is not in the server response
# Returns 1+ = content is present in HTML

That single command distinguishes SSR from CSR on any URL and it takes seconds. A result of zero means every non rendering crawler, including all AI crawlers covered in the crawlers guide, sees nothing.

Metadata Has to Be Server Rendered Too

Title tags, meta descriptions, canonical tags, and schema markup set via client side JavaScript may not be present in the initial HTML. Google's renderer handles them when it runs, but the deferred queue means they may be read late or not at all.

In Next.js App Router, the metadata export or generateMetadata function handles this correctly by including it in the server response:

// app/products/[slug]/page.tsx
export async function generateMetadata({ params }) {
  const product = await getProduct(params.slug)
  return {
    title: product.name + ' | YourStore',
    description: product.shortDescription,
    alternates: { canonical: '/products/' + params.slug },
  }
}

In a CSR app, react-helmet or similar sets metadata client side, which is the deferred rendering problem applied to the tags that matter most for ranking. Move them to the server or to the prerendered snapshot. Schema markup faces the same issue, per the schema guide, since JSON-LD injected by JavaScript may not be present in the initial response.

Internal Links Must Be Crawlable HTML

Client side routing in SPAs uses JavaScript navigation rather than real anchor tags. A link implemented as an onClick handler is invisible to crawlers. Even router libraries that render anchor tags may use event.preventDefault() and push state rather than producing a genuine navigable link.

The requirement: every internal link should be a real <a> tag with an href attribute containing a URL. Framework router components like Next.js's Link do this correctly. Custom navigation using button clicks or div event handlers does not.

This matters for architecture as much as for individual pages. A site whose navigation exists only in JavaScript has, from a crawler's perspective, no internal links at all, which is the orphan page problem at site scale, per the architecture guide.

Hydration Errors Break Rendering Silently

A hydration mismatch occurs when the server rendered HTML and the client rendered DOM disagree. React logs a warning, continues with the client version, and the page works for the user.

The SEO problem: if the server rendered version is the one that was correct and the client version is different, Google indexed the server version, which may be incomplete. If the server version was the incomplete one and hydration was meant to fix it, Google sees the incomplete version.

Common causes: rendering dates or times that differ between server and client, conditionally rendering content based on window dimensions (which do not exist on the server), and using browser APIs during server rendering.

// This produces a hydration mismatch. Broken.
function Timestamp() {
  return <span>{new Date().toLocaleString()}</span>
}

// Fix: defer the dynamic part to the client
function Timestamp() {
  const [time, setTime] = useState(null)
  useEffect(() => setTime(new Date().toLocaleString()), [])
  return <span>{time ?? 'Loading...'}</span>
}

Lazy Loading Content vs Lazy Loading Components

Lazy loading a component's code bundle is a performance optimisation that affects how quickly the JavaScript loads. Lazy loading the content a component displays is a content visibility decision.

The distinction matters because they get conflated. React.lazy and next/dynamic defer the component's JavaScript. The server can still render a fallback or a loading state. As long as the actual content was in the initial server response, crawling is unaffected.

Content that loads via useEffect or after an intersection observer triggers is genuinely absent until the event fires, which a non rendering crawler never triggers. That is the image lazy loading problem from the image SEO guide applied to text content.

Testing Beyond Google

Google renders JavaScript. Most other crawlers, including the ones feeding AI systems, do not. A site that works for Googlebot because rendering eventually happens will fail for every non rendering crawler simultaneously.

The NotioncCue AI Crawler Audit checks what specific crawlers receive from a URL, which catches the gap between what Google's renderer processes and what a direct HTTP request returns.

Start your free NotioncCue trial and test a page that uses client side data fetching. The server response is almost always thinner than the team assumes, and for non Google crawlers, the server response is all there is.

One command that tells you where you stand: curl -s yoursite.com/important-page | wc -c compared to saving the rendered page from your browser and checking that file's size. A large gap means a large share of your content is JavaScript dependent.

Common Questions

Does Google render all JavaScript now?
It renders most of it, with delays. The queue, the rendering budget, and the complexity threshold mean not all pages get rendered, and those that do may wait days or weeks. Server rendering removes the dependency entirely.

Is dynamic rendering still recommended?
Google deprecated its recommendation for dynamic rendering (serving different content to crawlers versus users) in 2024. SSR is the preferred approach. Dynamic rendering is a legacy workaround, not a current best practice.

Do AI crawlers render JavaScript?
Generally no. GPTBot, ClaudeBot, and similar fetch HTML and read what they receive. Content that requires rendering is invisible to them regardless of how well Google handles it, per the crawlers guide.

Share this post
Check your AEO score
Scan your domain free — get your AI visibility score across 5 LLMs in 30 seconds.
Scan my site →
SS
Sudhir Singh
Senior SEO & AEO Specialist · NotioncCue

Senior SEO and AEO specialist with 12+ years across e-commerce, global education, and healthcare. Building Notion Cue to track brand citations across ChatGPT, Perplexity, Gemini, and AI Overviews.

View all →
Get AEO updates weekly.

Citation shifts, algorithm changes, and what's actually working.