元資料 (Metadata)

Next.js 提供了一個 Metadata API,可用於定義應用程式的元資料(例如 HTML head 元素中的 metalink 標籤),以提升 SEO 和網頁分享能力。

您可以透過兩種方式為應用程式添加元資料:

  • 基於設定的元資料 (Config-based Metadata):在 layout.jspage.js 檔案中匯出一個靜態 metadata 物件 或動態的 generateMetadata 函式
  • 基於檔案的元資料 (File-based Metadata):在路由區段中添加靜態或動態生成的特殊檔案。

透過這兩種方式,Next.js 會自動為您的頁面生成相關的 <head> 元素。您還可以使用 ImageResponse 建構函式來建立動態 OG 圖片。

靜態元資料 (Static Metadata)

要定義靜態元資料,請從 layout.js 或靜態 page.js 檔案匯出一個 Metadata 物件

import type { Metadata } from 'next'

export const metadata: Metadata = {
  title: '...',
  description: '...',
}

export default function Page() {}

如需所有可用選項,請參閱 API 參考文件

動態元資料 (Dynamic Metadata)

您可以使用 generateMetadata 函式來獲取需要動態值的元資料。

import type { Metadata, ResolvingMetadata } from 'next'

type Props = {
  params: { id: string }
  searchParams: { [key: string]: string | string[] | undefined }
}

export async function generateMetadata(
  { params, searchParams }: Props,
  parent: ResolvingMetadata
): Promise<Metadata> {
  // 讀取路由參數
  const id = params.id

  // 獲取資料
  const product = await fetch(`https://.../${id}`).then((res) => res.json())

  // 可選地存取並擴展(而非替換)父元資料
  const previousImages = (await parent).openGraph?.images || []

  return {
    title: product.title,
    openGraph: {
      images: ['/some-specific-page-image.jpg', ...previousImages],
    },
  }
}

export default function Page({ params, searchParams }: Props) {}

如需所有可用參數,請參閱 API 參考文件

須知事項

  • 透過 generateMetadata 產生的靜態和動態元資料僅在伺服器元件 (Server Components) 中支援
  • fetch 請求會自動記憶化 (memoized),以便在 generateMetadatagenerateStaticParams、佈局 (Layouts)、頁面 (Pages) 和伺服器元件 (Server Components) 之間共享相同資料。如果無法使用 fetch,可以使用 React 的 cache
  • Next.js 會等待 generateMetadata 內的資料獲取完成後,才將 UI 串流至客戶端。這確保了串流回應 (streamed response) 的第一部分包含 <head> 標籤。

基於檔案的元資料 (File-based Metadata)

以下特殊檔案可用於元資料:

您可以使用這些檔案來設定靜態元資料,也可以透過程式碼動態生成這些檔案。

如需實作和範例,請參閱 Metadata Files API 參考文件和動態圖片生成 (Dynamic Image Generation)

行為 (Behavior)

基於檔案的元資料具有較高優先級,會覆蓋任何基於設定的元資料。

預設欄位 (Default Fields)

即使路由未定義元資料,也會自動添加兩個預設 meta 標籤:

<meta charset="utf-8" />
<meta name="viewport" content="width=device-width, initial-scale=1" />

須知事項:您可以覆蓋預設的 viewport meta 標籤。

順序 (Ordering)

元資料的評估順序是從根區段 (root segment) 開始,一直到最接近最終 page.js 的區段。例如:

  1. app/layout.tsx (根佈局)
  2. app/blog/layout.tsx (嵌套的部落格佈局)
  3. app/blog/[slug]/page.tsx (部落格頁面)

合併 (Merging)

根據評估順序,從同一路由的多個區段匯出的 Metadata 物件會淺層合併,形成路由的最終元資料輸出。重複的鍵會根據順序被替換

這意味著,如 openGraphrobots 這類具有嵌套欄位的元資料,如果在較早的區段中定義,會被最後定義它們的區段覆蓋

覆寫欄位 (Overwriting fields)

app/layout.js
export const metadata = {
  title: 'Acme',
  openGraph: {
    title: 'Acme',
    description: 'Acme is a...',
  },
}
app/blog/page.js
export const metadata = {
  title: 'Blog',
  openGraph: {
    title: 'Blog',
  },
}

// 輸出:
// <title>Blog</title>
// <meta property="og:title" content="Blog" />

在上面的範例中:

  • app/layout.js 中的 titleapp/blog/page.js 中的 title 替換
  • app/layout.js 中的所有 openGraph 欄位在 app/blog/page.js 中被替換,因為 app/blog/page.js 設定了 openGraph 元資料。請注意 openGraph.description 的缺失。

如果您希望在區段之間共享某些嵌套欄位,同時覆蓋其他欄位,可以將它們提取到單獨的變數中:

app/shared-metadata.js
export const openGraphImage = { images: ['http://...'] }
app/page.js
import { openGraphImage } from './shared-metadata'

export const metadata = {
  openGraph: {
    ...openGraphImage,
    title: 'Home',
  },
}
app/about/page.js
import { openGraphImage } from '../shared-metadata'

export const metadata = {
  openGraph: {
    ...openGraphImage,
    title: 'About',
  },
}

在上面的範例中,OG 圖片在 app/layout.jsapp/about/page.js 之間共享,而標題則不同。

繼承欄位 (Inheriting fields)

app/layout.js
export const metadata = {
  title: 'Acme',
  openGraph: {
    title: 'Acme',
    description: 'Acme is a...',
  },
}
app/about/page.js
export const metadata = {
  title: 'About',
}

// 輸出:
// <title>About</title>
// <meta property="og:title" content="Acme" />
// <meta property="og:description" content="Acme is a..." />

注意事項

  • app/layout.js 中的 titleapp/about/page.js 中的 title 替換
  • app/layout.js 中的所有 openGraph 欄位在 app/about/page.js 中被繼承,因為 app/about/page.js 未設定 openGraph 元資料。

動態圖片生成 (Dynamic Image Generation)

ImageResponse 建構函式允許您使用 JSX 和 CSS 生成動態圖片。這對於建立社群媒體圖片(如 Open Graph 圖片、Twitter 卡片等)非常有用。

ImageResponse 使用 Edge Runtime,Next.js 會自動為快取的圖片添加正確的標頭,有助於提高效能並減少重新計算。

要使用它,您可以從 next/server 匯入 ImageResponse

app/about/route.js
import { ImageResponse } from 'next/server'

export const runtime = 'edge'

export async function GET() {
  return new ImageResponse(
    (
      <div
        style={{
          fontSize: 128,
          background: 'white',
          width: '100%',
          height: '100%',
          display: 'flex',
          textAlign: 'center',
          alignItems: 'center',
          justifyContent: 'center',
        }}
      >
        Hello world!
      </div>
    ),
    {
      width: 1200,
      height: 600,
    }
  )
}

ImageResponse 與其他 Next.js API(包括路由處理器 (Route Handlers) 和基於檔案的元資料)整合良好。例如,您可以在 opengraph-image.tsx 檔案中使用 ImageResponse,以在構建時或請求時動態生成 Open Graph 圖片。

ImageResponse 支援常見的 CSS 屬性,包括 flexbox 和絕對定位、自訂字體、文字換行、置中和嵌套圖片。查看支援的 CSS 屬性完整列表

須知事項

  • 範例可在 Vercel OG Playground 中查看。
  • ImageResponse 使用 @vercel/ogSatori 和 Resvg 將 HTML 和 CSS 轉換為 PNG。
  • 僅支援 Edge Runtime。預設的 Node.js 運行時無法使用。
  • 僅支援 flexbox 和部分 CSS 屬性。進階佈局(如 display: grid)無法使用。
  • 最大套件大小為 500KB。套件大小包括您的 JSX、CSS、字體、圖片和其他資源。如果超過限制,請考慮減少資源大小或在運行時獲取。
  • 僅支援 ttfotfwoff 字體格式。為了最大化字體解析速度,優先使用 ttfotf 而非 woff

JSON-LD

JSON-LD 是一種結構化資料格式,可供搜尋引擎用來理解您的內容。例如,您可以使用它來描述人物、事件、組織、電影、書籍、食譜等許多類型的實體。

我們目前對 JSON-LD 的建議是在 layout.jspage.js 元件中將結構化資料渲染為 <script> 標籤。例如:

export default async function Page({ params }) {
  const product = await getProduct(params.id)

  const jsonLd = {
    '@context': 'https://schema.org',
    '@type': 'Product',
    name: product.name,
    image: product.image,
    description: product.description,
  }

  return (
    <section>
      {/* 將 JSON-LD 添加到頁面 */}
      <script
        type="application/ld+json"
        dangerouslySetInnerHTML={{ __html: JSON.stringify(jsonLd) }}
      />
      {/* ... */}
    </section>
  )
}

您可以使用 Rich Results Test(Google)或通用的 Schema Markup Validator 來驗證和測試您的結構化資料。

您可以使用社群套件(如 schema-dts)來為 JSON-LD 添加 TypeScript 類型:

import { Product, WithContext } from 'schema-dts'

const jsonLd: WithContext<Product> = {
  '@context': 'https://schema.org',
  '@type': 'Product',
  name: 'Next.js Sticker',
  image: 'https://nextjs.org/imgs/sticker.png',
  description: 'Dynamic at the speed of static.',
}