Skip to content

Commit 8c7e5fe

Browse files
committed
feat(seo): enhance seo and security configurations
- Remove static robots.txt and generate it dynamically during build - Add security headers in vercel.json - Improve structured data with BreadcrumbList and Course schema - Make site URL dynamic based on deployment environment - Enhance sitemap configuration with priority and changefreq
1 parent 9a3dc7f commit 8c7e5fe

3 files changed

Lines changed: 136 additions & 26 deletions

File tree

docs/.vitepress/config.mjs

Lines changed: 90 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,22 @@ const isEdgeOne = !!process.env.EDGEONE || process.env.EDGEONE === '1'
1212
// 3. 否则(如 GitHub Pages),使用 '/easy-vibe/'
1313
const base = process.env.BASE || (isVercel || isEdgeOne ? '/' : '/easy-vibe/')
1414

15+
// 站点 URL 配置 - 根据部署环境动态确定
16+
const getSiteUrl = () => {
17+
if (isVercel && process.env.VERCEL_URL) {
18+
return `https://${process.env.VERCEL_URL}`
19+
}
20+
if (isEdgeOne && process.env.EDGEONE_URL) {
21+
return `https://${process.env.EDGEONE_URL}`
22+
}
23+
if (process.env.SITE_URL) {
24+
return process.env.SITE_URL
25+
}
26+
return 'https://datawhalechina.github.io/easy-vibe'
27+
}
28+
29+
const siteUrl = getSiteUrl()
30+
1531
// 语言映射配置
1632
const localeMap = {
1733
'zh-cn': {
@@ -79,9 +95,6 @@ const localeMap = {
7995
// SEO 相关配置
8096
const getSeoHead = (locale, title, description, path = '') => {
8197
const seoConfig = localeMap[locale] || localeMap['zh-cn']
82-
const siteUrl = isVercel
83-
? 'https://your-project.vercel.app'
84-
: 'https://datawhalechina.github.io/easy-vibe'
8598
const canonicalUrl = path ? `${siteUrl}${path}` : `${siteUrl}/${locale}/`
8699
const ogImageUrl = `${siteUrl}${base}logo.png`
87100

@@ -172,10 +185,41 @@ const getSeoHead = (locale, title, description, path = '') => {
172185
'@type': 'ImageObject',
173186
url: ogImageUrl
174187
}
188+
},
189+
mainEntity: {
190+
'@type': 'Course',
191+
name: title,
192+
description: description,
193+
provider: {
194+
'@type': 'Organization',
195+
name: 'Datawhale',
196+
sameAs: 'https://github.com/datawhalechina/easy-vibe'
197+
}
175198
}
176199
}
177200
head.push(['script', { type: 'application/ld+json' }, JSON.stringify(jsonLd)])
178201

202+
// 添加 BreadcrumbList 结构化数据
203+
const breadcrumbJsonLd = {
204+
'@context': 'https://schema.org',
205+
'@type': 'BreadcrumbList',
206+
itemListElement: [
207+
{
208+
'@type': 'ListItem',
209+
position: 1,
210+
name: '首页',
211+
item: siteUrl
212+
},
213+
{
214+
'@type': 'ListItem',
215+
position: 2,
216+
name: locale === 'zh-cn' ? '教程' : 'Tutorial',
217+
item: `${siteUrl}/${locale}/`
218+
}
219+
]
220+
}
221+
head.push(['script', { type: 'application/ld+json', class: 'breadcrumb-jsonld' }, JSON.stringify(breadcrumbJsonLd)])
222+
179223
return head
180224
}
181225

@@ -1053,25 +1097,64 @@ export default defineConfig({
10531097

10541098
// Sitemap 配置
10551099
sitemap: {
1056-
hostname: 'https://datawhalechina.github.io/easy-vibe',
1100+
hostname: siteUrl,
1101+
changefreq: 'weekly',
1102+
priority: {
1103+
'/': 1.0,
1104+
'/zh-cn/': 0.9,
1105+
'/zh-cn/stage-0/': 0.9,
1106+
'/zh-cn/stage-1/': 0.8,
1107+
'/zh-cn/stage-2/': 0.8,
1108+
'/zh-cn/stage-3/': 0.8,
1109+
'/zh-cn/appendix/': 0.7
1110+
},
10571111
transformItems(items) {
1058-
// 过滤掉旧版内容和未完成的语言版本
10591112
return items.filter((item) => {
10601113
const url = item.url
1061-
// 排除旧版内容目录
10621114
if (
10631115
url.includes('/extra/') ||
10641116
url.includes('/examples/') ||
10651117
url.includes('/project/')
10661118
) {
10671119
return false
10681120
}
1069-
// 包含所有语言版本
10701121
return true
10711122
})
10721123
}
10731124
},
10741125

1126+
// 构建结束时动态生成 robots.txt
1127+
async buildEnd(siteConfig) {
1128+
const fs = await import('fs')
1129+
const path = await import('path')
1130+
1131+
const robotsTxt = `# https://www.robotstxt.org/robotstxt.html
1132+
User-agent: *
1133+
Allow: /
1134+
1135+
# 禁止搜索引擎抓取旧版内容(已迁移到新目录结构)
1136+
Disallow: /zh-cn/extra/
1137+
Disallow: /zh-cn/examples/
1138+
Disallow: /zh-cn/project/
1139+
Disallow: /en/extra/
1140+
Disallow: /en/examples/
1141+
Disallow: /en/project/
1142+
1143+
# 禁止抓取 VitePress 缓存和构建文件
1144+
Disallow: /.vitepress/
1145+
Disallow: /@fs/
1146+
1147+
# Sitemap 位置
1148+
Sitemap: ${siteUrl}/sitemap.xml
1149+
`
1150+
1151+
const outDir = siteConfig.outDir || path.resolve(__dirname, '.vitepress/dist')
1152+
const robotsPath = path.join(outDir, 'robots.txt')
1153+
1154+
fs.writeFileSync(robotsPath, robotsTxt, 'utf-8')
1155+
console.log('✓ Generated robots.txt with sitemap URL:', `${siteUrl}/sitemap.xml`)
1156+
},
1157+
10751158
// 多语言配置 - 使用 cn/en-us/ja 结构
10761159
locales: {
10771160
// 根路径 — 仅用于 404 页面兜底,实际首页由 docs/index.md 自动重定向

docs/public/robots.txt

Lines changed: 0 additions & 18 deletions
This file was deleted.

vercel.json

Lines changed: 46 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,5 +2,50 @@
22
"buildCommand": "npm run build",
33
"installCommand": "npm install",
44
"framework": "vitepress",
5-
"outputDirectory": "docs/.vitepress/dist"
5+
"outputDirectory": "docs/.vitepress/dist",
6+
"headers": [
7+
{
8+
"source": "/(.*)",
9+
"headers": [
10+
{
11+
"key": "X-Content-Type-Options",
12+
"value": "nosniff"
13+
},
14+
{
15+
"key": "X-Frame-Options",
16+
"value": "DENY"
17+
},
18+
{
19+
"key": "X-XSS-Protection",
20+
"value": "1; mode=block"
21+
},
22+
{
23+
"key": "Referrer-Policy",
24+
"value": "strict-origin-when-cross-origin"
25+
},
26+
{
27+
"key": "Permissions-Policy",
28+
"value": "camera=(), microphone=(), geolocation=()"
29+
}
30+
]
31+
},
32+
{
33+
"source": "/sitemap.xml",
34+
"headers": [
35+
{
36+
"key": "Cache-Control",
37+
"value": "public, max-age=86400, s-maxage=86400"
38+
}
39+
]
40+
},
41+
{
42+
"source": "/robots.txt",
43+
"headers": [
44+
{
45+
"key": "Cache-Control",
46+
"value": "public, max-age=86400, s-maxage=86400"
47+
}
48+
]
49+
}
50+
]
651
}

0 commit comments

Comments
 (0)