🔍 進階 SEO 實戰:動態 Metadata 與 Open Graph

如果你架設了一個超酷的部落格或電商網站,但貼到 Facebook 或 LINE 群組時,只顯示一段醜醜的網址、沒有漂亮的縮圖與標題,那你的點擊率絕對會慘不忍睹。

在傳統 React (Client-side Rendering) 時代,要解決這個問題非常痛苦,你必須依賴額外的伺服器或是套件來做預先渲染 (Prerendering)。 但恭喜你,在 Next.js App Router 的世界裡,SEO 最佳化不僅是內建的,而且簡單到令人髮指!

本章我們將帶你徹底掌握 Metadata API,教你如何動態生成標題、描述,以及自動產生社群分享預覽圖 (Open Graph Image)。


1. 靜態 Metadata 設定

如果你只是要為首頁或特定的固定頁面設定標題,你只需要在該頁面的 page.tsxlayout.tsx 中匯出一個 metadata 常數即可。

// src/app/layout.tsx 或是 src/app/page.tsx
import { Metadata } from 'next';

export const metadata: Metadata = {
  title: 'Vibe Tutor 知識付費平台',
  description: '全台最強大的 SaaS 實戰開發課程,教你用 Next.js 打造網路印鈔機!',
  keywords: ['Next.js', 'React', 'SaaS', '教學', 'Vibe Tutor'],
  authors: [{ name: 'Vibe Coder' }],
  // Open Graph 設定,這是給 Facebook, LINE 抓取用的
  openGraph: {
    title: 'Vibe Tutor 知識付費平台',
    description: '全台最強大的 SaaS 實戰開發課程',
    url: 'https://vibe-tutor.com',
    siteName: 'Vibe Tutor',
    images: [
      {
        url: 'https://vibe-tutor.com/og-image.jpg',
        width: 1200,
        height: 630,
        alt: 'Vibe Tutor 課程預覽圖',
      },
    ],
    locale: 'zh_TW',
    type: 'website',
  },
  // Twitter 設定
  twitter: {
    card: 'summary_large_image',
    title: 'Vibe Tutor 知識付費平台',
    description: '全台最強大的 SaaS 實戰開發課程',
    creator: '@vibecoder',
    images: ['https://vibe-tutor.com/twitter-image.jpg'],
  },
};

export default function RootLayout({ children }: { children: React.ReactNode }) {
  // ...
}

💡 實戰小技巧:Template 標題

你可以在 layout.tsx 設定標題模板,這樣內頁就不用每次都重複寫品牌名稱了:

// src/app/layout.tsx
export const metadata: Metadata = {
  title: {
    template: '%s | Vibe Tutor', // 子頁面會取代 %s
    default: 'Vibe Tutor 知識付費平台', // 如果子頁面沒設定,就用這個
  },
};

// src/app/pricing/page.tsx
export const metadata: Metadata = {
  title: '定價方案', // 最終標題會自動變成 "定價方案 | Vibe Tutor"
};

2. 動態 Metadata (generateMetadata)

如果你有一個部落格文章頁面 /blog/[slug]/page.tsx,每篇文章的標題都不一樣,你該怎麼辦? 這時候你就不能用常數 metadata 了,你必須匯出一個非同步函數 generateMetadata

Next.js 會在渲染頁面「之前」,先執行這個函數去資料庫撈資料,然後生成出對應的 <title><meta> 標籤放在 HTML 的 <head> 裡!

// src/app/blog/[slug]/page.tsx
import { Metadata, ResolvingMetadata } from 'next';
import { db } from '@/lib/db'; // 你的資料庫

type Props = {
  params: { slug: string }
};

// 動態生成 Metadata
export async function generateMetadata(
  { params }: Props,
  parent: ResolvingMetadata
): Promise<Metadata> {
  // 1. 去資料庫尋找這篇文章
  const post = await db.post.findUnique({ where: { slug: params.slug } });

  // 如果文章不存在,你可以回傳一個預設的 404 標題
  if (!post) {
    return {
      title: '文章找不到',
    };
  }

  // Optionally access and extend (rather than replace) parent metadata
  const previousImages = (await parent).openGraph?.images || [];

  return {
    title: post.title,
    description: post.excerpt,
    openGraph: {
      title: post.title,
      description: post.excerpt,
      // 如果文章有自己的封面圖就用,沒有就用父層預設的
      images: post.coverImage ? [post.coverImage] : previousImages, 
    },
  };
}

export default async function BlogPostPage({ params }: Props) {
  // 這是實際的頁面內容渲染邏輯
  const post = await db.post.findUnique({ where: { slug: params.slug } });
  // ...
}

💡 效能擔憂?Next.js 已經幫你搞定了!

你可能會問:「在 generateMetadata 裡面查了一次資料庫,然後在下面的 BlogPostPage 裡面又查了一次資料庫,這樣不是浪費效能嗎?」 不用擔心!Next.js 底層使用了 fetch 快取與 React 的 cache 機制,同一個 Request 裡面對同一個 URL/查詢只會真實執行一次,後面的呼叫都會直接回傳快取結果,完全不傷效能!


3. 終極殺手鐧:自動產生 OG 圖片 (Image Generation)

你是不是常常看到 Vercel 或是 GitHub 的文章分享連結時,圖片上會「自動寫上文章的標題」? 這叫做動態 OG 圖片 (Dynamic Open Graph Image)。

以前要實作這個功能,你要去串接第三方 API 或是用 Headless Chrome 截圖,複雜到想放棄。 現在,Next.js 原生支援了 @vercel/og,你只需要寫 JSX,它就會自動幫你畫成一張圖片!

步驟 1:建立 opengraph-image.tsx 檔案

在你的路由資料夾底下(例如 src/app/blog/[slug]/),建立一個名為 opengraph-image.tsx (或 .alt.txt / .tsx 搭配 ImageResponse) 的特殊檔案。

// src/app/blog/[slug]/opengraph-image.tsx
import { ImageResponse } from 'next/og';
import { db } from '@/lib/db';

// 設定產生圖片的大小
export const runtime = 'edge';
export const alt = 'Blog Post Cover Image';
export const size = {
  width: 1200,
  height: 630,
};
export const contentType = 'image/png';

export default async function Image({ params }: { params: { slug: string } }) {
  // 抓取文章標題
  const post = await db.post.findUnique({ where: { slug: params.slug } });
  const title = post?.title || '精彩文章';

  return new ImageResponse(
    (
      // 在這裡寫 JSX!它會被轉成一張圖片!
      <div
        style={{
          fontSize: 80,
          background: 'linear-gradient(to bottom right, #4f46e5, #ec4899)', // 炫酷漸層
          color: 'white',
          width: '100%',
          height: '100%',
          display: 'flex',
          flexDirection: 'column',
          alignItems: 'center',
          justifyContent: 'center',
          padding: '80px',
          textAlign: 'center',
          fontWeight: 800,
        }}
      >
        {/* 顯示從資料庫抓來的動態標題! */}
        <div style={{ marginBottom: 40 }}>{title}</div>
        
        {/* 加上你的品牌 Logo 或文字 */}
        <div style={{ fontSize: 40, color: 'rgba(255,255,255,0.8)' }}>
          🚀 Vibe Tutor 實戰教學
        </div>
      </div>
    ),
    {
      ...size,
    }
  );
}

就這樣!不用任何設定,只要有人在 Facebook 貼上你這篇文章的網址: https://your-domain.com/blog/my-awesome-post Facebook 的爬蟲就會自動來讀取這張由 JSX 即時運算出來的漸層底色+大標題的超美圖片!

這就是 Next.js 被譽為最適合做 SEO 與商業網站的終極原因!掌握了 Metadata 與 OG Image,你的網站流量保證能突破天際!📈

解鎖完整教學內容

本章為付費內容。加入專案即可解鎖超過 5000 字的深度解析,包含 10 個以上神級 Prompt 與真實 Source Code 範例!