nextjs-seo

Compare original and translation side by side

🇺🇸

Original

English
🇨🇳

Translation

Chinese

Next.js SEO Optimization

Next.js SEO优化

Comprehensive SEO guide for Next.js 16+ applications using App Router.
Version: Updated for Next.js 16.1.3 (January 2026)
这是一份针对使用App Router的Next.js 16+应用的全面SEO指南。
版本: 已更新至Next.js 16.1.3(2026年1月)

Quick SEO Audit

快速SEO审计

Run this checklist for any Next.js project:
  1. Check robots.txt:
    curl https://your-site.com/robots.txt
  2. Check sitemap:
    curl https://your-site.com/sitemap.xml
  3. Check metadata: View page source, search for
    <title>
    and
    <meta name="description">
  4. Check JSON-LD: View page source, search for
    application/ld+json
  5. Check Core Web Vitals: Run Lighthouse in Chrome DevTools
为任意Next.js项目运行以下检查清单:
  1. 检查robots.txt
    curl https://your-site.com/robots.txt
  2. 检查站点地图
    curl https://your-site.com/sitemap.xml
  3. 检查元数据:查看页面源代码,搜索
    <title>
    <meta name="description">
  4. 检查JSON-LD:查看页面源代码,搜索
    application/ld+json
  5. 检查Core Web Vitals:在Chrome开发者工具中运行Lighthouse

Essential Files

关键文件

app/layout.tsx - Root Metadata

app/layout.tsx - 根元数据

typescript
import type { Metadata, Viewport } from 'next';

// Viewport (separate export required in Next.js 14+)
export const viewport: Viewport = {
  width: 'device-width',
  initialScale: 1,
  maximumScale: 5,
  userScalable: true,
  themeColor: [
    { media: '(prefers-color-scheme: light)', color: '#ffffff' },
    { media: '(prefers-color-scheme: dark)', color: '#0a0a0a' },
  ],
};

export const metadata: Metadata = {
  metadataBase: new URL('https://your-site.com'),
  title: {
    default: 'Site Title - Main Keyword',
    template: '%s | Site Name',
  },
  description: 'Compelling description with keywords (150-160 chars)',
  keywords: ['keyword1', 'keyword2', 'keyword3'],
  openGraph: {
    type: 'website',
    locale: 'en_US',
    url: 'https://your-site.com',
    siteName: 'Site Name',
    title: 'Site Title',
    description: 'Description for social sharing',
    images: [{ url: '/og-image.png', width: 1200, height: 630, alt: 'Site preview' }],
  },
  twitter: {
    card: 'summary_large_image',
    title: 'Site Title',
    description: 'Description for Twitter',
    images: ['/og-image.png'],
  },
  alternates: {
    canonical: '/',
  },
  robots: {
    index: true,
    follow: true,
  },
};
typescript
import type { Metadata, Viewport } from 'next';

// Viewport(Next.js 14+中需要单独导出)
export const viewport: Viewport = {
  width: 'device-width',
  initialScale: 1,
  maximumScale: 5,
  userScalable: true,
  themeColor: [
    { media: '(prefers-color-scheme: light)', color: '#ffffff' },
    { media: '(prefers-color-scheme: dark)', color: '#0a0a0a' },
  ],
};

export const metadata: Metadata = {
  metadataBase: new URL('https://your-site.com'),
  title: {
    default: '站点标题 - 主关键词',
    template: '%s | 站点名称',
  },
  description: '包含关键词的有吸引力描述(150-160字符)',
  keywords: ['关键词1', '关键词2', '关键词3'],
  openGraph: {
    type: 'website',
    locale: 'en_US',
    url: 'https://your-site.com',
    siteName: '站点名称',
    title: '站点标题',
    description: '用于社交分享的描述',
    images: [{ url: '/og-image.png', width: 1200, height: 630, alt: '站点预览图' }],
  },
  twitter: {
    card: 'summary_large_image',
    title: '站点标题',
    description: '用于Twitter的描述',
    images: ['/og-image.png'],
  },
  alternates: {
    canonical: '/',
  },
  robots: {
    index: true,
    follow: true,
  },
};

app/sitemap.ts - Dynamic Sitemap

app/sitemap.ts - 动态站点地图

typescript
import type { MetadataRoute } from 'next';

export default function sitemap(): MetadataRoute.Sitemap {
  const baseUrl = 'https://your-site.com';

  return [
    {
      url: baseUrl,
      lastModified: new Date(),
      changeFrequency: 'weekly',
      priority: 1,
      images: [`${baseUrl}/og-image.png`], // Next.js 16 Image Sitemap
    },
    {
      url: `${baseUrl}/about`,
      lastModified: new Date(),
      changeFrequency: 'monthly',
      priority: 0.8,
    },
  ];
}
typescript
import type { MetadataRoute } from 'next';

export default function sitemap(): MetadataRoute.Sitemap {
  const baseUrl = 'https://your-site.com';

  return [
    {
      url: baseUrl,
      lastModified: new Date(),
      changeFrequency: 'weekly',
      priority: 1,
      images: [`${baseUrl}/og-image.png`], // Next.js 16 图片站点地图
    },
    {
      url: `${baseUrl}/about`,
      lastModified: new Date(),
      changeFrequency: 'monthly',
      priority: 0.8,
    },
  ];
}

app/robots.ts - Robots Configuration

app/robots.ts - Robots配置

typescript
import type { MetadataRoute } from 'next';

export default function robots(): MetadataRoute.Robots {
  const baseUrl = 'https://your-site.com';

  return {
    rules: [
      {
        userAgent: '*',
        allow: '/',
        disallow: ['/api/', '/_next/', '/admin/'],
      },
    ],
    sitemap: `${baseUrl}/sitemap.xml`,
    host: baseUrl,
  };
}
typescript
import type { MetadataRoute } from 'next';

export default function robots(): MetadataRoute.Robots {
  const baseUrl = 'https://your-site.com';

  return {
    rules: [
      {
        userAgent: '*',
        allow: '/',
        disallow: ['/api/', '/_next/', '/admin/'],
      },
    ],
    sitemap: `${baseUrl}/sitemap.xml`,
    host: baseUrl,
  };
}

Key Principles

核心原则

Rendering Strategy for SEO

SEO渲染策略

StrategyUse WhenSEO Impact
SSG (Static)Content rarely changesBest - pre-rendered HTML
SSRDynamic content per requestGreat - server-rendered
ISRLarge sites, periodic updatesGreat - cached + fresh
CSRDashboards, authenticated areasPoor - avoid for SEO pages
策略使用场景SEO影响
SSG(静态生成)内容极少变更最佳 - 预渲染HTML
SSR(服务器端渲染)每个请求需动态内容优秀 - 服务器渲染
ISR(增量静态再生)大型站点、定期更新优秀 - 缓存+新鲜内容
CSR(客户端渲染)仪表盘、认证区域较差 - SEO页面应避免使用

Core Web Vitals Targets

Core Web Vitals目标

MetricTargetImpact
LCP (Largest Contentful Paint)< 2.5sLoading speed
INP (Interaction to Next Paint)< 200msInteractivity
CLS (Cumulative Layout Shift)< 0.1Visual stability
指标目标影响
LCP(最大内容绘制)< 2.5秒加载速度
INP(交互到下一次绘制)< 200毫秒交互性
CLS(累积布局偏移)< 0.1视觉稳定性

References

参考资料

  • Metadata API: See references/metadata-api.md
  • Sitemap & Robots: See references/sitemap-robots.md
  • JSON-LD Structured Data: See references/json-ld.md
  • SEO Audit Checklist: See references/checklist.md
  • Troubleshooting: See references/troubleshooting.md
  • 元数据API:查看 references/metadata-api.md
  • 站点地图与Robots:查看 references/sitemap-robots.md
  • JSON-LD结构化数据:查看 references/json-ld.md
  • SEO审计清单:查看 references/checklist.md
  • 故障排除:查看 references/troubleshooting.md

Common Mistakes to Avoid

需避免的常见错误

  1. Mixing next-seo with Metadata API - Use only Metadata API in App Router
  2. Missing canonical URLs - Always set
    alternates.canonical
  3. Using CSR for SEO pages - Use SSG/SSR for indexable content
  4. Blocking assets in robots.txt - Don't block CSS/JS needed for rendering
  5. Missing metadataBase - Required for relative URLs in metadata
  6. Viewport in metadata - Must be separate export in Next.js 14+
  7. Mixing metadata object and generateMetadata - Use one or the other, not both
  1. 同时使用next-seo与元数据API - 在App Router中仅使用元数据API
  2. 缺少规范URL - 始终设置
    alternates.canonical
  3. 对SEO页面使用CSR - 对可索引内容使用SSG/SSR
  4. 在robots.txt中阻止资源 - 不要阻止渲染所需的CSS/JS
  5. 缺少metadataBase - 元数据中的相对URL需要此配置
  6. 在元数据中设置Viewport - Next.js 14+中必须单独导出
  7. 同时使用元数据对象与generateMetadata - 二选一,不要同时使用

Quick Fixes

快速修复方案

Add noindex to a page

为页面添加noindex

typescript
export const metadata: Metadata = {
  robots: {
    index: false,
    follow: false,
  },
};
typescript
export const metadata: Metadata = {
  robots: {
    index: false,
    follow: false,
  },
};

Dynamic metadata per page

为每个页面设置动态元数据

typescript
export async function generateMetadata({ params }): Promise<Metadata> {
  const product = await getProduct(params.id);
  return {
    title: product.name,
    description: product.description,
  };
}
typescript
export async function generateMetadata({ params }): Promise<Metadata> {
  const product = await getProduct(params.id);
  return {
    title: product.name,
    description: product.description,
  };
}

Canonical for dynamic routes

为动态路由设置规范URL

typescript
export async function generateMetadata({ params }): Promise<Metadata> {
  return {
    alternates: {
      canonical: `/products/${params.slug}`,
    },
  };
}
typescript
export async function generateMetadata({ params }): Promise<Metadata> {
  return {
    alternates: {
      canonical: `/products/${params.slug}`,
    },
  };
}