Our SEO Journey: From SPA to Next.js (The Complete Playbook)

Published: (December 16, 2025 at 05:59 AM EST)
7 min read
Source: Dev.to

Source: Dev.to

Remember that moment?
You’ve just launched your shiny new Single Page Application (SPA). The user experience is buttery smooth, the client‑side magic is undeniable, and you’re feeling good. Then you check your analytics. Organic traffic? Crickets. You realize your beautifully animated, data‑rich content is largely invisible to search engines. I’ve been there, pulling my hair out, wondering why Googlebot seemed to be giving my masterpiece the cold shoulder.

It’s a tale as old as modern web development: SPAs built with frameworks like React, Vue, or Angular are fantastic for interactivity, but out‑of‑the‑box they render their content primarily on the client‑side using JavaScript. While modern search‑engine crawlers can execute JavaScript, it’s not their favorite pastime. It costs them more crawl budget, introduces potential rendering issues, and can significantly delay indexing. This often leaves critical content unseen, leading to a frustrating SEO bottleneck.

In our journey, the realization hit hard: we needed to serve fully‑formed HTML to crawlers before JavaScript took over. This is where Next.js, built on React, entered the scene—not just as a framework, but as a strategic SEO weapon. It allowed us to leverage the power of React while ensuring our content was discoverable. It wasn’t just a switch; it was a fundamental shift in how we approached web presence.

Next.js Rendering Strategies that Solve the SPA SEO Problem

StrategyWhen to Use
Server‑Side Rendering (SSR)Render pages on the server for each request.
Static Site Generation (SSG)Render pages at build time.
Incremental Static Regeneration (ISR)SSG, but with the ability to re‑generate pages in the background after deployment.

Let’s dive into how we actually made this work and the insights we gathered.

1️⃣ Server‑Side Rendering with getServerSideProps

For dynamic content that changes frequently or requires real‑time data, getServerSideProps was our go‑to. Think user dashboards, highly personalized content, or news feeds.

// pages/post/[id].tsx
import { GetServerSideProps } from 'next';

interface PostProps {
  post: {
    id: string;
    title: string;
    content: string;
  };
}

export const getServerSideProps: GetServerSideProps = async (context) => {
  const { id } = context.params!; // Get ID from dynamic route
  const res = await fetch(`https://api.example.com/posts/${id}`);
  const post = await res.json();

  if (!post) {
    return { notFound: true };
  }

  return {
    props: { post },
  };
};

function PostPage({ post }: PostProps) {
  return (
    <>
      <h2>{post.title}</h2>
      <p>{post.content}</p>
    </>
  );
}

export default PostPage;

Insight: While powerful, don’t overuse SSR. Each request triggers a server‑side render, which can add latency. Reserve it for truly dynamic, often authenticated, content. For public, frequently accessed pages, we looked elsewhere.

2️⃣ Static Generation with getStaticProps & getStaticPaths

This was the true SEO game‑changer for content‑heavy, static‑ish pages like blog posts, documentation, or product pages. getStaticProps fetches data at build time, generating HTML files that are lightning‑fast to serve via a CDN. getStaticPaths is crucial for dynamic routes, telling Next.js which paths to pre‑render.

// pages/blog/[slug].tsx
import { GetStaticProps, GetStaticPaths } from 'next';

interface BlogPostProps {
  post: {
    slug: string;
    title: string;
    body: string;
  };
}

export const getStaticPaths: GetStaticPaths = async () => {
  const res = await fetch('https://api.example.com/blog-posts-slugs'); // Get all slugs
  const slugs: string[] = await res.json();

  const paths = slugs.map((slug) => ({
    params: { slug },
  }));

  return { paths, fallback: 'blocking' }; // 'blocking' shows loading state, then renders
};

export const getStaticProps: GetStaticProps = async ({ params }) => {
  const { slug } = params!;
  const res = await fetch(`https://api.example.com/blog/${slug}`);
  const post = await res.json();

  if (!post) {
    return { notFound: true };
  }

  return {
    props: { post },
    revalidate: 60, // ISR: regenerate no more than once every 60 seconds
  };
};

function BlogPostPage({ post }: BlogPostProps) {
  return (
    <>
      <h2>{post.title}</h2>
      <article>{post.body}</article>
    </>
  );
}

export default BlogPostPage;

Insight: The revalidate property in getStaticProps is pure gold. It enables Incremental Static Regeneration (ISR), giving us the performance benefits of SSG while allowing content updates without a full redeploy. We used this heavily for blog posts and product pages, where content updates regularly but doesn’t need to be real‑time.

3️⃣ SEO‑Boosting Tools Built Into Next.js

Optimized Images with next/image

One of the lowest‑hanging fruits for Core Web Vitals. This component automatically optimizes image size, format (e.g., WebP), and serves responsive images, lazy‑loading them by default. It’s a huge win for page speed—a significant ranking factor.

import Image from 'next/image';

function MyComponent() {
  return (
    <Image
      src="/hero.jpg"
      alt="Hero image"
      width={1200}
      height={800}
      priority
    />
  );
}

Pitfall:

  • Forgetting to configure your image domains in next.config.js when pulling images from an external CDN.
  • Not setting width and height, which can cause layout shifts.

Head Management with next/head

Crucial for setting meta tags, titles, descriptions, canonical URLs, and Open Graph tags—vital for how your pages appear in search results and social previews.

import Head from 'next/head';

function BlogPostPage({ post }: BlogPostProps) {
  return (
    <>
      <Head>
        <title>{post.title} – My Awesome Blog</title>
        <meta name="description" content={post.body.slice(0, 150)} />
        {/* Add more OG/Twitter tags as needed */}
      </Head>

      <h2>{post.title}</h2>
      <article>{post.body}</article>
    </>
  );
}

Tip: Keep your meta data DRY by extracting a reusable component that accepts title, description, image, etc., and injects the appropriate tags.

4️⃣ Additional Next.js SEO Wins

FeatureSEO Benefit
Automatic sitemap.xml generation (via plugins like next-sitemap)Helps crawlers discover all pages.
Built‑in Link component with prefetchingReduces perceived load time, improving user experience signals.
Route‑based code splittingSmaller JavaScript bundles → faster page loads → better Core Web Vitals.
robots.txt support (via next-sitemap or custom file)Guides crawlers on what to index.

🎉 Takeaways

  1. Render what crawlers need, when they need it.

    • Use SSR for truly dynamic, personalized content.
    • Use SSG/ISR for static‑ish pages that still need occasional updates.
  2. Leverage Next.js’s built‑in optimizations.

    • next/image for performance‑first images.
    • next/head for robust meta‑tag management.
    • Plugins (next-sitemap, next-seo) to automate boilerplate SEO tasks.
  3. Monitor and iterate.

    • Check Google Search Console for crawl errors.
    • Use Lighthouse or PageSpeed Insights to verify Core Web Vitals.
    • Adjust revalidate intervals based on content freshness needs.

By swapping our SPA for a Next.js‑powered site and thoughtfully applying these strategies, we turned “crickets” into a steady stream of organic traffic. The SEO bottleneck vanished, and the site now enjoys both stellar performance and search‑engine visibility.


Ready to give your SPA a Next.js makeover? The playbook is in your hands—go build something searchable!

# SEO Tips for Next.js

When your pages appear in search results and are shared on social media, proper meta tags are essential.

```tsx
import Head from 'next/head';

function MyPage({ title, description, imageUrl, url }) {
  return (
    <Head>
      <title>{title}</title>
      <meta name="description" content={description} />
      {/* Open Graph Tags for social media sharing */}
      <meta property="og:title" content={title} />
      <meta property="og:description" content={description} />
      <meta property="og:image" content={imageUrl} />
      <meta property="og:url" content={url} />
      {/* ... other meta tags */}
    </Head>
  );
}

Common Pitfalls

  • Not dynamicizing your <Head> content
    Hard‑coding meta descriptions or titles makes every page look the same to crawlers. Each page needs its own unique, relevant metadata.

  • Over‑reliance on client‑side data fetching
    Even with Next.js, it’s easy to fall back to useEffect for data fetching. For SEO‑critical content, fetch the data before the component renders using getServerSideProps or getStaticProps.

  • Incorrect robots.txt and sitemaps
    Next.js handles rendering, but you still need a solid robots.txt to guide crawlers and an accurate sitemap.xml to list important pages. Generating sitemaps dynamically from getStaticPaths output works well.

  • Ignoring Lighthouse / PageSpeed Insights
    Next.js gives you the tools, but you must actively optimise. Regularly check Core Web Vitals and avoid large bundles, unoptimized fonts, or render‑blocking CSS that can negate server‑rendering benefits.

  • Misunderstanding fallback in getStaticPaths

    • fallback: false → 404 for ungenerated paths.
    • fallback: true → a fallback version is served, then the page is generated.
    • fallback: 'blocking' → the request waits for the page to be generated.
      Each mode has its use cases; applying the wrong one can hurt UX and SEO.

Our Experience

Our transition from a pure SPA to a Next.js‑powered application wasn’t just a technical refactor; it was a fundamental shift in product strategy. It taught us to think about content delivery from the crawler’s perspective as much as the user’s. The flexibility of Next.js allowed us to pick the right rendering strategy for each piece of content, leading to:

  • Significant boosts in organic search visibility
  • Faster page loads
  • A much happier engineering team

If you’re embarking on your own SEO journey, remember: it’s a marathon, not a sprint. Next.js provides a powerful engine, but strategic implementation, continuous monitoring, and a deep understanding of your content’s needs are what truly drive success.

🚀 Read on My Blog

Back to Blog

Related posts

Read more »