|
1 | 1 | import { source } from "@/lib/source"; |
2 | 2 | import { safeJsonLdString } from "@/lib/json-ld"; |
3 | 3 | import { SITE_URL } from "@/lib/site-url"; |
| 4 | +import { ensureSeoDescription } from "@/lib/seo-description"; |
4 | 5 | import { DocsPage, DocsBody } from "fumadocs-ui/page"; |
5 | 6 | import { notFound } from "next/navigation"; |
6 | 7 | import type { Metadata } from "next"; |
@@ -65,12 +66,23 @@ export default async function DocPage({ params }: Param) { |
65 | 66 | ? `${SITE_URL}/${locale}/docs/${slugPath}` |
66 | 67 | : `${SITE_URL}/${locale}/docs`; |
67 | 68 |
|
| 69 | + // JSON-LD description 同步走兜底:避免结构化数据里出现空字符串,否则 |
| 70 | + // Google Rich Results 测试会 warning。与 generateMetadata 里的逻辑一致。 |
| 71 | + const sectionPathForJsonLd = |
| 72 | + (slug ?? []).length > 1 ? (slug ?? []).slice(0, -1) : []; |
| 73 | + const articleDescription = ensureSeoDescription({ |
| 74 | + description: page.data.description, |
| 75 | + title: page.data.title, |
| 76 | + sectionPath: sectionPathForJsonLd, |
| 77 | + locale, |
| 78 | + }); |
| 79 | + |
68 | 80 | // TechArticle: 让 docs 在 Google 搜索结果上更可能展示为技术文章卡片 |
69 | 81 | const articleJsonLd = { |
70 | 82 | "@context": "https://schema.org", |
71 | 83 | "@type": "TechArticle", |
72 | 84 | headline: page.data.title, |
73 | | - description: page.data.description, |
| 85 | + description: articleDescription, |
74 | 86 | url: docUrl, |
75 | 87 | inLanguage: locale === "en" ? "en-US" : "zh-CN", |
76 | 88 | publisher: { |
@@ -190,21 +202,35 @@ export async function generateMetadata({ params }: Param): Promise<Metadata> { |
190 | 202 | "", |
191 | 203 | ); |
192 | 204 |
|
| 205 | + // SEO description 兜底:page.data.description 可能为 undefined/空/极短 |
| 206 | + // (96 个 leetcode 题解完全没 description,67 个空,35 个 < 20 字符)。 |
| 207 | + // 用 ensureSeoDescription 拼 title + 面包屑 + 站点 tagline 补到 80+ 字符, |
| 208 | + // 让 Bing/Google 拿到完整摘要而不是从正文随便抓一段。 |
| 209 | + // sectionPath 取 slug 除末段外的所有段(末段是当前页本身,已在 title 里)。 |
| 210 | + const slugArr = slug ?? []; |
| 211 | + const sectionPath = slugArr.length > 1 ? slugArr.slice(0, -1) : []; |
| 212 | + const safeDescription = ensureSeoDescription({ |
| 213 | + description: page.data.description, |
| 214 | + title: page.data.title, |
| 215 | + sectionPath, |
| 216 | + locale, |
| 217 | + }); |
| 218 | + |
193 | 219 | return { |
194 | 220 | title: page.data.title, |
195 | | - description: page.data.description, |
| 221 | + description: safeDescription, |
196 | 222 | alternates: { canonical, languages: langs }, |
197 | 223 | openGraph: { |
198 | 224 | type: "article", |
199 | 225 | title: page.data.title, |
200 | | - description: page.data.description, |
| 226 | + description: safeDescription, |
201 | 227 | url: canonical, |
202 | 228 | locale: locale === "en" ? "en_US" : "zh_CN", |
203 | 229 | }, |
204 | 230 | twitter: { |
205 | 231 | card: "summary_large_image", |
206 | 232 | title: page.data.title, |
207 | | - description: page.data.description, |
| 233 | + description: safeDescription, |
208 | 234 | }, |
209 | 235 | }; |
210 | 236 | } |
0 commit comments