Abstract

After deploying my new Astro blog to GitHub Pages, I hit a wall: the site content loaded, but all CSS styling was gone, leaving my browser console littered with 404 errors. This article documents my entire debugging journey—from initial false assumptions to pinpointing and resolving the root cause—to help developers who might face a similar issue.

1. The Problem: The Case of the Vanishing CSS

Everything was ready to go. I had just built my personal blog using the modern Astro framework. Local development was flawless, and npm run build completed without a hitch. But when I excitedly pushed the contents of the dist directory to my GitHub Pages repository, reality struck.

The homepage was accessible, but it was a plain, unstyled HTML document.

Opening the browser’s developer tools, the Console and Network tabs clearly revealed the problem: all requests for CSS files were returning 404 Not Found.

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

  • assetFileNames can be a function that is called for each asset
  • assetInfo.name contains the original filename suggested by Vite, such as _slug_.BvCO7WHQ.css
  • We check if assetInfo.name starts with _
  • If it does, we use replace(/^_/, 'assets-') to replace the leading underscore with assets-
  • 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

  1. Understand Platform Constraints Deeply: Don’t assume a common fix like .nojekyll is 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).

  2. 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.

  3. 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.

  4. 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!

Ge Yuxu • AI & Engineering

脱敏说明:本文所有出现的表名、字段名、接口地址、变量名、IP地址及示例数据等均非真实,仅用于阐述技术思路与实现步骤,示例代码亦非公司真实代码。示例方案亦非公司真实完整方案,仅为本人记忆总结,用于技术学习探讨。
    • 文中所示任何标识符并不对应实际生产环境中的名称或编号。
    • 示例 SQL、脚本、代码及数据等均为演示用途,不含真实业务数据,也不具备直接运行或复现的完整上下文。
    • 读者若需在实际项目中参考本文方案,请结合自身业务场景及数据安全规范,使用符合内部命名和权限控制的配置。

Data Desensitization Notice: All table names, field names, API endpoints, variable names, IP addresses, and sample data appearing in this article are fictitious and intended solely to illustrate technical concepts and implementation steps. The sample code is not actual company code. The proposed solutions are not complete or actual company solutions but are summarized from the author's memory for technical learning and discussion.
    • Any identifiers shown in the text do not correspond to names or numbers in any actual production environment.
    • Sample SQL, scripts, code, and data are for demonstration purposes only, do not contain real business data, and lack the full context required for direct execution or reproduction.
    • Readers who wish to reference the solutions in this article for actual projects should adapt them to their own business scenarios and data security standards, using configurations that comply with internal naming and access control policies.

版权声明:本文版权归原作者所有,未经作者事先书面许可,任何单位或个人不得以任何方式复制、转载、摘编或用于商业用途。
    • 若需非商业性引用或转载本文内容,请务必注明出处并保持内容完整。
    • 对因商业使用、篡改或不当引用本文内容所产生的法律纠纷,作者保留追究法律责任的权利。

Copyright Notice: The copyright of this article belongs to the original author. Without prior written permission from the author, no entity or individual may copy, reproduce, excerpt, or use it for commercial purposes in any way.
    • For non-commercial citation or reproduction of this content, attribution must be given, and the integrity of the content must be maintained.
    • The author reserves the right to pursue legal action against any legal disputes arising from the commercial use, alteration, or improper citation of this article's content.

Copyright © 1989–Present Ge Yuxu. All Rights Reserved.