SEO for Next.js 2026 — Rank on Google, Appear in AI Search, and Google Discover

Sanjeev SharmaSanjeev Sharma
5 min read

Advertisement

Introduction

Why This Matters

SEO in 2026 means ranking on Google AND appearing in AI answer engines (ChatGPT, Perplexity, Gemini). AI engines pull from the same well as Google — structured data, clear headings, and factual content with citations. Next.js App Router has first-class metadata support that makes technical SEO straightforward.

Metadata API

// app/layout.tsx — site-wide defaults
import type { Metadata } from 'next'
 
export const metadata: Metadata = {
  metadataBase: new URL('https://yoursite.com'),
  title: {
    default: 'Your Site Name',
    template: '%s | Your Site Name',
  },
  description: 'The best resource for modern web development.',
  openGraph: {
    siteName: 'Your Site Name',
    locale: 'en_US',
    type: 'website',
  },
  twitter: {
    card: 'summary_large_image',
    site: '@yourtwitterhandle',
  },
  robots: {
    index: true,
    follow: true,
    googleBot: { index: true, follow: true, 'max-image-preview': 'large' },
  },
}
// app/blog/[slug]/page.tsx — dynamic metadata per page
import type { Metadata } from 'next'
import { getPost } from '@/lib/posts'
import { notFound } from 'next/navigation'
 
export async function generateMetadata(
  { params }: { params: Promise<{ slug: string }> }
): Promise<Metadata> {
  const { slug } = await params
  const post = await getPost(slug)
  if (!post) return {}
 
  return {
    title: post.title,
    description: post.excerpt,
    authors: [{ name: post.author.name }],
    publishedTime: post.publishedAt,
    openGraph: {
      title: post.title,
      description: post.excerpt,
      type: 'article',
      publishedTime: post.publishedAt,
      authors: [post.author.name],
      images: [{
        url: `/og?title=${encodeURIComponent(post.title)}`,
        width: 1200,
        height: 630,
        alt: post.title,
      }],
    },
    twitter: {
      card: 'summary_large_image',
      title: post.title,
      description: post.excerpt,
    },
  }
}

Dynamic OG Images

// app/og/route.tsx
import { ImageResponse } from 'next/og'
 
export async function GET(request: Request) {
  const { searchParams } = new URL(request.url)
  const title = searchParams.get('title') ?? 'Untitled'
 
  return new ImageResponse(
    (
      <div
        style={{
          display: 'flex',
          flexDirection: 'column',
          width: '100%',
          height: '100%',
          padding: '64px',
          background: 'linear-gradient(135deg, #1e293b 0%, #0f172a 100%)',
          color: 'white',
          fontFamily: 'sans-serif',
        }}
      >
        <div style={{ fontSize: 16, color: '#94a3b8', marginBottom: 24 }}>
          yoursite.com
        </div>
        <div style={{ fontSize: 56, fontWeight: 700, lineHeight: 1.2, flex: 1 }}>
          {title}
        </div>
      </div>
    ),
    { width: 1200, height: 630 }
  )
}

Structured Data (JSON-LD)

Structured data helps Google and AI engines understand your content:

// components/ArticleSchema.tsx
export function ArticleSchema({ post }: { post: Post }) {
  const schema = {
    '@context': 'https://schema.org',
    '@type': 'Article',
    headline: post.title,
    description: post.excerpt,
    author: {
      '@type': 'Person',
      name: post.author.name,
      url: `https://yoursite.com/authors/${post.author.slug}`,
    },
    publisher: {
      '@type': 'Organization',
      name: 'Your Site Name',
      logo: { '@type': 'ImageObject', url: 'https://yoursite.com/logo.png' },
    },
    datePublished: post.publishedAt,
    dateModified: post.updatedAt,
    image: post.coverImage,
    url: `https://yoursite.com/blog/${post.slug}`,
  }
 
  return (
    <script
      type="application/ld+json"
      dangerouslySetInnerHTML={{ __html: JSON.stringify(schema) }}
    />
  )
}
// FAQ structured data for AI engines
export function FAQSchema({ faqs }: { faqs: Array<{ q: string; a: string }> }) {
  const schema = {
    '@context': 'https://schema.org',
    '@type': 'FAQPage',
    mainEntity: faqs.map(({ q, a }) => ({
      '@type': 'Question',
      name: q,
      acceptedAnswer: { '@type': 'Answer', text: a },
    })),
  }
 
  return (
    <script
      type="application/ld+json"
      dangerouslySetInnerHTML={{ __html: JSON.stringify(schema) }}
    />
  )
}

Sitemap and robots.txt

// app/sitemap.ts
import type { MetadataRoute } from 'next'
import { getAllPosts } from '@/lib/posts'
 
export default async function sitemap(): Promise<MetadataRoute.Sitemap> {
  const posts = await getAllPosts()
 
  const postUrls = posts.map(post => ({
    url: `https://yoursite.com/blog/${post.slug}`,
    lastModified: new Date(post.updatedAt),
    changeFrequency: 'weekly' as const,
    priority: 0.8,
  }))
 
  return [
    { url: 'https://yoursite.com', lastModified: new Date(), priority: 1 },
    { url: 'https://yoursite.com/blog', lastModified: new Date(), priority: 0.9 },
    ...postUrls,
  ]
}
// app/robots.ts
import type { MetadataRoute } from 'next'
 
export default function robots(): MetadataRoute.Robots {
  return {
    rules: [
      { userAgent: '*', allow: '/' },
      { userAgent: 'Googlebot', allow: '/' },
    ],
    sitemap: 'https://yoursite.com/sitemap.xml',
  }
}

Common Mistakes

  • Setting the same title and description on every page — duplicate metadata hurts ranking
  • Not using metadataBase in Next.js — relative image URLs in OG tags do not resolve correctly
  • Missing datePublished and dateModified in Article schema — Google uses these for freshness signals
  • Not generating a sitemap — search engines may miss new pages for weeks
  • Ignoring Core Web Vitals — LCP, CLS, and INP are confirmed ranking signals

Best Practices

  • Write description tags as a complete sentence that answers what the page covers
  • Use template: '%s | Site Name' in the root layout so page titles stay consistent
  • Include FAQ structured data on content pages — AI engines prefer clear question-answer pairs
  • Generate OG images dynamically with ImageResponse so every page has a unique social card
  • Submit your sitemap in Google Search Console and monitor index coverage weekly

Key Takeaways

  • Next.js generateMetadata supports async data — use it for dynamic titles and descriptions per page
  • metadataBase is required for Next.js to resolve relative image URLs in OG and Twitter card tags
  • JSON-LD structured data helps both Google and AI answer engines understand your content type
  • FAQ and HowTo schemas increase the chance of appearing in AI-generated answer summaries
  • app/sitemap.ts exports a typed sitemap automatically served at /sitemap.xml
  • Core Web Vitals (LCP, CLS, INP) are ranking factors — performance optimization is SEO optimization
  • Dynamic OG images with ImageResponse run on the Edge and add zero build time
  • Canonical URLs prevent duplicate content penalties when content appears at multiple paths

Advertisement

Sanjeev Sharma

Written by

Sanjeev Sharma

Full Stack Engineer · E-mopro

Related reading