跳到主要内容

15 篇博文 含有标签「Docusaurus」

查看所有标签

Docusaurus 页面 hreflang 重复?内置 i18n 已自动生成

· 阅读需 4 分钟

多语言站点做 SEO 检查,翻页面源码发现 <link rel="alternate" hreflang="..."> 每个标签出现了两次——两套 hreflang 并存,语言码还互相重叠。

在开发 CCLEE Docusaurus Theme 时遇到此问题——基于 Docusaurus 3.x 的文档主题,内置多语言与 SEO 基础设施,hreflang 属于它必须原生做对的部份。

问题现象:head 里两套 hreflang 并存​

页面 head 中,同一 locale 的 alternate 链接出现两份——一套来自 Docusaurus 内置 i18n,一套来自某个自定义注入。单看每一套都对,合在一起就是冲突。自查一行命令:

curl -s https://your-site.com/ | grep -o 'hreflang="[^"]*"' | sort | uniq -c

每个语言码(含 x-default)出现超过一次即为重复。如果你搜的是「hreflang 重复」「duplicate hreflang」「Docusaurus hreflang 两个」,都是同一类问题。

根因:内置 i18n 已生成,插件再注入一套​

Docusaurus 配置好 i18n.locales 之后,内置 i18n 会自动为每个页面生成完整的 hreflang alternates(含 x-default)——这件事开箱即用,不需要任何插件参与。而站点此前的自定义 SEO 插件里,也写了一段 hreflang 注入逻辑,于是每个页面拿到两套:

  • 内置 i18n:按 locales 配置生成,随页面渲染输出;
  • 自定义插件:注入到 head 的另一套,语言码与内置生成的高度重叠。

hreflang 的语义是「声明」,不是「叠加」:两套声明只要有一条对不上(比如某 locale 的 URL 不同),对搜索引擎来说就是冲突标注。

解决方案:移除自定义注入,依赖内置 i18n​

修法是做减法——把自定义插件里的 hreflang 注入整段移除,只留 Docusaurus 内置生成的:

// 自定义 SEO 插件
- const links = locales.map((locale) => ({
- rel: 'alternate',
- hreflang: locale,
- href: urlFor(locale),
- }));
- // 注入 head ...

修复后的线上实测(两站首页):en-US、zh-CN、x-default 各恰好出现 1 次——干净的一套声明。改完跑一遍上面的 grep 自查,确认「每个语言码恰好一次」再收工。

边界与变体​

  • 什么时候才需要手动注入 hreflang:只有「非 Docusaurus 生成的页面」或「变体 URL 不遵循 i18n 路由规则」时才需要自定义;即便如此,也应先 grep 现状,确认内置没有覆盖你要声明的对。
  • 重复不只来自自家插件:多个 SEO 类插件叠加、主题自带 + 插件再来一份,都是常见来源;排查时按「每个 head 标签只有一个来源」的思路逐个禁用验证。
  • hreflang 与 canonical 的分工:canonical 解决「同语言重复内容」,hreflang 解决「跨语言变体」,二者不互相替代,也别让自定义逻辑把两者搅在一起。

注意事项

  • 内置能力先于自定义:Docusaurus 的 i18n、sitemap、meta 描述等 SEO 基建开箱即用,写注入逻辑前先确认内置是否已覆盖,否则就是在制造重复。
  • 发布前把 grep 自查纳入流程:一行命令的成本,拦住整类 head 标签冲突。
  • 搜索引擎对冲突 hreflang 的处理是忽略,不会报错提醒——静默失效,和 dotenv 截断是同一类「静默不报警」问题。

常见问题​

hreflang 标签是什么?​

它告诉搜索引擎页面有哪些语言/区域变体、分别对应哪个 URL,用于多语言站点的区域定向。形式为 <link rel="alternate" hreflang="zh-CN" href="...">。

hreflang 标签重复有什么影响?​

同一页面出现两套 hreflang(尤其 URL 或语言码冲突时),搜索引擎会忽略冲突的标注,区域定向随之失效——等于白配。实测重复场景:内置 i18n 已生成一套,自定义插件又注入一套。

Docusaurus 怎么生成 hreflang?​

开箱即用:配置 i18n 的 locales 后,Docusaurus 自动为每个页面输出全部 locale 的 alternates 与 x-default,无需任何插件。自查方法:curl 页面源码 grep hreflang 逐条计数。

CCLEE

独立开发者,24年电商行业实战经验,专注将AI能力落地于真实商业场景。

合作咨询

Docusaurus scripts 添加 inline 脚本构建失败?只支持 src 不接受 content

· 阅读需 5 分钟

在 docusaurus.config.ts 的 scripts 数组里用 { content: '...' } 注入 inline 脚本(例如百度统计的 IIFE),执行 npm run build 直接报错。

在开发 CCLEE Docusaurus Theme 时遇到此问题——基于 Docusaurus 3.x 的高级文档主题,紫色主题 + 深色模式 + Tailwind 排版增强,开箱即用的生产级文档站点模板。

TL;DR​

Docusaurus 的 scripts 配置只接受 src,不支持 inline content。把 inline 脚本挪到 static/js/ 下,用 { src: '/js/xxx.js', async: true } 引用即可;若脚本加载外部域名,还要同步更新 CSP。

问题现象​

按百度统计官方代码,本能地想直接塞进 scripts:

// docusaurus.config.ts
const config: Config = {
scripts: [
{
content: `var _hmt=_hmt||[];(function(){var hm=document.createElement("script");hm.src="https://hm.baidu.com/hm.js?XXXX";var s=document.getElementsByTagName("script")[0];s.parentNode.insertBefore(hm,s);})();`,
},
],
};

构建立刻失败:

[ERROR] Error: "scripts[1]" is invalid.
A script must be a plain string (the src), or an object with at least a "src" property.
at validateConfig (.../configValidation.js:397:15)

根因​

Docusaurus 的 scripts 配置在构建期被 validateScripts 逐条校验,每条只允许两种形态:

  1. 纯字符串:直接当作 src 处理
  2. 对象:必须包含 src 属性,可选 async、defer、data-* 等

设计上 scripts 只生成形如 <script src="..." /> 的标签,没有为 inline 脚本保留 content / innerHTML 字段。所以无论 inline 内容多短,校验都会在 src 缺失时直接抛错,本地构建、Vercel 远端构建同样失败。

想注入带构建期变量的 inline 脚本,应改用顶层的 headTags 配置(tagName: 'script' + innerHTML),而不是 scripts。

解决方案​

1. 把 inline 脚本放进 static/js/​

// static/js/baidu-tongji.js
var _hmt = _hmt || [];
(function () {
var hm = document.createElement('script');
hm.src = 'https://hm.baidu.com/hm.js?XXXX';
var s = document.getElementsByTagName('script')[0];
s.parentNode.insertBefore(hm, s);
})();

static/ 目录下的文件会被原样拷贝到站点根目录,最终 URL 即 /js/baidu-tongji.js。

2. 在 scripts 用 src 引用​

// docusaurus.config.ts
scripts: [
// 已有的 Umami(参见 /blog/docusaurus-umami-analytics)
{
src: 'https://tj.ccleeai.com/script.js',
async: true,
'data-website-id': 'xxxx',
},
// 百度统计:走静态文件
{
src: '/js/baidu-tongji.js',
async: true,
},
],

3. 同步更新 CSP​

如果站点启用了 Content-Security-Policy(推荐做法,参见我们之前总结的 Umami 集成与 CSP 配置),新加的外部域名必须放行,否则脚本会被浏览器拦截:

themeConfig: {
metadata: [
{
'http-equiv': 'Content-Security-Policy',
// script-src 加 https://hm.baidu.com
// connect-src、img-src 同步放行(hm.js 会发图片像素和 fetch 上报)
content: "default-src 'self'; " +
"script-src 'self' 'unsafe-inline' 'unsafe-eval' https://tj.ccleeai.com https://hm.baidu.com; " +
"connect-src 'self' https://tj.ccleeai.com https://hm.baidu.com; " +
"img-src 'self' data: https://hm.baidu.com; " +
"style-src 'self' 'unsafe-inline'; " +
"object-src 'none'; base-uri 'self'",
},
],
},

百度统计的 IIFE 内部用 document.createElement('script') 动态注入 <script src="hm.baidu.com/...">,所以光放 'unsafe-inline' 不够,必须把 hm.baidu.com 加进 script-src。

注意事项

  • scripts 数组里的纯字符串和 src 是等价的:'https://x/a.js' 与 { src: 'https://x/a.js' } 等效
  • 路径以 / 开头时是相对站点根目录(static/ 拷贝产物),不是文件系统根
  • 若脚本依赖运行期变量、必须 inline,请用顶层 headTags 而非 scripts;headTags 支持 innerHTML
  • 多套统计(Umami + 百度 + GA)可以共存,但每个外部域名都要单独加进 CSP,否则该域名上报被静默拦截

常见问题​

如何在 Docusaurus 配置自定义 scripts?​

在 docusaurus.config.ts 顶层的 scripts 数组中添加条目,每条要么是字符串(视作 src),要么是带 src 属性的对象。inline 内容不能用 content 字段——把脚本放进 static/js/ 目录后用 src: '/js/xxx.js' 引用即可。如果还需要 async、defer 或自定义 data-* 属性,把它们和 src 放在同一个对象里。

Docusaurus scripts 为什么不支持 inline content?​

Docusaurus 的 scripts 配置在构建期生成的是 <script src="..."> 标签,本身没有为 inline 脚本设计字段。校验器(configValidation.ts 中的 validateScripts)逐条检查,只要对象缺少 src 就抛出 A script must be a plain string, or an object with at least a "src" property,无论你的 content 写得多完整。这是刻意的 API 边界,把 inline 注入的能力交给了 headTags。

Docusaurus 如何注入 inline JavaScript(如百度统计)?​

最稳的方式是「静态文件 + 内部动态注入」:把官方那段 IIFE 脚本完整保存到 static/js/baidu-tongji.js,scripts 用 { src: '/js/baidu-tongji.js', async: true } 引用;脚本内部再 document.createElement('script') 加载 hm.baidu.com/hm.js。最后别忘了在 CSP 的 script-src、connect-src、img-src 都加上 https://hm.baidu.com,否则上报会被浏览器拦截。

CCLEE

独立开发者,24年电商行业实战经验,专注将AI能力落地于真实商业场景。

合作咨询