結構化資料(Structured Data)可以讓搜尋引擎更好地理解你的網頁內容,進而在搜尋結果中顯示豐富的摘要。本文介紹如何在 Astro 部落格實作自動 Schema 生成系統,支援 BlogPosting、FAQPage、HowTo 和 Breadcrumb。
什麼是 Schema 結構化資料?
Schema.org 定義了一套標準的詞彙,用於描述網頁內容。Google、Bing 等搜尋引擎會讀取這些資料,並在搜尋結果中呈現:
- BlogPosting:文章標題、發布日期、作者
- FAQPage:常見問題摺疊卡片
- HowTo:步驟教學列表
- Breadcrumb:麵包屑導航路徑
系統架構
我們的目標是從 Markdown 內容自動提取資訊,生成對應的 Schema:
Markdown 文章 │ ├── Frontmatter ─────────── BlogPosting Schema │ (title, published, description) │ ├── ## 常見問題 區塊 ────── FAQPage Schema │ (Q: ... A: ...) │ ├── ### 步驟 1/2/3 ──────── HowTo Schema │ └── category ────────────── Breadcrumb Schema實作步驟
步驟 1:建立 Schema 工具函式
在 src/utils/schema-utils.ts 建立各種 Schema 生成函式:
// BlogPosting Schema - 文章基本資訊export function generateBlogPostingSchema( entry: CollectionEntry<"posts">, author: { name: string; url: string }, siteUrl: string, lang: string) { return { "@context": "https://schema.org", "@type": "BlogPosting", headline: entry.data.title, description: entry.data.description, datePublished: entry.data.published.toISOString(), dateModified: entry.data.updated?.toISOString() || entry.data.published.toISOString(), author: { "@type": "Person", name: author.name, url: author.url, }, mainEntityOfPage: { "@type": "WebPage", "@id": `${siteUrl}/posts/${entry.slug}/`, }, inLanguage: lang, };}步驟 2:FAQ Schema 自動提取
從 Markdown 中找到「## 常見問題」區塊,提取 Q&A:
function extractFAQFromMarkdown(markdownContent: string): FAQItem[] { const faqs: FAQItem[] = [];
// 支援多種標題格式 const faqSectionMatch = markdownContent.match( /##\s*(常見問題|FAQ|常見疑問|Q\s*&\s*A)\s*\n([\s\S]*?)(?=\n##\s|$)/i );
if (!faqSectionMatch) return faqs;
const faqSection = faqSectionMatch[2]; const qaPairs = faqSection.split(/###\s*Q:/);
for (const pair of qaPairs) { if (!pair.trim()) continue;
const questionMatch = pair.match(/^([^]*?)(?=\n\s*A:)/); const answerMatch = pair.match(/A:\s*([^]*?)$/);
if (questionMatch && answerMatch) { faqs.push({ question: cleanMarkdownForSchema(questionMatch[1].trim()), answer: cleanMarkdownForSchema(answerMatch[1].trim()), }); } }
return faqs;}
export function generateFAQSchema(markdownContent: string) { const faqs = extractFAQFromMarkdown(markdownContent);
if (faqs.length === 0) return null;
return { "@context": "https://schema.org", "@type": "FAQPage", mainEntity: faqs.map((faq) => ({ "@type": "Question", name: faq.question, acceptedAnswer: { "@type": "Answer", text: faq.answer, }, })), };}步驟 3:HowTo Schema 自動提取
從 Markdown 找出步驟式標題(步驟 1、步驟 2…):
function extractStepsFromMarkdown(markdownContent: string): HowToStep[] { const steps: HowToStep[] = [];
// 匹配 ### 步驟 1: 標題 或 ### Step 1: Title const stepPattern = /###\s*(?:步驟|Step)\s*\d+[::]\s*([^\n]+)\n([\s\S]*?)(?=###\s*(?:步驟|Step)\s*\d+|##\s|$)/gi;
let match; while ((match = stepPattern.exec(markdownContent)) !== null) { steps.push({ name: match[1].trim(), text: cleanMarkdownForSchema(match[2].trim()), }); }
return steps;}
export function generateHowToSchema( markdownContent: string, title: string, description: string) { const steps = extractStepsFromMarkdown(markdownContent);
if (steps.length < 2) return null;
return { "@context": "https://schema.org", "@type": "HowTo", name: title, description: description, step: steps.map((step, index) => ({ "@type": "HowToStep", position: index + 1, name: step.name, text: step.text, })), };}步驟 4:Breadcrumb Schema
根據文章的 category 生成麵包屑:
export function generateBreadcrumbSchema( siteUrl: string, siteName: string, category: string, postTitle: string, postSlug: string) { return { "@context": "https://schema.org", "@type": "BreadcrumbList", itemListElement: [ { "@type": "ListItem", position: 1, name: siteName, item: siteUrl, }, { "@type": "ListItem", position: 2, name: category, item: `${siteUrl}/categories/${encodeURIComponent(category)}/`, }, { "@type": "ListItem", position: 3, name: postTitle, item: `${siteUrl}/posts/${postSlug}/`, }, ], };}步驟 5:整合至文章頁面
在 src/pages/posts/[...slug].astro 中生成並注入 Schema:
// 生成各種 Schemaconst schemas = [];
// BlogPosting(必備)schemas.push(generateBlogPostingSchema(entry, author, siteUrl, lang));
// FAQ(如果有常見問題區塊)const faqSchema = generateFAQSchema(entry.body);if (faqSchema) schemas.push(faqSchema);
// HowTo(如果有步驟教學)const howToSchema = generateHowToSchema( entry.body, entry.data.title, entry.data.description);if (howToSchema) schemas.push(howToSchema);
// Breadcrumbif (entry.data.category) { schemas.push(generateBreadcrumbSchema( siteUrl, siteName, entry.data.category, entry.data.title, entry.slug ));}在 HTML <head> 中注入:
{schemas.map((schema) => ( <script is:inline type="application/ld+json" set:html={JSON.stringify(schema)} />))}驗證 Schema
使用 Google 的工具驗證生成的 Schema:
- Rich Results Test - 測試單一網址
- Schema Markup Validator - 驗證 JSON-LD 語法
常見問題
Q: 為什麼 FAQ Schema 有時候不會生成?
A: FAQ Schema 只會在文章包含「## 常見問題」區塊,且有正確格式的 Q&A 時才會生成。格式要求是 ### Q: 問題 和 A: 答案。如果格式不對,extractFAQFromMarkdown 會回傳空陣列。
Q: HowTo Schema 的步驟數量有限制嗎?
A: Google 建議至少 2 個步驟。我們的實作會在步驟少於 2 個時回傳 null,不生成 HowTo Schema。
Q: 這些 Schema 對 SEO 有多大幫助?
A: Schema 本身不是排名因素,但可以提升 CTR(點擊率)。FAQ 會在搜尋結果顯示可展開的問答;HowTo 可能顯示步驟預覽。根據不同主題,CTR 提升可達 10-30%。
Q: 如何新增其他類型的 Schema?
A: 在 schema-utils.ts 新增對應的 generateXxxSchema 函式,然後在文章頁面中呼叫即可。常見的還有 Product、Review、Event 等類型。
總結
- BlogPosting:從 frontmatter 自動生成,每篇文章必備
- FAQPage:從「## 常見問題」區塊自動提取
- HowTo:從「### 步驟 N」標題自動提取
- Breadcrumb:根據 category 自動生成導航路徑
- 自動化:只需按照約定格式撰寫 Markdown,Schema 會自動生成
測試環境:macOS Sequoia 15.3, Astro 5.12.8, 2026/02/01
參考來源:
回報錯字、失效連結,或告訴我你想看的延伸主題。