PerformanceReactMedium

How do SPAs impact SEO?

01

The Short Answer

Single Page Applications (SPAs) hurt SEO by default because search engine crawlers receive an empty HTML shell — the content is rendered by JavaScript after the page loads. While Googlebot can execute JavaScript, it's slower, less reliable, and has a separate rendering queue that can delay indexing by days or weeks. Other search engines (Bing, DuckDuckGo, Baidu) have even less JavaScript rendering capability. The fix is to move critical content rendering to the server using SSR, SSG, or ISR — so crawlers get fully-rendered HTML on the first request.

02

Why SPAs Are Problematic for Crawlers

When a crawler requests a traditional server-rendered page, it gets complete HTML with all the content, headings, links, and metadata. When it requests a SPA, it gets something like <div id='root'></div> and a bunch of JavaScript files. The crawler then has to execute that JavaScript to see the actual content — and this is where things break down.

  • Crawler requests the URL and receives the HTML response
  • HTML contains only a shell div and script tags — no visible content
  • Crawler must execute JavaScript to render the page (expensive, slow)
  • Google's renderer has a separate queue — pages wait hours to days for JS rendering
  • Dynamic content loaded via API calls may not be waited for at all
  • Links rendered by JS may not be discovered for crawling
spa-vs-ssr-response.htmlhtml
<!-- What a SPA sends to crawlers (before JS executes) -->
<!DOCTYPE html>
<html>
<head>
  <title>My App</title> <!-- Often generic or missing -->
</head>
<body>
  <div id="root"></div>
  <script src="/bundle.js"></script>
  <!-- Crawler sees: empty page -->
  <!-- No headings, no content, no internal links -->
</body>
</html>

<!-- What SSR/SSG sends to crawlers (immediately) -->
<!DOCTYPE html>
<html>
<head>
  <title>10 React Performance Tips for 2024 | MyBlog</title>
  <meta name="description" content="Learn how to optimize..." />
  <link rel="canonical" href="https://myblog.com/react-performance" />
</head>
<body>
  <header><nav>...</nav></header>
  <main>
    <article>
      <h1>10 React Performance Tips for 2024</h1>
      <p>React applications can become slow as they grow...</p>
      <!-- Full content visible immediately -->
    </article>
  </main>
  <script src="/bundle.js"></script> <!-- Hydrates for interactivity -->
</body>
</html>
03

Specific SEO Problems in SPAs

Beyond the rendering issue, SPAs introduce several structural problems that affect how search engines understand and rank your content. These aren't just theoretical — they directly impact whether your pages appear in search results and how they're displayed.

ProblemImpactSolution
Single URL for all contentCrawlers can't index individual pagesUse client-side routing with real URL paths
Hash-based routing (#/page)Most crawlers ignore hash fragmentsSwitch to History API (pushState) routing
Missing meta tagsNo title/description in search resultsRender meta tags server-side or use react-helmet-async
No internal links in initial HTMLCrawlers can't discover other pagesSSR/SSG renders navigation links in HTML
Slow Time to First Contentful PaintCore Web Vitals penaltyServer-render above-the-fold content
API-dependent contentCrawler may not wait for async dataFetch data server-side, embed in HTML
JavaScript errors block renderingEntire page invisible to crawlerSSR provides fallback content regardless of JS
04

Solutions: SSR, SSG, and ISR

Modern React frameworks solve the SPA-SEO problem by rendering pages on the server. The three main strategies differ in when the HTML is generated — at build time, at request time, or a hybrid of both. Each has tradeoffs between freshness, performance, and infrastructure complexity.

Static Site Generation (SSG)

Pages are rendered to HTML at build time and served as static files. Best for content that doesn't change often — blog posts, documentation, marketing pages. Fastest possible response time (just serving a file), perfect for CDN caching, and crawlers always get complete HTML. The downside is that content is only as fresh as your last build.

Server-Side Rendering (SSR)

Pages are rendered to HTML on every request. Best for dynamic, personalized, or frequently-changing content — dashboards, search results, e-commerce product pages with live pricing. Crawlers always get fresh content, but you need a server running and response times are slower than static files.

Incremental Static Regeneration (ISR)

Pages are statically generated but revalidated in the background after a configurable time period. Combines the speed of SSG with the freshness of SSR — serves the cached static page instantly while regenerating it in the background for the next visitor. Ideal for content that changes periodically but doesn't need real-time accuracy.

rendering-strategies.tsxtsx
// Next.js App Router examples

// SSG — generated at build time (default for pages without dynamic data)
export default async function BlogPost({ params }: { params: { slug: string } }) {
  const post = await getPost(params.slug); // fetched at build time
  return <article><h1>{post.title}</h1><p>{post.content}</p></article>;
}

// Generate static paths at build time
export async function generateStaticParams() {
  const posts = await getAllPosts();
  return posts.map(post => ({ slug: post.slug }));
}

// ISR — static but revalidates every 60 seconds
export const revalidate = 60; // seconds

export default async function ProductPage({ params }: { params: { id: string } }) {
  const product = await getProduct(params.id);
  return <div><h1>{product.name}</h1><p>${product.price}</p></div>;
}

// SSR — rendered fresh on every request (no caching)
export const dynamic = 'force-dynamic';

export default async function SearchResults({ searchParams }: { searchParams: { q: string } }) {
  const results = await search(searchParams.q);
  return <ul>{results.map(r => <li key={r.id}>{r.title}</li>)}</ul>;
}
05

Essential SEO Metadata

Even with server rendering, you need proper metadata for each page. Search engines use title tags, meta descriptions, canonical URLs, and structured data to understand and display your content in search results. In Next.js App Router, you export a metadata object or generateMetadata function from each page.

seo-metadata.tsxtsx
import { Metadata } from 'next';

// Static metadata
export const metadata: Metadata = {
  title: 'React Performance Guide | MyBlog',
  description: 'Learn 10 proven techniques to optimize React app performance.',
  openGraph: {
    title: 'React Performance Guide',
    description: 'Learn 10 proven techniques to optimize React app performance.',
    url: 'https://myblog.com/react-performance',
    type: 'article',
  },
  alternates: {
    canonical: 'https://myblog.com/react-performance',
  },
};

// Dynamic metadata (for pages with params)
export async function generateMetadata({ params }: { params: { slug: string } }): Promise<Metadata> {
  const post = await getPost(params.slug);
  return {
    title: `${post.title} | MyBlog`,
    description: post.excerpt,
    openGraph: {
      title: post.title,
      description: post.excerpt,
      images: [{ url: post.coverImage }],
    },
  };
}

// Structured data (JSON-LD) for rich search results
export default function BlogPost({ post }) {
  const jsonLd = {
    '@context': 'https://schema.org',
    '@type': 'Article',
    headline: post.title,
    datePublished: post.publishedAt,
    author: { '@type': 'Person', name: post.author },
  };

  return (
    <>
      <script type="application/ld+json" dangerouslySetInnerHTML={{ __html: JSON.stringify(jsonLd) }} />
      <article>...</article>
    </>
  );
}
06

Why Interviewers Ask This

This question tests whether you understand the tradeoffs of SPA architecture beyond just developer experience. Interviewers want to see that you know why client-rendered content is problematic for crawlers, can explain SSR/SSG/ISR and when to use each, understand that Google can render JS but it's slow and unreliable, know about metadata, canonical URLs, and structured data, and can make architecture decisions that balance UX, DX, and discoverability.

Quick Revision Cheat Sheet

Core problem: SPAs send empty HTML — crawlers may not execute JS to see content

Google's JS rendering: Works but delayed (separate queue) — not guaranteed for all content

SSG: Build-time HTML — fastest, best for static content, stale until rebuild

SSR: Request-time HTML — always fresh, needs a server, slower TTFB

ISR: Static + background revalidation — speed of SSG, freshness of SSR

Must-haves: Unique titles, meta descriptions, canonical URLs, proper heading hierarchy, internal links in HTML