Back to Blog
TechnicalFeb 12, 2026

Next.js SEO Checklist for Production Websites

Most Next.js sites fail at SEO. Not because of content—but because of wrong setup. This checklist fixes that.

22 min read
Published Feb 12, 2026

Last updated: Feb 12, 2026

Disclosure: Some links in this post are affiliate links. I may earn a commission at no extra cost to you if you sign up or buy through them. I only recommend tools I use and trust.

Introduction

Most Next.js sites fail at SEO.

Not because of content—but because of wrong setup.

This Next.js SEO checklist fixes that. Use it to ensure your production site is optimized for indexing, speed, schema, and rankings. For hosting, I deploy on Vercel (see my Vercel review)—zero config and great for Next.js SEO.

1. Rendering Setup

In Next.js App Router, use Server Components by default. They render on the server, making content immediately available to crawlers.

✅ Use Server Components (Default)

// app/blog/page.tsx - Server Component (default)
export default async function BlogPage() {
  const posts = await getPosts() // Server-side data fetch
  return (
    <div>
      {posts.map(post => (
        <article key={post.id}>
          <h2>{post.title}</h2>
          <p>{post.content}</p>
        </article>
      ))}
    </div>
  )
}

Why: Server Components render HTML on the server. Google crawlers see fully rendered content immediately. No JavaScript required.

❌ Avoid Client Components for SEO Content

// ❌ BAD: Client Component for content
'use client'
export default function BlogPage() {
  const [posts, setPosts] = useState([])
  useEffect(() => {
    fetch('/api/posts').then(res => res.json()).then(setPosts)
  }, [])
  // Crawlers won't see this content!
}

Problem: Client Components require JavaScript. Crawlers may not execute JS, so content won't be indexed.

✅ Use ISR for Dynamic Content

// app/products/[id]/page.tsx
export const revalidate = 3600 // Revalidate every hour

export default async function ProductPage({ params }) {
  const product = await getProduct(params.id)
  return <ProductDetail product={product} />
}

Why: Incremental Static Regeneration (ISR) pre-renders pages at build time and regenerates them on demand. Best of both worlds: fast static pages + fresh content.

2. Metadata

Export metadata from your page.tsx or layout.tsx. Next.js App Router uses the Metadata API.

✅ Static Metadata

// app/blog/page.tsx
import type { Metadata } from 'next'

export const metadata: Metadata = {
  title: 'Blog | My Site',
  description: 'Read our latest blog posts',
  alternates: {
    canonical: 'https://www.mysite.com/blog',
  },
  openGraph: {
    title: 'Blog | My Site',
    description: 'Read our latest blog posts',
    url: 'https://www.mysite.com/blog',
    siteName: 'My Site',
    type: 'website',
    images: [{ url: 'https://www.mysite.com/og-image.jpg' }],
  },
  twitter: {
    card: 'summary_large_image',
    title: 'Blog | My Site',
    description: 'Read our latest blog posts',
    images: ['https://www.mysite.com/og-image.jpg'],
  },
  robots: {
    index: true,
    follow: true,
  },
}

export default function BlogPage() {
  return <div>...</div>
}

✅ Dynamic Metadata

// app/blog/[slug]/page.tsx
import type { Metadata } from 'next'

export async function generateMetadata({ params }): Promise<Metadata> {
  const post = await getPost(params.slug)
  
  return {
    title: post.title + ' | My Site',
    description: post.excerpt,
    alternates: {
      canonical: 'https://www.mysite.com/blog/' + params.slug,
    },
    openGraph: {
      title: post.title,
      description: post.excerpt,
      url: 'https://www.mysite.com/blog/' + params.slug,
      type: 'article',
      publishedTime: post.publishedAt,
      images: [post.ogImage],
    },
  }
}

Key points:

  • Always include alternates.canonical with full URL (www domain)
  • Set robots: { index: true, follow: true } unless page should be excluded
  • Include OpenGraph and Twitter Card for social sharing
  • Use generateMetadata for dynamic routes

✅ Root Layout Metadata

// app/layout.tsx
import type { Metadata } from 'next'

export const metadata: Metadata = {
  metadataBase: new URL('https://www.mysite.com'),
  title: {
    default: 'My Site',
    template: '%s | My Site',
  },
  description: 'Default site description',
  openGraph: {
    type: 'website',
    locale: 'en_US',
    siteName: 'My Site',
  },
}

Why: metadataBase sets the base URL for relative URLs. title.template allows child pages to inherit the site name.

3. Sitemap

Create app/sitemap.ts. Next.js serves it at /sitemap.xml automatically.

✅ Static Sitemap

// app/sitemap.ts
import type { MetadataRoute } from 'next'

export default function sitemap(): MetadataRoute.Sitemap {
  return [
    {
      url: 'https://www.mysite.com',
      lastModified: new Date(),
      changeFrequency: 'daily',
      priority: 1,
    },
    {
      url: 'https://www.mysite.com/blog',
      lastModified: new Date(),
      changeFrequency: 'weekly',
      priority: 0.8,
    },
  ]
}

✅ Dynamic Sitemap

// app/sitemap.ts
import type { MetadataRoute } from 'next'

export default async function sitemap(): Promise<MetadataRoute.Sitemap> {
  const posts = await getPosts()
  
  const postEntries = posts.map((post) => ({
    url: 'https://www.mysite.com/blog/' + post.slug,
    lastModified: post.updatedAt,
    changeFrequency: 'weekly' as const,
    priority: 0.7,
  }))
  
  return [
    {
      url: 'https://www.mysite.com',
      lastModified: new Date(),
      changeFrequency: 'daily',
      priority: 1,
    },
    ...postEntries,
  ]
}

Best practices:

  • Use absolute URLs with www domain
  • Set lastModified to actual update dates
  • Use appropriate changeFrequency (daily, weekly, monthly)
  • Set priority 0.0–1.0 (homepage = 1.0)

✅ Robots.txt

// app/robots.ts
import type { MetadataRoute } from 'next'

export default function robots(): MetadataRoute.Robots {
  return {
    rules: [
      {
        userAgent: '*',
        allow: '/',
        disallow: ['/api/', '/admin/'],
      },
    ],
    sitemap: 'https://www.mysite.com/sitemap.xml',
  }
}

Next.js serves this at /robots.txt. Always include your sitemap URL.

4. Schema (Structured Data)

Add JSON-LD structured data for rich results. Use script tags with type="application/ld+json".

✅ Article Schema

// app/blog/[slug]/layout.tsx
export default function BlogPostLayout({ children }) {
  const articleSchema = {
    '@context': 'https://schema.org',
    '@type': 'Article',
    headline: 'Post Title',
    description: 'Post description',
    author: {
      '@type': 'Person',
      name: 'Author Name',
    },
    datePublished: '2026-02-12',
    dateModified: '2026-02-12',
    image: 'https://www.mysite.com/post-image.jpg',
  }
  
  return (
    <>
      <script
        type="application/ld+json"
        dangerouslySetInnerHTML={{ __html: JSON.stringify(articleSchema) }}
      />
      {children}
    </>
  )
}

✅ Breadcrumb Schema

const breadcrumbSchema = {
  '@context': 'https://schema.org',
  '@type': 'BreadcrumbList',
  itemListElement: [
    {
      '@type': 'ListItem',
      position: 1,
      name: 'Home',
      item: 'https://www.mysite.com',
    },
    {
      '@type': 'ListItem',
      position: 2,
      name: 'Blog',
      item: 'https://www.mysite.com/blog',
    },
    {
      '@type': 'ListItem',
      position: 3,
      name: 'Post Title',
      item: 'https://www.mysite.com/blog/post-slug',
    },
  ],
}

✅ FAQ Schema

const faqSchema = {
  '@context': 'https://schema.org',
  '@type': 'FAQPage',
  mainEntity: [
    {
      '@type': 'Question',
      name: 'What is Next.js?',
      acceptedAnswer: {
        '@type': 'Answer',
        text: 'Next.js is a React framework for production.',
      },
    },
  ],
}

Common schemas: Article, BlogPosting, BreadcrumbList, FAQPage, Organization, Person, WebSite.

Validate schemas using Google Rich Results Test.

5. Speed

Fast sites rank better. Optimize Core Web Vitals: LCP, FID/INP, CLS.

✅ Use next/image

import Image from 'next/image'

<Image
  src="/hero.jpg"
  alt="Hero image"
  width={1200}
  height={630}
  priority // For above-the-fold images
  quality={85}
  placeholder="blur"
/>

Why: Automatic WebP/AVIF conversion, lazy loading, responsive sizes, and optimized delivery.

✅ Optimize Fonts

// app/layout.tsx
import { Inter } from 'next/font/google'

const inter = Inter({ subsets: ['latin'] })

export default function RootLayout({ children }) {
  return (
    <html lang="en" className={inter.className}>
      {children}
    </html>
  )
}

Why: Next.js automatically optimizes fonts, self-hosts them, and eliminates layout shift.

✅ Code Splitting

// Dynamic imports for heavy components
import dynamic from 'next/dynamic'

const HeavyChart = dynamic(() => import('@/components/Chart'), {
  loading: () => <p>Loading chart...</p>,
  ssr: false, // Only if component requires browser APIs
})

✅ Script Optimization

import Script from 'next/script'

<Script
  src="https://analytics.example.com/script.js"
  strategy="afterInteractive" // or "lazyOnload"
/>

Strategies: afterInteractive (default), lazyOnload (after page load), beforeInteractive (critical only).

6. Indexing

Ensure Google can crawl and index your site.

✅ Verify robots.txt

Check https://www.mysite.com/robots.txt. Ensure important pages aren't disallowed.

✅ Submit Sitemap

Submit https://www.mysite.com/sitemap.xml in Google Search Console.

✅ Check for noindex

// ❌ BAD: Don't do this unless page should be excluded
export const metadata = {
  robots: {
    index: false, // Page won't be indexed!
  },
}

Only set index: false for pages that shouldn't be indexed (admin, drafts, etc.).

✅ Use Canonical URLs

Always set canonical URLs to prevent duplicate content issues. Use www domain consistently.

✅ Test Rendering

Use Google Rich Results Test and URL Inspection Tool to verify pages render correctly for Google.

7. Common Bugs

❌ Missing Metadata Export

// ❌ BAD: No metadata export
export default function Page() {
  return <div>Content</div>
}

// ✅ GOOD: Export metadata
export const metadata = {
  title: 'Page Title',
  description: 'Page description',
}

❌ Relative Canonical URLs

// ❌ BAD
alternates: {
  canonical: '/blog', // Relative URL
}

// ✅ GOOD
alternates: {
  canonical: 'https://www.mysite.com/blog', // Absolute URL
}

❌ Client-Side Content

// ❌ BAD: Content in Client Component
'use client'
export default function Page() {
  const [data, setData] = useState(null)
  useEffect(() => {
    fetch('/api/data').then(res => res.json()).then(setData)
  }, [])
  return <div>{data}</div> // Crawlers won't see this
}

// ✅ GOOD: Server Component
export default async function Page() {
  const data = await getData() // Server-side fetch
  return <div>{data}</div> // Crawlers see this
}

❌ Missing Alt Text

// ❌ BAD
<Image src="/image.jpg" width={500} height={300} />

// ✅ GOOD
<Image 
  src="/image.jpg" 
  alt="Descriptive alt text for SEO"
  width={500} 
  height={300} 
/>

❌ Duplicate Canonical Tags

Don't add canonical tags manually if you're using Next.js Metadata API. The API handles it automatically.

8. Tools

Use these tools to audit and monitor your Next.js SEO optimization:

  • Google Search Console — Monitor indexing, search performance, and issues
  • Google Rich Results Test — Validate structured data
  • PageSpeed Insights — Test Core Web Vitals and performance
  • Lighthouse — Audit SEO, performance, accessibility
  • Vercel Analytics — Real user metrics (RUM) for production sites

For more tools, see the SEO tools and resources page.

Hosting & deploy for Next.js SEO (quick comparison)

PlatformBest forPriceFree tierLink
VercelNext.js, zero config, edgeFree / ProYesMy review
NetlifyStatic + serverlessFree / paidYesResources

Vercel Deployment Best Practices

When deploying to Vercel:

  • Use www subdomain consistently (set in Vercel project settings)
  • Enable x-robots-tag headers if needed (via Vercel config)
  • Monitor Core Web Vitals in Vercel Analytics
  • Use Edge Functions for API routes that need low latency
  • Enable ISR for dynamic content that changes infrequently

Best hosting for Next.js SEO

For Next.js, Vercel is the default choice: same team as Next.js, automatic optimizations, and Analytics for Core Web Vitals. I’ve written a full Vercel review with pros, cons, and when to choose it.

Is Vercel worth it in 2026?

For production Next.js sites, yes. Free tier is enough for small projects; Pro when you need more bandwidth and analytics. No server config—deploy and your SEO setup (sitemap, metadata, server rendering) just works.

👉 If you want to deploy your Next.js site with zero SEO headaches, I personally recommend Vercel — start free → see resources.

Frequently Asked Questions

What are the most common Next.js SEO mistakes?

Common mistakes include: missing metadata exports, incorrect canonical URLs, no sitemap.xml, missing structured data, client-side rendering for SEO-critical content, and not using next/image for images. This checklist covers all of these.

How do I set up metadata in Next.js App Router?

Use the Metadata API by exporting a metadata object or generateMetadata function from your page.tsx or layout.tsx. Include title, description, canonical URL, OpenGraph tags, and Twitter Card. See the Metadata section in this checklist for code examples.

Do I need a sitemap for Next.js SEO?

Yes. Create a sitemap.ts file in your app directory that exports a sitemap function returning an array of URLs with lastModified, changeFrequency, and priority. Next.js will serve it at /sitemap.xml automatically.

How do I add structured data (schema.org) to Next.js?

Use JSON-LD script tags in your layout or page components. Create schema objects for Article, BreadcrumbList, FAQPage, etc., and render them using dangerouslySetInnerHTML in a script tag with type="application/ld+json". See the Schema section for examples.

What rendering method is best for Next.js SEO?

Server-side rendering (SSR) or Static Site Generation (SSG) are best for SEO. Use Server Components by default in App Router. Avoid client-side rendering for content that needs to be indexed. Use ISR (Incremental Static Regeneration) for dynamic content that needs frequent updates.

Want More SEO Resources?

Check out the SEO for Developers guide, performance optimization case study, and SEO tools and resources.

👉 Deploy your Next.js site with zero config → Vercel review (free tier available).

Key Takeaways

  • Use Server Components by default in App Router—avoid client-side rendering for SEO-critical content.
  • Export metadata objects from page.tsx/layout.tsx. Include title, description, canonical, OpenGraph, and Twitter Card.
  • Create sitemap.ts and robots.ts in app directory. Next.js serves them automatically at /sitemap.xml and /robots.txt.
  • Add JSON-LD structured data for Article, BreadcrumbList, FAQPage. Use script tags with type="application/ld+json".

Related Guides

Want more articles like this?

Subscribe to get practical guides and case studies delivered to your inbox. No spam, just real systems that work.