从 404 到 200:记一次 Astro + GitHub Pages 样式丢失问题的深度排查
这些 CSS 文件的路径看起来像这样:
https://geyuxu.com/_astro/_slug_.BvCO7WHQ.css
服务器找不到 Astro 构建出来的 CSS 文件。问题在哪里?
排查:两条死路
假设 1:Jekyll 的锅?
GitHub Pages 默认使用 Jekyll 构建站点。Jekyll 有一条众所周知的规则:忽略所有以下划线开头的目录和文件,例如 _posts、_includes。Astro 的构建产物中,CSS 文件放在 _astro 目录下,看起来直指病因。
尝试:在仓库根目录添加空的 .nojekyll 文件,告诉 GitHub Pages 跳过 Jekyll、直接提供静态内容。
结果:_astro 目录下的 CSS 仍然是 404。
.nojekyll 应该已完全禁用 Jekyll,但问题依旧。显然另有原因。
假设 2:目录访问问题?
也许 _astro 目录因为缺少 index.html 而无法访问?
尝试:在 dist/_astro/ 下手动创建空的 index.html,重新部署。
结果:无效。我们请求的是具体文件,不是浏览目录列表。
真正的原因
禁用 Jekyll 是必要条件,但不是充分条件。进一步搜索之后,发现了一个更底层的约束:
GitHub Pages 会屏蔽所有文件名以下划线开头的文件的直接访问。这条规则似乎在服务器层面生效,与 Jekyll 无关。
Astro 生成的 CSS 文件名,例如 _slug_.BvCO7WHQ.css,同样以下划线开头。_astro 目录名是一个问题,文件名本身是另一个问题。.nojekyll 两者都没有解决。
修改构建产物命名
既然平台规则无法改变,就让构建输出主动规避:配置 Astro 不生成以下划线开头的文件名。
Astro 底层使用 Vite,并通过 astro.config.mjs 暴露了 Vite 的配置接口。关键配置项是 vite.build.rollupOptions.output.assetFileNames,它控制 CSS、图片等资源文件的输出路径和命名规则。
配置方案
打开项目根目录下的 astro.config.mjs,添加 vite 配置:
// astro.config.mjs
import { defineConfig } from 'astro/config';
export default defineConfig({
site: 'https://geyuxu.com',
// ... 其他配置
vite: {
build: {
rollupOptions: {
output: {
// 修改资产文件命名规则,避免下划线开头
assetFileNames: (assetInfo) => {
const info = assetInfo.name.split('.');
const ext = info[info.length - 1];
const name = info.slice(0, -1).join('.');
// 如果文件名以下划线开头,替换为 'assets-'
const finalName = name.startsWith('_') ? name.replace(/^_/, 'assets-') : name;
return `_astro/${finalName}.[hash].${ext}`;
}
}
}
}
},
// ... 其他配置如 markdown 等
});
assetFileNames接受一个函数,每个资源文件都会调用一次。assetInfo.name是 Vite 建议的原始文件名,例如_slug_.BvCO7WHQ.css。- 若文件名以
_开头,replace(/^_/, 'assets-')将其改写为assets-slug_.BvCO7WHQ.css。
验证
重新构建并部署:
npm run build
npm run deploy
CSS 现在从 assets-slug_.BvCO7WHQ.css 这样的路径加载,HTTP 状态码变为 200。通过以下命令确认:
curl -I https://geyuxu.com/_astro/assets-slug_.BvCO7WHQ.css
返回:
HTTP/2 200
content-type: text/css; charset=utf-8
经验总结
平台的限制比表面工具更深。 .nojekyll 禁用的是 Jekyll 的构建步骤,并不覆盖服务器的文件服务规则。理解每一层平台究竟控制什么,比套用常规解法更重要。
从源头解决问题。 配置构建工具生成符合平台要求的产物,比在部署环境中打补丁更干净。Vite 的 assetFileNames 选项提供了完整控制,不需要改动任何平台配置。
下划线有隐含含义。 在 Jekyll、Node.js 约定以及 GitHub Pages 的文件服务规则中,以下划线开头的文件名都具有特殊或私有的含义。使用有主张的平台部署时,构建工具中的命名选择会产生实际影响。