修复 Nuxt Content 静态站点刷新时的尾部斜杠 404 问题

修复 Nuxt Content 静态站点刷新时的尾部斜杠 404 问题

免责声明

本文内容仅代表作者个人经验,仅供参考。文中涉及的操作可能存在风险,请在尝试前务必备份重要数据。因参照本文操作导致的任何数据丢失、硬件损坏或其他损失,本人不承担任何法律责任。

问题现象

在使用 Nuxt Content 构建的静态博客中,遇到了一个奇怪的问题:

  • 从主页点击文章链接跳转,一切正常
  • 但在文章页面刷新时,会出现瞬间 404,然后才加载出内容
  • URL 会自动从 /2025/macai 变成 /2025/macai/(添加尾部斜杠)

这个问题只在生产环境(Netlify、Cloudflare Pages、GitHub Pages)出现,本地开发环境正常。

问题根源

1. Nuxt 的预渲染机制

Nuxt 在执行 nuxt generate 进行静态站点生成(SSG)时,默认会将路由转换为子文件夹结构:

text
/2025/macai  →  /2025/macai/index.html
/about       →  /about/index.html

这种结构会导致浏览器访问时自动在 URL 末尾添加 /,因为实际访问的是目录下的 index.html

2. Nuxt Content 的精确路径匹配

[...slug].vue 中,我们使用 Nuxt Content 查询文章:

ts
const { data: post } = await useAsyncData(
  route.path,
  () => queryCollection('content').path(route.path).first(),
)

问题在于:

  • 文章在 Content 数据库中的路径是 /2025/macai(无尾部斜杠)
  • 但刷新时 route.path/2025/macai/(有尾部斜杠)
  • 精确匹配失败 → 返回 null → 显示 404

3. 为什么主页跳转没问题?

从主页跳转走的是 Vue Router 客户端路由,不会触发真实的 HTTP 请求,所以不会有尾部斜杠问题。

刷新页面时走的是 服务器路由(即使是静态托管),会加载实际的 HTML 文件,触发尾部斜杠的添加。

解决方案

解决此问题的核心在于改变 Nuxt 的静态文件生成策略,使其不再生成子文件夹结构。

nuxt.config.ts 中配置 Nitro 的 autoSubfolderIndex 选项:

ts
// 需要引入环境变量判断工具,如 std-env 或直接使用 process.env
// import { isCI } from 'std-env' 

export default defineNuxtConfig({
  nitro: {
    prerender: {
      // 在 CI/CD 环境(生产构建)中禁用子文件夹索引
      autoSubfolderIndex: process.env.CI ? false : undefined,
    },
  },
})

配置效果

  • 生成 /2025/macai.html 而不是 /2025/macai/index.html
  • 浏览器访问 /2025/macai 时直接加载对应的 .html 文件
  • URL 不会自动添加尾部斜杠
  • 根路径 / 仍然生成 index.html(Nuxt 会自动处理根目录)

为什么建议只在 CI 环境启用?

  • 本地开发环境(npm run dev)使用开发服务器,不需要此配置,保持默认行为可以避免不必要的干扰。
  • 该问题主要存在于静态托管平台(Netlify/Cloudflare Pages/GitHub Pages),因此只需在构建部署时生效。

技术细节

autoSubfolderIndex 的工作原理

配置值生成结构访问 URL说明
true(默认)/path/index.html/path/传统子文件夹结构,会导致尾部斜杠
false/path.html/path扁平化结构,完美解决问题
undefined根据环境自动判断-开发环境默认行为

为什么不用 router.options.strict?

最初尝试使用:

ts
router: {
  options: {
    strict: true,
  },
},

但这会让 Vue Router 严格区分 /path/path/,反而加剧了问题,因为预渲染默认仍会生成子文件夹结构,导致带斜杠的 URL 无法匹配路由配置。

为什么不用重定向规则?

Nitro 的 routeRules 不支持动态函数重定向:

ts
// ❌ 不支持
routeRules: {
  '/**': {
    redirect: {
      to: (path) => path.endsWith('/') ? path.slice(0, -1) : path
    }
  },
}

只能写静态重定向规则,无法处理所有动态文章路径。

影响范围

这个配置会影响:

  • ✅ 所有文章路由(/2025/macai
  • ✅ 所有页面路由(/about, /archive
  • ✅ 动态路由([...slug].vue
  • ✅ 根路径 / 仍然是 index.html

不会影响

  • ❌ API 路由(/api/*
  • ❌ 特殊文件(/atom.xml, /favicon.ico
  • ❌ 已有的重定向规则

验证方法

本地测试

bash
# 生成静态站点
pnpm generate

# 预览生成的站点(注意:预览服务器的行为可能与真实环境略有不同)
pnpm preview

# 最准确的方法是检查生成的文件结构
ls -la dist/2025/
# 应该看到 macai.html 而不是 macai/index.html

生产环境测试

  1. 部署到 Netlify/Cloudflare Pages/GitHub Pages
  2. 访问文章页面(如 https://example.com/2025/macai
  3. 刷新页面,检查:
    • URL 是否保持 /2025/macai(无尾部斜杠)
    • 是否没有 404 闪现
    • 内容是否正常加载

总结

这个问题的本质是 Nuxt 预渲染的文件结构Nuxt Content 的路径匹配机制 不一致导致的。

最优雅的解决方案不是在前端代码中打补丁(手动去斜杠),而是通过配置 autoSubfolderIndex: false 从源头改变静态文件的生成结构。这不仅解决了 404 问题,也让站点的 URL 结构更加规范和统一。

新故事即将发生
Gemini Banana 绘画提示词分享

评论区

评论加载中...