Table of Contents#
-
Understanding GitHub Pages Caching
- How GitHub Pages Delivers Content
- Common Cache Headers in Play
- Why Aggressive Caching Causes Stale Content
-
- Using Browser Developer Tools
- Command-Line Tools (curl)
- Online Cache Checkers
- Testing Across Devices/Browsers
-
Forcing Updates: Strategies to Bypass Aggressive Caching
- Versioned Filenames: The Gold Standard
- Query Strings (With Caveats)
- Meta Tags for HTML Files
- Cache-Busting with Build Tools (Jekyll, Webpack, etc.)
- Manually Triggering CDN Cache Refreshes
- Client-Side Hard Refreshes
1. Understanding GitHub Pages Caching#
Before diving into fixes, let’s unpack how caching works on GitHub Pages.
How GitHub Pages Delivers Content#
GitHub Pages hosts your static files (HTML, CSS, JS, images) and serves them via a global CDN (Content Delivery Network) powered by Cloudflare. CDNs cache content at edge locations worldwide to reduce latency—when a user visits your site, the CDN serves the cached version from the nearest edge server instead of fetching it from GitHub’s origin servers every time.
Common Cache Headers in Play#
Caching behavior is controlled by HTTP headers sent by the server (GitHub/Cloudflare) to the client (browser). Key headers include:
Cache-Control: Directs how the browser/CDN caches content. Example values:max-age=31536000: Cache the file for 1 year (31536000 seconds).public: Allow CDNs and proxies to cache the file.must-revalidate: The cache must check the origin server for updates aftermax-ageexpires.
Expires: A specific date/time after which the cached content is invalid (e.g.,Expires: Wed, 21 Oct 2024 07:28:00 GMT).ETag: A unique identifier for the file version (e.g.,ETag: "abc123"). Browsers send this back to check if the file has changed.Last-Modified: The timestamp when the file was last updated on the origin server.
Why Aggressive Caching Causes Stale Content#
By default, GitHub Pages and Cloudflare set long max-age values for static assets (CSS, JS, images) to improve performance. For example:
- CSS/JS files:
max-age=31536000(1 year). - Images:
max-age=604800(1 week).
This means after the first load, browsers/CDNs will serve the cached version for months—even if you’ve updated the file on GitHub. Unless the cache is explicitly invalidated, users won’t see your changes.
2. How to Detect Stale Content#
Before trying to fix stale content, confirm that caching is the issue. Here are tools to diagnose:
Using Browser Developer Tools#
Modern browsers (Chrome, Firefox, Edge) have built-in tools to inspect cache behavior:
- Open DevTools: Right-click your page → "Inspect" → Go to the Network tab.
- Enable "Disable Cache" (temporarily): Check the box to bypass the browser cache and see the latest content from the server. If the page updates here, caching is the culprit.
- Check Response Headers:
- Reload the page (without "Disable Cache" checked).
- Click on a file (e.g.,
style.css) in the Network tab. - Go to the Headers tab → Look for
Cache-Control,ETag, andLast-Modified. - If
max-ageis large (e.g., 1 year) andETag/Last-Modifieddon’t match your latest commit, the file is stale.
Command-Line Tools (curl)#
For a quick check without a browser, use curl to fetch HTTP headers:
# Replace YOUR-USERNAME and YOUR-REPO with your GitHub Pages details
curl -I https://YOUR-USERNAME.github.io/YOUR-REPO/style.cssLook for Cache-Control, ETag, and Last-Modified in the output. Example:
HTTP/2 200
cache-control: public, max-age=31536000
etag: "abc123"
last-modified: Wed, 01 Jan 2024 12:00:00 GMT
If last-modified is older than your latest commit, the CDN/browser is serving stale content.
Online Cache Checkers#
Tools like Redbot (from the IETF) or GTmetrix analyze cache headers and flag issues. For example, Redbot will tell you if a file’s Cache-Control settings are causing stale content.
Testing Across Devices/Browsers#
Caches are device/browser-specific. Test on:
- A different browser (Chrome vs. Safari).
- A mobile device (browsers often have separate caches).
- Incognito/private mode (which bypasses most cached content).
If the page updates in incognito mode but not in regular mode, the issue is client-side caching.
3. Forcing Updates: Strategies to Bypass Aggressive Caching#
Once you’ve confirmed stale content, use these techniques to force updates.
1. Versioned Filenames (The Gold Standard)#
The most reliable way to bypass caching is to change the filename of updated files. Browsers/CDNs treat filenames as unique identifiers—if the name changes, they fetch the new file.
Example:
- Old:
style.css→ New:style.v2.css - Update your HTML to reference the new file:
<link rel="stylesheet" href="style.v2.css">
Why it works: CDNs and browsers have no cached version of style.v2.css, so they fetch it from the origin server immediately.
2. Query Strings (With Caveats)#
A quicker (but less reliable) fix is appending a version query string to filenames:
Example:
- Old:
script.js→ New:script.js?v=2
Caveats:
- Some proxies/CDNs ignore query strings and cache the base filename (
script.js), so this may not work universally. - Not recommended for critical updates (use versioned filenames instead).
3. Meta Tags for HTML Files#
For HTML pages (e.g., index.html), add meta tags to hint to browsers not to cache the page. Place this in the <head>:
<meta http-equiv="Cache-Control" content="no-cache, no-store, must-revalidate">
<meta http-equiv="Expires" content="0">
<meta http-equiv="Pragma" content="no-cache">Limitations:
- Only affects the HTML file itself (not assets like CSS/JS).
- Some browsers ignore meta tags in favor of server-sent
Cache-Controlheaders.
4. Cache-Busting with Build Tools#
If you use a static site generator (Jekyll, Hugo) or build tool (Webpack, Vite), automate cache busting:
Jekyll Example:#
Jekyll (GitHub Pages’ default generator) can use Liquid tags to append a hash of the file content to the filename. Add this to your _config.yml:
permalink: prettyThen use asset_path with a hash:
<link rel="stylesheet" href="{{ "/assets/css/style.css" | asset_path | append: "?v=" | append: site.time | date: "%Y%m%d%H%M%S" }}">This appends a timestamp (e.g., ?v=202405201430) to the filename, ensuring uniqueness on each build.
Webpack Example:#
Webpack automatically adds content hashes to filenames in production builds. Configure webpack.config.js:
module.exports = {
output: {
filename: '[name].[contenthash].js', // e.g., main.a1b2c3.js
path: path.resolve(__dirname, 'dist')
}
};5. Manually Triggering CDN Cache Refreshes#
GitHub Pages uses Cloudflare, but you can’t directly clear Cloudflare’s cache. However, Cloudflare invalidates cache when:
- The file’s
ETagorLast-Modifiedtimestamp changes (triggered by a new commit). - The
Cache-Controlheader is updated (via a_headersfile).
Force a CDN refresh:
- Make a minor change to the stale file (e.g., add a comment).
- Commit and push to GitHub Pages. The new
ETag/Last-Modifiedwill trigger Cloudflare to cache the updated file.
6. Client-Side Hard Refreshes#
For users stuck with stale content, instruct them to perform a hard refresh to bypass browser cache:
- Windows/Linux:
Ctrl + Shift + R - Mac:
Cmd + Shift + R(Chrome/Safari) orCmd + Opt + R(Firefox).
4. Best Practices to Avoid Stale Content#
- Use Versioned Filenames Consistently: Make this your default for CSS/JS/images. Tools like Webpack or Jekyll automate this.
- Avoid Overly Long Cache TTLs: For frequently updated files (e.g., blog posts), set shorter
max-age(e.g.,max-age=86400for 1 day) via a_headersfile:# In your GitHub Pages repo, create a file named _headers /blog/*.html Cache-Control: public, max-age=86400 - Test Cache Behavior: Use DevTools and
curlto verifyCache-Controlheaders after deploying updates. - Document Your Cache Strategy: Note which files have long TTLs (e.g., logos) and which need frequent updates (e.g.,
index.html). - Leverage Build Tools: Automate cache busting with Jekyll, Webpack, or Hugo to avoid manual filename changes.
5. Conclusion#
Stale content on GitHub Pages is a common headache, but it’s solvable with the right tools and strategies. By understanding how GitHub Pages/Cloudflare cache content, detecting stale files with browser/command-line tools, and using cache-busting techniques like versioned filenames, you can ensure users always see your latest updates.
Remember: Caching is a performance feature, not a bug—use it wisely. Combine long TTLs for static assets with automated cache busting for updates, and you’ll balance speed and freshness perfectly.