Skip to content

问题现象

最近发现重构后的博客在 Vercel 部署后出现了一个奇怪的问题:当直接通过 URL(无 .html 后缀)访问(例如 .../posts/my-post),页面会先显示出一个空的布局,内容区域一片空白,然后在刷新后,地址栏带上了 .html 后缀,文章内容才被加载出来。

由于我的博客在 Google 的索引也是这样没有 .html 后缀的形式,因此也会出现相同的现象,导致极其糟糕的用户体验。

排查与定位

为了找到根源,我首先排查了 VitePress 主题中的客户端代码,检查是否存在 location.hrefrouter.push 之类的手动跳转逻辑,但一无所获。

既然客户端代码没有问题,那么问题很可能出在部署平台的配置上。我的博客托管在 Vercel,因此 vercel.json 文件成为了重点排查对象。果然,我在其中发现了问题的根源:

json
// vercel.json (旧的错误配置)
{
  "rewrites": [
    {
      "source": "/(.*)",
      "destination": "/index.html"
    }
  ]
}

原理分析:错误的 SPA 模式

上述 rewrites 规则的含义是:“无论用户请求什么路径,都返回根目录下的 index.html 文件”。

这是一个典型的 单页应用 (SPA) 配置。但 VitePress 本质上是一个 静态站点生成器 (SSG),它为每一篇文章都预先生成了独立的 HTML 文件(例如 /posts/my-post.html)。

这个错误的配置导致了如下低效的加载流程:

  1. 请求:浏览器请求 /posts/my-post
  2. 错误响应:Vercel 服务器根据规则,返回了通用的 index.html(一个只包含基本布局和 JS 脚本的“空壳”)。
  3. 客户端接管:浏览器加载完这个“空壳”后,执行 VitePress 的客户端 JS。
  4. 二次加载:JS 脚本启动后,发现浏览器地址栏的 URL 与当前页面内容不匹配,于是在客户端重新发起请求或通过路由切换来获取真正的文章内容。然而不知为何,并没有真正显示出来。刷新后才由于重定向至 .html 后缀的链接,正常显示了。

解决方案:正确的服务器端重写

解决方案是修改 vercel.json,让其在服务器端就能智能地处理这种无后缀的 URL 请求,直接返回正确的 HTML 文件。

我将 rewrites 规则修改为:

json
// vercel.json (新的正确配置)
{
  "rewrites": [
    {
      "source": "/:path((?!.*\\.).*)",
      "destination": "/:path.html"
    }
  ]
}

这个新规则的原理是:

  • source: "/:path((?!.*\\.).*)": 匹配所有不包含 . (点) 的 URL 路径。这样它就能匹配到 /posts/my-post 这样的文章路径,同时忽略 /assets/image.png 这样的静态资源文件。
  • destination": "/:path.html": 将匹配到的路径在服务器内部重写,为其添加 .html 后缀。

这样,新的高效加载流程变为:

  1. 请求:浏览器请求 /posts/my-post
  2. 智能重写:Vercel 服务器在内部将路径“翻译”为 /posts/my-post.html
  3. 正确响应:服务器直接返回包含完整文章内容的 posts/my-post.html 文件。
  4. 瞬时加载:浏览器一次性接收到完整页面,立即渲染,无任何延迟和闪烁。

总结

这个问题提醒我们,在使用现代前端框架时,必须确保部署环境的配置与框架自身的工作模式相匹配。将为 SSG 设计的 VitePress 错误地配置为 SPA 模式,虽然刷新后最终也能显示内容,但是会导致 Google 进入的读者看到的是几乎空白的页面,一头雾水。通过重写vercel的重定向规则,我们就能轻松修复这个问题,提供更流畅的用户体验。