返回部落格

Next.js App Router 技術 SEO 實用清單

Next.js App Router 技術 SEO 實用清單

2026年10月9日 — SEO

發布於

2026年10月9日

閱讀時間

4 分鐘

分類

SEO

標籤

  • SEO
  • Next.js
  • App Router
  • Metadata
  • Structured Data

(摘要)在 Next.js App Router 中配置 Metadata、canonical、sitemap、robots.txt、結構化資料、hreflang 和 Core Web Vitals,附可直接複製的程式碼。

Next.js 提供了做技術 SEO 所需的一切,但幾乎沒有一項是預設開啟的。這是我在每個 App Router 專案上都會過一遍的清單,大致按影響大小排序。

1. 重要內容用服務端渲染

搜尋引擎能執行 JavaScript,但對服務端渲染的 HTML 收錄得更快、更穩定。在 App Router 中,服務端元件是預設選項——把標題、正文和連結放在服務端元件裡,客戶端元件只負責互動,不負責內容。

快速檢測:禁用 JavaScript 開啟頁面,或者執行 curl https://yoursite.com/page。如果響應裡沒有主要文字,第一輪收錄裡也不會有。

2. 設定 metadataBase 和標題範本

在根佈局中:

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

export const metadata: Metadata = {
  metadataBase: new URL('https://example.com'),
  title: {
    default: 'Example Studio — Web Design and Development',
    template: '%s — Example Studio',
  },
  description: 'Websites and products for small teams: design, development and launch.',
  openGraph: { siteName: 'Example Studio', type: 'website' },
}

metadataBase 會把相對 URL(canonical、Open Graph 圖片)轉成絕對 URL。有了標題範本,子頁面只需設定自己那部分標題。

3. 每個頁面都有獨立的標題和描述

每個需要被收錄的頁面都要有自己的 <title>(約 50–60 個字元,關鍵詞靠前)和 meta description(約 150–160 個字元,寫得讓人想點擊)。動態頁面使用 generateMetadata:

app/blog/[slug]/page.tsx
export async function generateMetadata({ params }: { params: Promise<{ slug: string }> }) {
  const { slug } = await params
  const post = await getPost(slug)
  if (!post) return {}
  return {
    title: post.title,
    description: post.excerpt,
    alternates: { canonical: `/blog/${post.slug}` },
    openGraph: {
      type: 'article',
      publishedTime: post.date,
      images: [{ url: post.cover, width: 1200, height: 630 }],
    },
  }
}

4. Canonical 連結

每個頁面都設定 alternates.canonical。它能防止查詢參數(?utm_source=...)、末尾斜線和備用網域造成的重複內容。選定一種形式——帶不帶 www、帶不帶末尾斜線——然後在託管平台把其他形式重定向過來。

5. Sitemap

app/sitemap.ts 會生成 /sitemap.xml,靜態匯出也支援:

app/sitemap.ts
import type { MetadataRoute } from 'next'
import { getAllPosts } from '@/lib/blog'

export const dynamic = 'force-static'

export default async function sitemap(): Promise<MetadataRoute.Sitemap> {
  const base = 'https://example.com'
  const posts = await getAllPosts()
  return [
    { url: base, changeFrequency: 'monthly', priority: 1 },
    ...posts.map((post) => ({
      url: `${base}/blog/${post.slug}`,
      lastModified: post.date,
      changeFrequency: 'yearly' as const,
      priority: 0.7,
    })),
  ]
}

在 Google Search Console 和 Bing 網站管理員工具中各提交一次。如果主要面向國內使用者,也別忘了百度搜尋資源平台。

6. robots.txt

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

export const dynamic = 'force-static'

export default function robots(): MetadataRoute.Robots {
  return {
    rules: { userAgent: '*', allow: '/' },
    sitemap: 'https://example.com/sitemap.xml',
  }
}

不要用 robots.txt 來阻止頁面被收錄——被遮蔽的頁面仍可能透過外部連結被收錄。應該在頁面的 metadata 中使用 robots: { index: false }。

7. 結構化資料(JSON-LD)

結構化資料幫助搜尋引擎理解頁面是什麼。對於文章,在頁面中渲染一個 script 標籤:

tsx
const jsonLd = {
  '@context': 'https://schema.org',
  '@type': 'BlogPosting',
  headline: post.title,
  description: post.excerpt,
  datePublished: post.date,
  image: `https://example.com${post.cover}`,
  author: { '@type': 'Person', name: 'Jane Doe', url: 'https://example.com' },
}

return (
  <>
    <script
      type="application/ld+json"
      dangerouslySetInnerHTML={{ __html: JSON.stringify(jsonLd).replace(/</g, '\\u003c') }}
    />
    <article>{/* ... */}</article>
  </>
)

對小型企業有用的類型:首頁用 Organization 或 Person,文章用 BlogPosting,深層頁面用 BreadcrumbList,商品頁用 Product。用 Google 的富媒體搜尋結果測試工具驗證。

8. Open Graph 圖片

在領英、X、Slack 和各種聊天軟體裡分享連結時,會用到 Open Graph 標籤。給每個頁面一張 1200×630 像素的圖片。可以在頁面旁邊放一個靜態的 opengraph-image.png,也可以在 opengraph-image.tsx 中用 ImageResponse 生成(靜態匯出時在建置階段生成,所以不能依賴請求資訊)。

9. 多語言網站:真實的 URL 和 hreflang

每種語言都需要自己的 URL,否則搜尋引擎永遠只能看到其中一種。用 localStorage 在客戶端切換語言對訪客沒問題,但 Google 看不到。使用語言路由段——app/[lang]/...——並宣告各語言版本:

ts
alternates: {
  canonical: '/en/blog/my-post',
  languages: {
    en: '/en/blog/my-post',
    'zh-CN': '/zh/blog/my-post',
    'sr-Latn': '/sr/blog/my-post',
    'x-default': '/en/blog/my-post',
  },
}

每個語言版本都應列出所有其他版本,包括它自己。

10. 內部連結和標題層級

  • 每頁一個 <h1>,然後按順序使用 <h2>/<h3>。標題是大綱,不是字號。
  • 相關頁面之間互相連結,並使用描述性的錨文字("靜態匯出指南",而不是"點擊這裡")。使用 <Link>,確保它們是真正的 <a href> 元素。
  • 重要頁面離首頁不超過三次點擊。

11. Core Web Vitals

速度是排名訊號,更重要的是轉化訊號。用 PageSpeed Insights 測量(實地資料來自真實的 Chrome 使用者),目標是 LCP ≤ 2.5 秒、INP ≤ 200 毫秒、CLS ≤ 0.1。App Router 專案中常見的問題:首屏圖片過大、客戶端 JavaScript 過多,以及字型或未設尺寸的圖片造成的佈局偏移。如果你的網站動畫很多,可以看動畫網站的 Core Web Vitals 優化。

12. 圖片

  • 內容圖片寫描述性的 alt,裝飾性圖片用空的 alt=""。
  • 明確的 width/height 或 aspect-ratio,防止佈局偏移。
  • 使用現代格式(AVIF、WebP)和合適的尺寸。
  • 描述性的檔名:nextjs-sitemap-example.png,而不是 IMG_2041.png。

13. 404 和重定向

對不存在的頁面返回真正的 404(App Router 中用 notFound())。移動或重新命名頁面時,從舊 URL 加一個 301 重定向——在靜態匯出中,就是託管平台的 _redirects 規則。

精簡版清單

  • 內容放在服務端元件中
  • 根佈局中設定 metadataBase 和標題範本
  • 每個頁面都有獨立的標題、描述和 canonical
  • sitemap.ts 和 robots.ts,並提交 sitemap
  • 文章、組織、麵包屑的 JSON-LD
  • 每個頁面都有 Open Graph 圖片
  • 每種語言一個 URL,並配置 hreflang
  • 合理的標題層級和描述性內部連結
  • Core Web Vitals 全部達標
  • 真正的 404,以及移動頁面的 301

在做靜態網站?閱讀 Next.js 靜態匯出完全指南,瞭解沒有伺服器時哪些地方不一樣。

電子報

用 Email 訂閱新文章

有新文章時寄一封簡短的信給你。不發垃圾信,隨時可以取消訂閱。

不用 CMS 和資料庫,在 Next.js 中搭建 Markdown 部落格
不用 CMS 和資料庫,在 Next.js 中搭建 Markdown 部落格
下一篇