January 10, 2024
10 min read

Next.js SEO Best Practices: From Setup to Production

Learn how to optimize Next.js applications for search engines with App Router, metadata API, and rendering strategies.

Next.js is built for SEO, but there are still best practices and pitfalls to watch for. This guide covers everything from metadata management to sitemap generation for production Next.js apps in 2024.

Choosing the Right Rendering Strategy

Next.js offers three rendering strategies. Choose wisely:

Static Site Generation (SSG)

Best for content that doesn't change often (marketing pages, blogs, docs). Pages are built at compile time.

// app/blog/[slug]/page.tsx
export async function generateStaticParams() {
  const posts = await getPosts();
  return posts.map((post) => ({ slug: post.slug }));
}

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

SEO impact: Excellent. HTML is instant, crawlers get full content immediately.

Server-Side Rendering (SSR)

Best for dynamic content (user dashboards, personalized pages, real-time data). Pages are rendered per request.

// Force dynamic rendering
export const dynamic = 'force-dynamic';

export default async function Dashboard() {
  const data = await fetchUserData();
  return <div>{data.name}</div>;
}

SEO impact: Good. HTML is generated server-side, but slower than SSG.

Client-Side Rendering (CSR)

Use sparingly for authenticated or highly interactive content. Mark components with 'use client'.

'use client';
import { useState, useEffect } from 'react';

export default function InteractiveWidget() {
  const [data, setData] = useState(null);
  useEffect(() => {
    fetch('/api/data').then(r => r.json()).then(setData);
  }, []);
  return <div>{data?.value}</div>;
}

SEO impact: Poor. Content is invisible until JavaScript runs. Avoid for public-facing pages.

Metadata API (App Router)

Next.js 13+ App Router provides a powerful metadata API for SEO:

Static Metadata

// app/page.tsx
export const metadata = {
  title: 'Home | My Site',
  description: 'Welcome to my site',
  openGraph: {
    title: 'Home | My Site',
    description: 'Welcome to my site',
    url: 'https://example.com',
    siteName: 'My Site',
  },
};

Dynamic Metadata

// app/products/[id]/page.tsx
export async function generateMetadata({ params }) {
  const product = await fetchProduct(params.id);
  return {
    title: `${product.name} | My Store`,
    description: product.description,
    openGraph: {
      title: product.name,
      description: product.description,
      images: [product.image],
    },
  };
}

This ensures every page has unique, accurate metadata in the initial HTML — critical for SEO.

Structured Data with JSON-LD

Add structured data to help search engines understand your content:

export default function ProductPage({ product }) {
  const jsonLd = {
    '@context': 'https://schema.org',
    '@type': 'Product',
    name: product.name,
    description: product.description,
    offers: {
      '@type': 'Offer',
      price: product.price,
      priceCurrency: 'USD',
    },
  };

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

Sitemap and Robots.txt

Next.js supports file-based sitemap and robots.txt:

// app/sitemap.ts
export default async function sitemap() {
  const posts = await getPosts();
  const postUrls = posts.map((post) => ({
    url: `https://example.com/blog/${post.slug}`,
    lastModified: post.updatedAt,
  }));

  return [
    { url: 'https://example.com', lastModified: new Date() },
    ...postUrls,
  ];
}
// app/robots.ts
export default function robots() {
  return {
    rules: {
      userAgent: '*',
      allow: '/',
      disallow: '/admin/',
    },
    sitemap: 'https://example.com/sitemap.xml',
  };
}

Image Optimization

Use Next.js Image component for automatic optimization:

import Image from 'next/image';

<Image
  src={product.image}
  alt={product.name}
  width={800}
  height={600}
  priority={true} // for LCP images
/>

Always include alt text for accessibility and SEO. Use priority for above-the-fold images.

Canonical URLs

Set canonical URLs in metadata to prevent duplicate content issues:

export const metadata = {
  alternates: {
    canonical: 'https://example.com/products/123',
  },
};

Internationalization (i18n)

For multi-language sites, use Next.js i18n with proper hreflang tags:

export const metadata = {
  alternates: {
    languages: {
      'en-US': 'https://example.com/en-US',
      'de-DE': 'https://example.com/de-DE',
    },
  },
};

Common Next.js SEO Mistakes

1. Using Client Components Everywhere

Don't add 'use client' unless necessary. Server Components render on the server by default — better for SEO.

2. Dynamic Imports Without SSR

// BAD: Skips server rendering
const Component = dynamic(() => import('./Component'), { ssr: false });

// GOOD: Renders server-side
const Component = dynamic(() => import('./Component'));

3. Fetching Data in useEffect

Client-side data fetching means content isn't in initial HTML. Use Server Components or getServerSideProps (Pages Router).

Testing Your Next.js SEO

  1. Run WatchThis on your production URL
  2. Verify title, meta description, and canonical are in raw HTML
  3. Check that H1 and primary content appear before JavaScript runs
  4. Use Google Search Console's URL Inspection tool
  5. Run Lighthouse audits for performance and SEO

Conclusion

Next.js makes React SEO straightforward, but you must choose the right rendering strategy and avoid client-side pitfalls. Use the Metadata API, generate sitemaps, and always test with WatchThis before deploying.

Check your Next.js app now to ensure search engines see your content correctly.