← Back to Blog

Technical SEO

JavaScript SEO: When Your Beautiful Website Is a Blank Page to Google

Let me tell you about a very bad six weeks.

A company — established brand, strong organic traffic history, real revenue dependency on search — launched a brand new product line as a separate website. The development team chose Next.js. The stack was modern, the performance was excellent, and the frontend was genuinely impressive.

There was one problem.

Everything was delivered via JavaScript. The page titles. The meta descriptions. The H1 tags. The canonical tags. The internal navigation. The product descriptions. Every piece of content and every technical SEO signal on every single page — loaded client-side, after the initial HTML shell had been served.

From a browser: perfect. Fast. Responsive. Everything exactly where it should be.

From Googlebot’s perspective: four thousand pages of empty HTML shells.

The site launched. The team waited for rankings. A week passed. Two weeks. A month. Six weeks.

Organic impressions in Google Search Console: near zero. Pages indexed: a handful of top-level URLs. The four thousand product pages this business was depending on for revenue — completely invisible to Google.

This was not a blog. This was a new product line with commercial intent, real search demand, and a business forecast built on organic traffic arriving within weeks of launch.

It did not arrive. Because Google could not see the website.

The fix — migrating to Server-Side Rendering so all critical content was present in the initial HTML response — took the development team two weeks to implement.

Two weeks to fix. Six weeks invisible. For a product launch.

JavaScript SEO mistakes are not always this catastrophic. But the severe ones are very, very severe.

How Google Crawls JavaScript — The Two-Wave Problem

Traditional HTML pages are straightforward to crawl. Googlebot requests the URL, the server returns the full HTML content immediately. Googlebot reads the content. Done.

JavaScript-rendered pages work differently. When Googlebot requests a JavaScript-heavy page, the initial response is a largely empty HTML document — a shell. The actual content is populated by JavaScript that executes in the browser after the initial HTML loads.

Googlebot has to handle this in two separate processes:

Wave 1 — Initial crawl: Googlebot fetches the raw HTML. If the content is already there (server-side rendered), it reads it immediately. If the page is client-side rendered, Googlebot sees the empty shell and queues it for rendering.

Wave 2 — Rendering queue: Google’s rendering infrastructure runs a headless browser to execute the JavaScript and generate the fully rendered page. This content is then indexed.

The problem is the gap between Wave 1 and Wave 2. Pages can sit in the rendering queue for hours, days, or weeks. Google’s rendering capacity is limited. Pages from newer, lower-authority sites sit longer. And some pages never get rendered at all — particularly if the JavaScript is complex, dependent on user interactions, or throws errors during rendering.

For a new product launch, waiting weeks for Google’s rendering queue to process four thousand pages is not a viable go-to-market strategy.


Client-Side Rendering vs Server-Side Rendering vs Static Generation

Client-Side Rendering (CSR)
The server sends an empty HTML shell. JavaScript executes in the browser and populates the page with content.

  • User experience: fast subsequent navigation once JS bundle is loaded
  • SEO impact: Google must render the JS to see content — delays, rendering queue dependency, risk of content never being seen

Server-Side Rendering (SSR)
The server generates the full HTML — including all content, meta tags, and structured data — on each request, before sending it to the browser.

  • User experience: slower initial page generation but fully rendered HTML on arrival
  • SEO impact: Google sees all content immediately in Wave 1. No rendering queue dependency.

Static Site Generation (SSG)
HTML is pre-built at deploy time and served as static files. The full content is in the HTML from the first request.

  • User experience: extremely fast — static files are served directly from CDN
  • SEO impact: excellent — same as SSR, Google sees everything immediately

Incremental Static Regeneration (ISR) — Next.js specific
A hybrid: pages are statically generated but can be regenerated in the background after a set interval. Good for content that changes periodically but doesn’t need real-time updates.

For SEO purposes: SSR and SSG are both correct approaches. CSR for pages Google needs to index is a risk — the severity depends on site authority, content type, and how much of the page depends on JavaScript.


What Google Can and Cannot See

Google can typically handle:

  • JavaScript that’s straightforward and standard — React, Vue, Angular components rendering on a simple page
  • JavaScript-rendered content on high-authority sites that Google allocates substantial rendering resources to
  • Lazy-loaded images with standard loading="lazy" attributes

Google struggles with or may miss entirely:

  • Content that only loads after user interaction (scroll, click, hover) — Googlebot doesn’t simulate user behaviour
  • Content behind authentication or login walls
  • JavaScript that throws errors during rendering — the page may partially render or not render at all
  • Infinite scroll without HTML pagination links — Googlebot cannot simulate scrolling
  • Content loaded from external APIs at render time that are slow or unavailable during Googlebot’s rendering pass
  • Complex JavaScript applications on newer, lower-authority domains — less rendering budget allocated

The critical rule: if content is important for Google to see and rank, it must be in the initial HTML. Do not make Google earn it.


The Specific Things That Break in JavaScript SEO

Page titles and meta descriptions loaded via JS. If your <title> tag and <meta name="description"> are populated by JavaScript after the initial HTML loads, Wave 1 sees an empty title. Google may generate its own title from on-page content — which may not be what you want.

Canonical tags loaded via JS. A canonical tag that only exists after JavaScript executes may be ignored entirely by Google. Canonical tags must be in the static HTML.

Internal links in JavaScript navigation. If your site navigation is rendered via JavaScript and the links don’t exist in the initial HTML, Googlebot may not discover linked pages through internal link crawling. This directly impacts the discoverability of every page in your site.

Structured data/schema injected by JS. Schema markup that only appears in the rendered page (not the static HTML) may or may not be processed depending on when the page gets rendered. Server-side schema delivery is always more reliable.

Hreflang tags in JS. International sites with JavaScript-delivered hreflang tags face the same problem — the language/region directives may not be read in Wave 1.

Content for indexing behind tabs, accordions, or dynamic components. Content that only becomes visible in the DOM after a user click is treated as less indexable than always-visible content — even if Googlebot can eventually render it. Don’t hide important content behind interactive elements that require simulation.


How to Test What Google Actually Sees

Google Search Console — URL Inspection → Test Live URL

This is the most important test. Go to Search Console → URL Inspection → enter your URL → click “Test Live URL” → click “View Tested Page.”

This shows you what Google’s renderer actually sees when it processes your page — the rendered DOM, not the source HTML. Compare the rendered page to your live page. Any missing content is content Google isn’t reliably indexing.

Check specifically:

  • Is the full body content present?
  • Are title and meta description present?
  • Are canonical tags present?
  • Is structured data visible?
  • Are all internal navigation links present?

View Page Source vs. Inspect Element

CTRL+U (View Page Source) shows the raw HTML your server delivers — what Google sees in Wave 1.

Right-click → Inspect shows the rendered DOM after JavaScript has executed — what a browser sees.

If critical content appears in Inspect but not in View Page Source — that content is JavaScript-rendered and subject to the Wave 2 problem.

Google’s Rich Results Test

For structured data specifically, paste your URL into the Rich Results Test. If it can only detect schema in the “rendered” version and not the raw HTML, your schema is JavaScript-dependent.

Screaming Frog with JavaScript rendering disabled

In Screaming Frog settings, disable JavaScript rendering and crawl your site. The pages Screaming Frog sees without JS execution is close to what Google’s Wave 1 sees. Missing titles, descriptions, and links on the non-JS crawl are the things most at risk.


How to Fix JavaScript SEO Problems

Move to Server-Side Rendering or Static Generation for all critical pages

For Next.js: use getServerSideProps for SSR or getStaticProps for SSG on pages that need to be indexed. Any page critical for organic traffic should be rendering its content server-side.

For other frameworks: verify that the production build is configured to SSR or SSG — not just the development environment.

Ensure all critical tags are in the static HTML

Title tags, meta descriptions, canonical tags, hreflang tags, and Open Graph tags must all be present in the initial HTML response. If you’re using a head management library (React Helmet, Next.js <Head>), verify that the static HTML output includes these tags — not just the browser-rendered version.

Use real <a href> links for all navigation

JavaScript-based navigation that changes the view without actual <a href> links (common in single-page applications) means Googlebot cannot follow links to discover other pages. Every page that should be crawled needs a real HTML anchor link pointing to it from somewhere else on the site.

Implement lazy loading correctly

Lazy loading images (loading="lazy") is correct and recommended for off-screen images. Lazy loading main body text, headings, or navigation links is not. Only use lazy loading for resources that are genuinely non-critical for initial page understanding.

For infinite scroll: implement paginated HTML fallbacks

If your site uses infinite scroll for content feeds or product listings, ensure there are also proper HTML pagination links (/page/2//page/3/) that Googlebot can follow. Infinite scroll without HTML links means Google only ever sees what’s loaded on page 1.


JavaScript SEO and Next.js Specifically

Since Next.js is the framework where JavaScript SEO problems most commonly arise in modern builds, a specific note:

Next.js is exceptional for SEO — when used correctly. It supports SSR, SSG, and ISR natively, and was specifically designed with these capabilities. The technology is not the problem.

The problem is configuration and assumptions.

By default in recent Next.js versions, pages use a hybrid approach — some content is statically rendered, some is client-side. Client-side data fetching (useEffectuseState for API calls) means that content is not present in the initial HTML. If your product descriptions, prices, page titles, or category listings are fetched client-side, they are not reliably visible to Google.

The fix is architectural: move data that Google needs to see into getServerSideProps or getStaticProps. This ensures it’s embedded in the HTML response Google receives in Wave 1.

Before launching any Next.js site: check every key page template with View Page Source. If the content you want Google to rank for isn’t visible in the source, it’s client-side rendered. Fix it before launch. Not after six weeks of invisibility.


The TL;DR

  • Google crawls JavaScript in two waves — Wave 1 (raw HTML) is immediate; Wave 2 (rendering queue) can take days or weeks
  • Content, meta tags, canonicals, hreflang, and schema markup delivered only via JavaScript may not be reliably seen by Google
  • Client-Side Rendering = SEO risk for indexed content. Server-Side Rendering or Static Generation = correct approach for pages that need to rank
  • Test what Google sees using URL Inspection in Search Console — “Test Live URL” → “View Tested Page” shows the actual rendered DOM
  • View Page Source shows Wave 1. What’s missing from Page Source but visible in Inspect Element is JavaScript-rendered and at risk.
  • All navigation links must be real <a href> links — JavaScript-only navigation prevents Googlebot from discovering pages
  • Next.js supports SSR and SSG natively — use getServerSideProps or getStaticProps for all pages that need organic visibility

The most expensive JavaScript SEO mistakes happen on new product launches, on sites with strong traffic dependencies, and on development teams who tested in a browser and assumed what they saw was what Google saw.

It is not always what Google sees.

If your site is on a JavaScript framework and you’re seeing pages that should be indexed but aren’t — drop the URL in the comments. This is diagnosable and fixable. Subu has seen the worst version of this story. Yours is probably less severe.

— Subu, SEO by Subu

Subu Avatar

Written by the human behind Subu

(Usually typed between panic attacks and client calls)

  • Job: SEO Consultant, Comic Creator, and Content Writer
  • Diet: 90% Caffeine, 10% Panic
  • Mission: Fixing the internet's broken architecture, one ranking drop at a time.

If you need your traffic rescued, Hire the Human.

Need help with your SEO?

Book a project and get implementation-grade recommendations.

Hire Subu
Jump to section