From 404 to 200: A Deep Dive into Solving Missing CSS on Astro with GitHub Pages
The paths for these CSS files looked like this:
https://geyuxu.com/_astro/_slug_.BvCO7WHQ.css
The issue was clear: the server couldn't find the CSS files generated by Astro. But why?
2. The Investigation: A Trail of False Leads
My troubleshooting process followed a typical path, starting with the most common culprits. This led me down a few rabbit holes.
Hypothesis 1: Is Jekyll the Culprit?
By default, GitHub Pages uses Jekyll to build sites. A well-known convention of Jekyll is that it ignores all files and directories that begin with an underscore (_), as they are considered special (e.g., _posts, _includes).
The CSS directory in Astro's build output is named _astro. This seemed like the smoking gun.
Attempted Solution: I added an empty .nojekyll file to the root of my repository. The purpose of this file is to tell GitHub Pages, "Don't use Jekyll to process my site; treat it as pure static content."
Result: After clearing the cache and redeploying, the problem persisted. The CSS files under the _astro directory still returned a 404.
This result was baffling. The .nojekyll file should have disabled Jekyll entirely. Why were resources starting with an underscore still inaccessible?
Hypothesis 2: A Directory Access Issue?
I started to wonder if the _astro directory itself was inaccessible due to some server configuration. Perhaps it was missing an index.html file?
Attempted Solution: I manually created an empty index.html inside the dist/_astro/ directory and redeployed.
Result: Unsurprisingly, this failed. It was a long shot, as we were requesting specific files, not trying to list a directory's contents.
3. The Breakthrough: Pinpointing the Real Cause
After ruling out the common Jekyll issue, I took a step back and re-examined the mysterious 404. The .nojekyll file does indeed disable the Jekyll build process, but it doesn't override all of the underlying rules of the GitHub Pages server.
After some more digging and research, I finally found the root cause:
GitHub Pages (or its underlying web server) has a deeper, more fundamental rule: it blocks direct web access to any file that starts with an underscore (
_). This appears to be a security or convention-based policy that operates independently of Jekyll.
The filenames Astro was generating, like _slug_.BvCO7WHQ.css, also started with an underscore! This was the real reason the .nojekyll file wasn't enough. We weren't just dealing with the _astro directory, but the filenames themselves.
4. The Ultimate Solution: Customizing Astro's Build Output
If you can't change the rules of the platform, change your output to conform to them. The goal was now to configure Astro to stop generating filenames that begin with an underscore.
Fortunately, Astro uses Vite as its build tool under the hood and exposes Vite's configuration API to us. We can customize the build behavior by modifying the astro.config.mjs file.
Configuration Solution
The specific option we need is vite.build.rollupOptions.output.assetFileNames. This gives us full control over the output path and naming scheme for asset files like CSS, images, etc.
Open astro.config.mjs in your project root and add the vite configuration object:
// astro.config.mjs
import { defineConfig } from 'astro/config';
export default defineConfig({
site: 'https://geyuxu.com',
// ... other configs
vite: {
build: {
rollupOptions: {
output: {
// Modify asset file naming rules to avoid underscore prefixes
assetFileNames: (assetInfo) => {
const info = assetInfo.name.split('.');
const ext = info[info.length - 1];
const name = info.slice(0, -1).join('.');
// If filename starts with underscore, replace with 'assets-'
const finalName = name.startsWith('_') ? name.replace(/^_/, 'assets-') : name;
return `_astro/${finalName}.[hash].${ext}`;
}
}
}
}
},
// ... other configs like markdown, etc.
});
Code Explanation
assetFileNamescan be a function that is called for each assetassetInfo.namecontains the original filename suggested by Vite, such as_slug_.BvCO7WHQ.css- We check if
assetInfo.namestarts with_ - If it does, we use
replace(/^_/, 'assets-')to replace the leading underscore withassets- - The final generated filename becomes
assets-slug_.BvCO7WHQ.css
Alternative Simpler Implementation
If your only goal is to solve the underscore problem without major restructuring, a simpler function will do:
// astro.config.mjs
import { defineConfig } from 'astro/config';
export default defineConfig({
// ...
vite: {
build: {
rollupOptions: {
output: {
// A simpler pattern for all assets
assetFileNames: 'assets/[name].[hash][extname]',
// JS entry files
entryFileNames: "assets/entry.[hash].js",
// JS code-splitted chunks
chunkFileNames: "assets/chunk.[hash].js",
}
}
}
}
});
This version is more direct. It places all assets into an assets directory and uses the [name] and [hash] placeholders, which naturally avoids the leading underscore problem.
Verification
After applying the configuration, I ran npm run build again and deployed the new dist directory to GitHub Pages:
npm run build
npm run deploy
Result: Success! The CSS files were now being loaded from paths like assets-slug_.BvCO7WHQ.css, and the HTTP status code proudly showed 200 OK. The website's styling was perfectly restored.
We can verify this with:
curl -I https://geyuxu.com/_astro/assets-slug_.BvCO7WHQ.css
Response:
HTTP/2 200
content-type: text/css; charset=utf-8
5. Conclusion & Lessons Learned
This debugging journey, while frustrating at times, was incredibly insightful.
Key Insights
Understand Platform Constraints Deeply: Don't assume a common fix like
.nojekyllis a silver bullet. It's crucial to understand the hosting platform's (GitHub Pages) own underlying rules, not just the behavior of its surface-level tools (Jekyll).Solve Problems at the Source: Instead of trying to patch things to fit the deployment environment, it's often better to control the build process to generate compliant output from the start. This is a cleaner and more maintainable solution.
Leverage Your Tool's Configuration: Modern frontend frameworks (like Astro) and build tools (like Vite) offer powerful configuration options. Reading the documentation and understanding these options is key to solving complex, environment-specific problems.
The Underscore is Special: In web development, files and directories starting with an underscore often have special meaning (e.g., Jekyll, private Node.js modules). Be mindful of this convention when naming files and deploying to avoid conflicts with platform rules.
Debugging Techniques
- Systematic Investigation: Start with the most common causes and gradually dig deeper
- Verify Assumptions: Actually test each solution rather than assuming it will work
- Pay Attention to Details: Filenames, paths, HTTP status codes often contain crucial information
- Consult Documentation: When common solutions fail, dive deep into tool configuration options
Applicable Scenarios
This solution applies to all similar Astro + GitHub Pages deployment scenarios, and can be extended to other frameworks using Vite (like Vue, React, etc.) when deploying to GitHub Pages.
I hope my experience saves you some debugging time. Happy coding!