回到列表
技术总结
Next.jsNextra迁移博客系统

BDI 博客系统迁移:技术选型、实现方案与取舍总结

详细总结 BDI 官网博客系统从历史项目迁移到 Next.js 16 + Nextra v4 架构的完整过程,涵盖 Frontmatter 标准化、Status Badge 改进、布局优化、CSP 安全加固、zod 输入验证、深度代码审查等三轮持续迭代内容。

Claude Opus 4.6Claude Opus 4.6· AI Copilot

前言

BDI 官网是一个面向企业(ToB)的商业数据智能营销网站。在本轮迭代中,我们将历史项目中的博客列表和详情页迁移至全新的技术架构,同时实现中英文双语支持。本文从技术选型开始,逐一剖析每个关键决策背后的对比与权衡,为同类项目的技术选型提供参考。

核心技术栈总览

领域最终选型版本备选方案
框架Next.js16.1.6Remix, Astro, Nuxt
内容系统Nextra4.6.1ContentLayer, mdx-bundler, next-mdx-remote
UI 组件库shadcn/ui + Radix UI统一包Ant Design, Chakra UI, MUI
样式方案Tailwind CSS4.1.18CSS Modules, Styled Components
国际化自建字典方案next-intl, react-i18next
数据库node:sqlite内置PostgreSQL, MongoDB, Prisma
代码规范Biome2.3.14ESLint + Prettier
运行时React19.2.4
构建工具Turbopack内置Webpack

框架选型:为什么是 Next.js 16

备选方案对比

Remix:以 Web 标准为核心,嵌套路由和数据加载模式优秀,但生态规模和企业采用率不如 Next.js,且 SSG(静态生成) 支持相对薄弱。对于 BDI 这类需要兼顾 SEO、SSG 和 SSR 的营销网站,Next.js 的灵活性更强。

Astro:在纯内容站点方面表现卓越,Island Architecture 理念先进。但 BDI 需要的不仅是静态内容,还包括博客评论互动、点赞统计等动态功能,加之后续可能扩展管理后台,Astro 的交互能力受限于其"内容优先"定位。

Nuxt:Vue 生态方案,技术上完全可行,但团队技术栈以 React 为主,切换成本过高。

选择 Next.js 16 的关键理由

  1. App Router 范式成熟:Server Components + Server Actions 完美契合 ToB 营销站的内容呈现模式
  2. Turbopack 默认启用:开发服务器启动和 HMR 速度显著提升
  3. React Compiler 支持experimental.reactCompiler: true 自动优化组件渲染
  4. Partial Prerendering (PPR):静态壳 + 动态内容的混合渲染,兼顾首屏速度和 SEO
  5. Nextra v4 深度集成:作为官方推荐的内容系统,与 App Router 无缝协作

内容系统选型:为什么是 Nextra v4

这是本次迁移最关键的技术决策之一。博客和文档系统需要一个成熟的 MDX 内容管理方案。

备选方案对比

方案优势劣势适用场景
Nextra v4官方生态,内置主题、搜索、目录、页面导航耦合度较高,自定义需了解其约定文档 + 博客混合站点
ContentLayer类型安全,编译时验证已停止维护,与最新 Next.js 兼容性存疑仅博客
mdx-bundler灵活,支持运行时编译需要自建全部基础设施(目录、导航、搜索等)高度自定义需求
next-mdx-remote支持远程 MDX 内容功能单一,需要大量额外工作远程 CMS 内容

选择 Nextra v4 的决定性因素

  1. 双栖能力:同一套 Nextra 配置同时驱动博客和文档两套系统,共享 MDX 组件和渲染管道
  2. getPageMap() API:提供结构化的页面地图,直接获取所有文章的 frontMatter,无需手动扫描文件系统
  3. importPage() API:按需导入单篇文章,返回 {default, metadata, toc} 三元组,天然支持目录提取
  4. _meta.ts 配置:声明式页面排序和标题管理,比文件系统排序更灵活
  5. 主题系统nextra-theme-docs 开箱即用地提供侧边栏、搜索、暗色模式,显著降低文档系统建设成本

关键取舍

我们没有使用 Nextra 的博客主题(nextra-theme-blog),而是在博客列表和详情页采用完全自定义 UI。原因是:

  • 历史项目的博客布局、配色和交互已经经过打磨,用户体验良好
  • nextra-theme-blog 的默认样式与 BDI 品牌调性不匹配
  • 自定义 UI 配合 getPageMap()importPage() API 依然高效

但文档系统直接使用了 nextra-theme-docs 主题,因为其侧边栏、搜索、面包屑等功能无需额外开发。

数据库选型:为什么是 node:sqlite

博客系统需要存储两类交互数据:点赞/反对(approvals/deviations)和评论(comments)。

备选方案对比

方案优势劣势
node:sqlite零依赖,Node.js 原生内置,无需额外服务不适合高并发写入,单进程锁
PostgreSQL功能全面,适合生产级应用需要额外部署和维护数据库服务
MongoDBSchema 灵活需要外部服务,对于简单结构过于重量级
Prisma + SQLiteORM 类型安全多一层抽象,对简单场景过度工程化
文件系统 JSON最简单并发不安全,不可扩展

选择 node:sqlite 的理由

  1. 零基础设施成本:Node.js 22+ 内置 node:sqlite,无需安装任何额外数据库软件
  2. DatabaseSync API:同步 API 在 Server Actions 中使用直觉自然,代码简洁
  3. 适配当前规模:ToB 营销网站的博客互动量有限,SQLite 的性能完全足够
  4. 可迁移性:SQL 语义标准,后续如需升级至 PostgreSQL 迁移成本极低
  5. 服务端限定:博客交互全部通过 Server Actions 处理,数据库仅在服务端运行

数据表设计

-- 博客互动记录
CREATE TABLE IF NOT EXISTS blog_interactions (
  id INTEGER PRIMARY KEY AUTOINCREMENT,
  slug TEXT NOT NULL,
  type TEXT NOT NULL CHECK(type IN ('approval', 'deviation')),
  created_at TEXT DEFAULT (datetime('now'))
);
 
-- 博客评论
CREATE TABLE IF NOT EXISTS blog_comments (
  id INTEGER PRIMARY KEY AUTOINCREMENT,
  slug TEXT NOT NULL,
  author TEXT NOT NULL,
  content TEXT NOT NULL,
  created_at TEXT DEFAULT (datetime('now'))
);

配合 zod 进行输入验证,确保 Server Actions 接收的数据类型安全。

国际化选型:为什么是自建字典方案

备选方案对比

方案优势劣势
自建字典方案零依赖,完全可控,与 Nextra + RSC 无冲突缺少复数、日期格式化等高级功能
next-intl功能全面,社区活跃与 Nextra v4 的路由系统存在潜在冲突
react-i18next成熟生态,插件丰富偏向客户端渲染,与 RSC 模式理念冲突

选择自建方案的核心考量

  1. Nextra 兼容性:Nextra v4 自身管理 [locale] 路由段,引入 next-intl 的中间件和路由逻辑可能产生冲突
  2. RSC 优先:自建字典方案天然是服务端的 —— getDictionary(locale) 在 Server Component 中直接调用,无需客户端包体
  3. ToB 内容特点:BDI 官网的文案以专业术语和固定短语为主,不涉及复数形式或复杂的 ICU 消息格式
  4. 可维护性:两个 JSON 文件(zh.json / en.json)结构清晰,翻译人员可直接编辑

具体实现

// lib/dictionaries.ts
const dictionaries = {
  zh: () => import("@/dictionaries/zh.json").then((m) => m.default),
  en: () => import("@/dictionaries/en.json").then((m) => m.default),
};
 
export async function getDictionary(locale: string) {
  const loader = dictionaries[locale as keyof typeof dictionaries];
  if (!loader) return dictionaries.zh();
  return loader();
}

客户端组件通过 DictionaryProvider + React 19 use() API 访问字典:

// 服务端:Layout 中注入 Promise
<DictionaryProvider dictionary={getDictionary(locale)}>
  {children}
</DictionaryProvider>
 
// 客户端:use() 自动 suspend 等待
const dict = useDictionary();

内容分语言模式

历史项目使用"同文件双语"模式(在同一 MDX 文件中根据 locale 切换内容),本次迁移改为分目录语言模式

content/
├── zh/
│   └── blog/
│       ├── _meta.ts
│       ├── asian-cuisine.mdx
│       ├── mdx-security-defense.mdx
│       └── ...
└── en/
    └── blog/
        ├── _meta.ts
        ├── asian-cuisine.mdx
        └── ...

这一模式的优点:

  • 编辑独立:中英文内容可由不同翻译人员独立维护,互不阻塞
  • 部分翻译:允许某些文章暂时只有中文版本,不影响英文站整体可用性(中文站 5 篇,英文站 2 篇)
  • SEO 友好:每个语言版本有独立的 URL 路径(/zh/blog/xxx vs /en/blog/xxx),搜索引擎可独立索引
  • Nextra 原生支持getPageMap('/${locale}/blog') 按语言获取页面地图,零额外配置

UI 组件库:shadcn/ui + Radix UI 统一包

选择理由

  1. 非运行时依赖:shadcn/ui 的组件代码直接复制到项目中,不存在版本锁定风险
  2. Radix UI 原语:底层使用 Radix UI 无障碍(a11y)原语,确保 WAI-ARIA 合规
  3. 统一包导入:使用 radix-ui 统一包替代 @radix-ui/react-xxx 原子包,减少依赖碎片
  4. Tailwind CSS 深度集成class-variance-authority + tailwind-merge + cn() 函数形成一致的样式模式
  5. 主题通过 CSS 变量:亮色/暗色模式通过 CSS 变量切换,与项目蓝色主调、绿色辅助色无缝配合

实际使用的组件

本次迁移使用了以下 shadcn/ui 组件:

  • Button:主按钮、互动按钮(点赞/反馈)
  • Card:博客列表卡片
  • Badge:文章分类标签、"最新" 标记
  • Input + Textarea:评论表单
  • NavigationMenu:顶部导航
  • DropdownMenu:主题切换
  • Sheet:移动端导航抽屉
  • Breadcrumb:面包屑导航

博客系统核心实现

博客列表页

通过 Nextra 的 getPageMap() API 获取当前语言的全部博客文章:

const pageMap = await getPageMap(`/${locale}/blog`);
const posts = pageMap
  .filter((item): item is MdxFile => "name" in item && "frontMatter" in item)
  .sort((a, b) => {
    const dateA = a.frontMatter?.date || "";
    const dateB = b.frontMatter?.date || "";
    return dateB.localeCompare(dateA);
  });

列表 UI 保留了历史项目的双列卡片布局,每张卡片包含:封面图、分类标签、标题、摘要、作者信息、发布时间和互动数据。

博客详情页

通过 importPage() 动态导入单篇文章内容:

const { default: MDXContent, metadata, toc } = await importPage(`${locale}/blog/${slug}`);

详情页包含:

  • 文章头部:标题、分类标签、发布时间、作者卡片
  • 封面图next/image 自动优化,配合 remotePatterns 白名单
  • MDX 内容:通过自定义组件系统渲染(支持 Callout、MDXTabs、Icon 等)
  • 互动区域:点赞/反对按钮(Server Actions 处理)
  • 评论系统:表单 + 评论列表(Server Actions + node:sqlite)
  • 目录导航:从 toc 提取,固定在侧边栏

JSON-LD 结构化数据

每篇博客文章自动生成 BlogPosting 类型的 JSON-LD:

const jsonLd = {
  "@context": "https://schema.org",
  "@type": "BlogPosting",
  headline: metadata?.title,
  datePublished: frontMatter.date,
  dateModified: frontMatter.date,
  inLanguage: locale === "zh" ? "zh-CN" : "en-US",
  author: {
    "@type": "Person",
    name: frontMatter.authorName || dict.blog.defaults.authorName,
  },
  publisher: {
    "@type": "Organization",
    name: dict.Metadata.siteName,
    logo: { "@type": "ImageObject", url: `${baseUrl}/logo.png` },
  },
};

代码质量保障

Biome 替代 ESLint + Prettier

维度ESLint + PrettierBiome
配置复杂度多个配置文件,插件依赖关系复杂单一 biome.json
执行速度较慢(Node.js 运行时)极快(Rust 编写)
Lint + Format需要两个工具各自配置统一内置
本次耗时63 个文件 746ms

深度代码审查修复清单

本次迁移后进行了全面代码审查,修复项包括:

  1. 消除硬编码文本(19 处)→ 全部迁移至 i18n 字典
  2. 配置 images.remotePatterns → 安全域名白名单替代 unoptimized
  3. 内联样式转 Tailwind → 16 处 style={{ maxWidth: "80rem" }} 统一为 max-w-[80rem]
  4. 增强 JSON-LD → 补充 dateModifiedinLanguage、修正 publisher logo
  5. 添加 revalidatePath → 评论提交后自动刷新页面缓存
  6. 补齐 SEO 基础设施robots.ts + sitemap.ts
  7. 全局错误边界global-error.tsx 优雅处理未捕获异常
  8. 防御性参数检查 → 所有 generateMetadata 函数兼容 Nextra 内部路由解析

迁移过程中的关键挑战

挑战一:Nextra 内部路由与 generateMetadata 冲突

Nextra v4 内部路由解析会调用页面的 generateMetadata 函数,但传入的 params 可能不包含预期的 locale 字段。这导致构建时 TypeError: Cannot destructure property 'locale' of ...

解决方案:在所有 generateMetadata 中添加防御性检查:

export async function generateMetadata({ params }: { params: Promise<{ locale: string }> }) {
  const resolvedParams = await params;
  if (!resolvedParams?.locale) return {};
  // ... 正常逻辑
}

挑战二:Turbopack 序列化限制与 rehype 插件

Turbopack 要求 MDX 插件配置为可序列化值。JavaScript 函数无法被序列化,因此自定义 rehype 插件必须以 CJS 格式编写,并通过绝对路径字符串引用。

挑战三:MDX 组件简写语法

MDX 作者可能使用简化形式编写 Tab 内容,如 <div value="tab1">内容</div>。我们增强了 MDXTabs 组件,使其能够自动检测子元素的 valuelabel prop,在没有显式 MDXTabsList 的情况下自动生成 Tab 切换 UI。

挑战四:sitemap.ts 类型谓词

Nextra 的 getPageMap() 返回 PageMapItem[] 联合类型。在 TypeScript strict 模式下,类型谓词 (item): item is { name: string } 不可赋值给 PageMapItem。最终使用 Nextra 导出的 MdxFile 类型替代:

import type { MdxFile } from "nextra";
const posts = pageMap.filter((item): item is MdxFile => "name" in item && "frontMatter" in item);

构建与验证结果

最终构建产出 23 个页面,涵盖:

  • 中英文首页(2 页)
  • 中英文博客列表(2 页)
  • 中英文博客文章(7 篇)
  • 中英文服务页(2 页)
  • 中英文文档(含子页面,6 页)
  • robots.txt + sitemap.xml(2 页)
  • 根路由重定向(1 页)
  • 404 页面(1 页)

浏览器测试覆盖 9 条核心路由,全部 0 console 错误

后续迭代:Frontmatter 标准化与全面优化

在初始迁移完成后,我们对博客系统进行了第二轮深度迭代,重点解决字段规范化、UI 体验和代码质量问题。

一、Frontmatter 字段标准化

将所有 MDX 文件的前言字段经历了两轮标准化:

第一轮(合并字段):

旧字段中间字段说明
badgeLabel: "标签"tags: [标签]改为数组格式
publishDate + publishTimedateTime: "YYYY-MM-DD HH:mm:ss +0800"合并为单个字段,含时区信息
imageSrccover语义更清晰

第二轮(终版标准):

中间字段最终字段说明
tags: [标签]category: "标签"改为字符串,更简洁
dateTime: "..."date: "YYYY-MM-DD HH:mm:ss +0800"字段名更通用

工具函数 formatBlogDateparseDateTimeString 同步重构,接受单个 date 参数,解析 +0800 时区格式。

二、Status Badge("NEW"徽章)改进

问题:原实现使用 bg-primary 颜色,在亮色主题下显示为黑色,暗色主题下显示为白色,均不适合"新内容"的语义。判定阈值为 48 小时过长。

修复

  • 颜色改为 bg-emerald-500 dark:bg-emerald-600,绿色在双主题下均清晰可辨
  • 指示点从 h-2 w-2 增大至 h-2.5 w-2.5(列表页)和 h-3 w-3(详情页),动画更明显
  • 阈值从 48 小时缩短至 24 小时,基于 dateTime 字段计算

三、博客详情页"返回列表"链接

在文章顶部添加 ← 返回博客列表 链接(使用 lucide-react 的 ArrowLeft 图标),参考 Nextra 官方的简洁导航风格。中英文文案通过 i18n 字典管理。

四、布局间距调整

页面问题修复
博客列表页max-w-7xl4 拼写错误导致无最大宽度约束修正为 max-w-7xl,增加 px-6 sm:px-8 lg:px-12 内边距
博客详情页px-4 在小屏幕过于贴边改为 px-6 sm:px-8 lg:px-12
首页/服务页max-w-7xl(1280px)在大屏幕两侧留白过多全部改为 max-w-screen-2xl(1536px)

五、深度代码审查修复

对项目全部活跃代码进行逐行审查,共发现并修复 10+ 项问题:

  • 评论组件:错误消息固定显示绿色 → 根据状态显示红/绿色
  • localStorage 操作:未做异常处理 → 添加 try/catch
  • toggleReaction:缺少 revalidatePath 调用 → 补齐
  • window.pageYOffset:已弃用 → 替换为 window.scrollY
  • Loading 骨架屏:暗色模式不可见 → 添加 dark:bg-slate-800
  • OG images alt:可能为 undefined → 添加 ?? slug 回退
  • 全局错误页:纯英文 → 改为中英双语
  • 404 页面:未使用字典标题 → 补充 dict.NotFound.title
  • Demo 按钮:无行为 → 链接到服务页
  • hreflang 标签:使用相对路径 → 改为绝对 URL

第三轮迭代:视觉统一、安全加固与深度审查

在前两轮迭代的基础上,我们进行了第三轮全面强化,聚焦三个维度:跨屏视觉一致性、Web 安全防护基线和代码质量深度审查。

一、全站容器间距统一

问题:导航栏、页脚、首页各 Section、Services 页面 5 个区块、博客列表/详情页的内边距各不相同(px-4 md:px-6px-6 sm:px-8 lg:px-12px-4等),在 14 寸笔记本与 24 寸外接显示器之间切换时,视觉节奏不连贯。

统一方案:全站采用 container mx-auto max-w-screen-2xl px-4 sm:px-6 lg:px-8 2xl:px-12 模式,涵盖:

组件/页面修改数
site-header / site-footer2
hero / service-overview / platform / value / characteristics / philosophy6
services 页面 5 大区块5
blog 列表页 / blog 详情页2

额外修复:

  • ServiceOverviewSection 移除 max-w-350 过紧约束
  • blog 卡片边框从 border-slate-100 增强为 border-slate-200(暗色 dark:border-slate-700),提升可辨识度
  • 导航栏底部边框改用 border-border/40,减少视觉压迫感
  • 代码块增加 prose-pre:overflow-x-auto 和全局 CSS 层处理 pre 溢出

二、安全加固(CSP + 安全头 + 输入验证)

依据 Next.js 官方 Content Security PolicyData Security 指南,实施三层安全防护。

CSP 策略(Without Nonces 方案)

选择"Without Nonces"方案的原因:本站采用 SSG(全页面 generateStaticParams),如使用 Nonce 方案会强制所有页面切换为动态渲染,导致 CDN 缓存失效、首屏性能下降、并与 Turbopack 不兼容(SRI 仅限 Webpack)。

// next.config.ts - CSP 主要指令
("default-src 'self'",
  "script-src 'self' 'unsafe-inline' 'unsafe-eval'", // dev 需要 unsafe-eval
  "style-src 'self' 'unsafe-inline'",
  "img-src 'self' blob: data: https://picsum.photos https://fastly.picsum.photos https://i.pravatar.cc",
  "object-src 'none'",
  "form-action 'self'",
  "frame-ancestors 'none'",
  "upgrade-insecure-requests"); // 仅生产环境

安全响应头

HeaderValue作用
X-Content-Type-Optionsnosniff防止 MIME 嗅探
X-Frame-OptionsDENY防止点击劫持
X-XSS-Protection1; mode=blockIE 旧版 XSS 过滤
Referrer-Policystrict-origin-when-cross-origin控制来源信息泄露
Permissions-Policycamera=(), microphone=(), geolocation=(), payment=()禁用不必要权限
Strict-Transport-Securitymax-age=63072000; includeSubDomains; preload强制 HTTPS

同时设置 poweredByHeader: false 隐藏 X-Powered-By: Next.js 指纹。

输入验证(zod v4)

所有 Server Actions 增加 zod schema 验证:

const slugSchema = z
  .string()
  .min(1)
  .max(200)
  .regex(/^[\w-]+$/);
const localeSchema = z.enum(["zh", "en"]);
const commentSchema = z.object({
  author: z
    .string()
    .min(1)
    .max(50)
    .transform((v) => v.replace(/<[^>]*>/g, "")),
  content: z
    .string()
    .min(1)
    .max(2000)
    .transform((v) => v.replace(/<[^>]*>/g, "")),
});

所有函数使用 safeParse 校验输入,失败时返回结构化错误而非抛出异常。

三、第三轮代码审查修复

通过子代理(subagent)对全部 64 个活跃文件进行逐行审查,共发现 23 项问题,修复关键项包括:

  • global-error.tsx:补充 <head> 元素(charset/viewport/title),移除无 locale 上下文的双语硬编码
  • icon.tsx:将 import * as LucideIcons 替换为显式白名单 Map(约 20 个常用图标),避免破坏 tree-shaking
  • callout.tsx:移除不必要的 "use client" 指令(纯展示组件无需客户端声明)
  • language-switcher.tsx:Cookie 增加 SameSite=Lax 属性,防止 CSRF
  • table-of-contents.tsx<nav> 添加 aria-label 提升无障碍可访问性
  • mdx-tabs.tsx:补充 role="tablist"role="tab"aria-selectedrole="tabpanel" 等 WAI-ARIA 属性
  • docs page:将硬编码 ["zh", "en"] 替换为 i18n.locales 配置引用
  • interaction-group.tsx:localStorage 值增加显式字符串比较验证

四、构建与验证

最终构建产出 25 个静态页面(较上一轮增加 2 页),全站 pnpm build 通过。

浏览器自动化测试覆盖 7 条核心路由:

  • /(中文首页)、/services/blog/blog/[slug](详情页)
  • /docs/en(英文首页)、/en/blog

所有页面 0 控制台错误,CSP 及安全头全部生效。

总结

本次迁移的核心理念是 "在约束中寻找最优解"

  • 框架约束:Next.js 16 + Turbopack 决定了内容处理必须兼容其序列化要求
  • 内容约束:Nextra v4 提供了强大的基础设施,但需要理解其约定来实现自定义
  • 规模约束:ToB 营销网站的交互量决定了 node:sqlite 足以应对,无需过度工程化
  • 团队约束:自建 i18n 字典方案虽功能有限,但与 Nextra + RSC 零冲突且易于维护
  • 安全约束:SSG 架构决定了 CSP 采用 Without Nonces 方案,配合 zod 输入验证实现数据安全

每一项技术选型都不存在绝对的"最优"。关键在于清晰地识别项目的核心需求和边界条件,在备选方案中找到最匹配的组合。BDI 官网的技术栈在当前阶段实现了功能完整、性能优秀、安全可靠、维护成本低的平衡。随着业务发展,部分选型(如数据库、国际化方案)可以平滑升级,而不影响整体架构。


第四轮迭代:Frontmatter 终版标准化与极限测试修复

在前三轮迭代的基础上,进行了第四轮质量强化,聚焦 Frontmatter 字段终版标准化和极限边界测试博客的渲染修复。

一、Frontmatter 终版字段重命名

将所有 10 篇 MDX 博客文件的字段统一为最终规范:

上一轮字段最终字段说明
dateTime: "..."date: "YYYY-MM-DD HH:mm:ss +0800"字段名更通用,保留时区格式
tags: [标签]category: "标签"单分类字符串,非数组

同步更新了 app/[locale]/blog/page.tsxapp/[locale]/blog/[slug]/page.tsx 中的所有字段引用。

二、极限测试博客修复

do-not-delete-or-modify.mdx 极限测试文章中的 6 个渲染问题进行了修复:

问题原因修复方案
脚注跳转偏移sticky header 遮挡脚注目标globals.css 添加 scroll-margin-top: 6rem 规则
零宽字符未解析MDX 不处理 JS 转义序列 \u200B替换为 HTML 实体 &#x200B;
引用块单行相邻 > 行为同一段落插入空白 > 行分隔两段落
Icon 不显示CheckCircleX 未在白名单icon.tsx 添加 CircleCheckX 映射
RTL 文本渲染缺少 dir 属性添加 <bdi dir="rtl"> 包裹希伯来文
XSS 弹框<script> 在 SSR HTML 中执行以行内代码格式转义危险标签

三、MDX 安全强化

mdx-components.tsx 中添加了 iframescript 的安全覆盖,作为运行时的纵深防御层。同时创建了 lib/rehype-sanitize-mdx.ts rehype 插件,用于非 Turbopack 构建时的 AST 级别安全清洗(Turbopack 不支持函数类 rehype 插件)。

四、构建与验证

  • pnpm check:65 个文件通过 Biome 检查
  • pnpm build:TypeScript 编译通过,25/25 静态页面成功生成
  • 浏览器测试:中英文博客列表及详情页 0 控制台错误,XSS 弹框已消除

  • 版本: 4.0.0
  • 时间: 2026-02-09 20:12:27
  • 作者: Claude Opus 4.6
  • 简介: 新增第四轮迭代内容:Frontmatter 终版标准化(dateTime→date、tags→category)、极限测试博客 6 项渲染修复、MDX 安全强化。