Workbox setup in Vite PWA (GitHub Pages hosted) causing double forward slashes (//) in cache entries?
I’m experiencing an issue where my Workbox cache is being populated with double forward slashes in the cache keys. This occurs when my Vite PWA is hosted on GitHub Pages.
I’ve already tried:
- Adjusting the base configuration in Vite (both with and without trailing slashes)
- Clearing site data and unregistering service workers during testing
The generated service worker doesn’t include the URL suffix from GitHub Actions, which suggests this might be an internal Workbox behavior.
My GitHub Actions workflow includes:
- name: Production Build
run: bun run build:prod -- --base=/${{ github.event.repository.name }}/
I’ve created a reproducible example with artifacts from GitHub deployments, though the issue seems to manifest specifically when hosted on GitHub Pages or when not served from the root folder of a server.
What configuration options should I examine or alternative methods should I consider to ensure proper cache keys without double slashes?
Workbox in Vite PWA can generate double forward slashes in cache keys when hosted on GitHub Pages due to how the base URL is handled during service worker registration and cache key generation. This typically occurs because the service worker script is loaded with a different base path than the actual cache entries it’s trying to precache or intercept, causing Workbox to normalize URLs incorrectly.
Contents
- Understanding the Double Slash Issue
- Root Causes of the Problem
- Configuration Solutions
- Alternative Approaches
- Testing and Verification
- Best Practices for GitHub Pages Deployment
Understanding the Double Slash Issue
The double forward slash (//) issue in Workbox cache keys manifests when URLs are incorrectly normalized during the caching process. This typically appears as cache keys containing something like //assets// instead of /assets/ or assets/.
When your Vite PWA is hosted on GitHub Pages with a non-root path configuration (using base=/${{ github.event.repository.name }}/), several factors can contribute to this problem:
- Service Worker Registration Path: The service worker file gets registered with a specific path, but the precache manifest may contain URLs with different base paths
- Runtime Caching: During runtime, requests may be processed with incorrect URL normalization
- Cache Key Strategy: Workbox’s default cache key strategies may not handle the base URL transformation correctly
This issue specifically affects GitHub Pages deployments because GitHub serves content from subdirectories, and the URL structure differs from local development or root-hosted deployments.
Root Causes of the Problem
1. Vite Base Configuration Mismatch
The base configuration in Vite directly impacts how assets are referenced and how the service worker generates cache keys. When you set base=/${{ github.event.repository.name }}/, this affects:
- Asset references in HTML files
- Service worker script loading
- Precache manifest generation
However, the service worker itself may not be aware of this base path transformation when it processes requests.
2. Service Worker File Location
Service workers run in a different context than the main page. They’re registered from a specific path but intercept requests from the entire origin. This disconnect can cause URL normalization issues.
3. Workbox URL Normalization
Workbox uses its own URL normalization logic that may not account for custom base paths. The urlManipulation option in Workbox can sometimes create unintended double slashes when processing URLs.
4. GitHub Pages Specific Behavior
GitHub Pages serves content from subdirectories and may handle trailing slashes differently than other hosting platforms. This can cause the service worker to perceive URLs differently than the browser.
Configuration Solutions
1. Modify Vite PWA Configuration
Update your vite.config.js to include proper URL handling:
import { defineConfig } from 'vite'
import vue from '@vitejs/plugin-vue'
import { VitePWA } from 'vite-plugin-pwa'
export default defineConfig({
plugins: [
vue(),
VitePWA({
registerType: 'autoUpdate',
includeAssets: ['favicon.ico', 'apple-touch-icon.png', 'masked-icon.svg'],
manifest: {
name: 'Your App Name',
short_name: 'App',
description: 'Description',
theme_color: '#ffffff',
},
workbox: {
// Customize workbox configuration
runtimeCaching: [
{
urlPattern: /^https:\/\/.*\.(?:js|css|png|jpg|jpeg|svg|gif|woff|woff2|ttf)$/,
handler: 'CacheFirst',
options: {
cacheName: 'static-cache',
expiration: {
maxEntries: 100,
maxAgeSeconds: 60 * 60 * 24 * 30, // 30 days
},
cacheableResponse: {
statuses: [0, 200],
},
// Add URL manipulation to prevent double slashes
urlManipulation: ({ url }) => {
const pathname = url.pathname.replace(/\/+/g, '/')
url.pathname = pathname.startsWith('/') ? pathname : `/${pathname}`
return [url]
},
},
},
],
},
}),
],
base: process.env.NODE_ENV === 'production'
? `/${process.env.VITE_APP_NAME}/`
: '/',
})
2. Environment-Specific Build Configuration
Create separate build configurations for different environments:
// vite.config.js
import { defineConfig, loadEnv } from 'vite'
export default defineConfig(({ mode }) => {
const env = loadEnv(mode, process.cwd())
return {
base: env.VITE_BASE_URL || '/',
plugins: [
// ... your plugins
],
}
})
Then set environment variables in your .env files:
.env.production:VITE_BASE_URL=/${process.env.GITHUB_REPOSITORY.replace('github.com/', '').split('/')[1]}/.env.development:VITE_BASE_URL=/
3. Custom Service Worker File
Create a custom service worker that handles URL normalization:
// public/sw.js
import { precacheAndRoute } from 'workbox-precaching'
import { registerRoute } from 'workbox-routing'
import { CacheFirst } from 'workbox-strategies'
// Get the precache manifest from the injected variable
const precacheManifest = self.__WB_MANIFEST
// Normalize URLs to prevent double slashes
const normalizeUrl = (url) => {
try {
const parsedUrl = new URL(url, self.location.href)
pathname = parsedUrl.pathname.replace(/\/+/g, '/')
parsedUrl.pathname = pathname.startsWith('/') ? pathname : `/${pathname}`
return parsedUrl.toString()
} catch (e) {
return url
}
}
// Precache with normalized URLs
const normalizedManifest = precacheManifest.map(entry => ({
...entry,
url: normalizeUrl(entry.url)
}))
precacheAndRoute(normalizedManifest)
// Register runtime caching with URL manipulation
registerRoute(
({ url }) => url.pathname.startsWith('/api/'),
new CacheFirst({
cacheName: 'api-cache',
plugins: [{
cacheKeyWillBeUsed: async ({ request }) => {
const normalizedUrl = normalizeUrl(request.url)
return new Request(normalizedUrl, request)
}
}]
})
)
Alternative Approaches
1. GitHub Actions Path Adjustment
Modify your GitHub Actions workflow to handle the base path more carefully:
- name: Production Build
run: |
REPO_NAME="${{ github.event.repository.name }}"
bun run build:prod -- --base="/${REPO_NAME}/"
- name: Upload to GitHub Pages
uses: peaceiris/actions-gh-pages@v3
with:
github_token: ${{ secrets.GITHUB_TOKEN }}
publish_dir: ./dist
cname: your-domain.com
keep_files: true
2. Custom Cache Key Strategy
Implement a custom cache key strategy that handles the base path:
// In your Workbox configuration
customCacheKey: ({ request, url, params }) => {
// Remove double slashes and normalize the path
const normalizedPath = url.pathname.replace(/\/+/g, '/')
const cleanPath = normalizedPath.startsWith('/') ? normalizedPath : `/${normalizedPath}`
// Reconstruct the URL with the normalized path
const normalizedUrl = new URL(cleanPath, url.origin)
// Add a prefix if needed for GitHub Pages
const githubPagesPrefix = `/${process.env.VITE_APP_NAME || ''}/`
if (normalizedUrl.pathname.startsWith(githubPagesPrefix)) {
normalizedUrl.pathname = githubPagesPrefix + normalizedUrl.pathname.substring(githubPagesPrefix.length)
}
return normalizedUrl.toString()
}
3. Service Worker Scope Adjustment
Adjust the service worker scope to match your deployment pattern:
// Register service worker with proper scope
if ('serviceWorker' in navigator) {
window.addEventListener('load', () => {
const repoName = window.location.pathname.split('/')[1]
const swUrl = `/sw.js` // or `/your-repo-name/sw.js`
navigator.serviceWorker.register(swUrl, {
scope: repoName ? `/${repoName}/` : '/'
}).then(registration => {
console.log('ServiceWorker registration successful with scope:', registration.scope)
}).catch(error => {
console.log('ServiceWorker registration failed:', error)
})
})
}
Testing and Verification
1. Cache Inspection Tools
Use browser DevTools to inspect cache contents:
- Open Chrome DevTools (F12)
- Go to Application > Cache Storage
- Select your cache and examine the keys
- Look for entries with double slashes
2. Service Worker Debugging
Add debugging to your service worker:
// Add to your service worker
console.log('Service Worker activated')
console.log('Current scope:', self.scope)
console.log('Current location:', self.location.href)
// Log cache operations
self.addEventListener('install', (event) => {
console.log('Installing service worker with manifest:', self.__WB_MANIFEST)
})
self.addEventListener('activate', (event) => {
console.log('Service worker activated with scope:', self.scope)
})
3. URL Testing Script
Create a test page to verify URL handling:
// test-url-handling.js
const testUrls = [
'/assets/script.js',
'//assets/script.js',
'/assets//script.js',
'https://your-domain.com/your-repo-name/assets/script.js'
]
testUrls.forEach(url => {
console.log(`Original: ${url}`)
try {
const normalized = new URL(url, window.location.href)
console.log(`Normalized: ${normalized.pathname}`)
} catch (e) {
console.log(`Error: ${e.message}`)
}
})
Best Practices for GitHub Pages Deployment
1. Consistent Path Configuration
Ensure consistent path handling across all configuration files:
// Example of centralized path configuration
const GITHUB_PAGES_CONFIG = {
enabled: process.env.NODE_ENV === 'production',
repoName: process.env.GITHUB_REPOSITORY?.split('/').pop() || 'my-app',
baseUrl: process.env.NODE_ENV === 'production'
? `/${process.env.GITHUB_REPOSITORY?.split('/').pop()}/`
: '/'
}
export default defineConfig({
base: GITHUB_PAGES_CONFIG.baseUrl,
// ... rest of config
})
2. Environment Variable Management
Set up proper environment variables:
# .env.production
VITE_APP_NAME=my-app
VITE_BASE_URL=/my-app/
VITE_API_URL=https://api.example.com
# .env.development
VITE_APP_NAME=my-app
VITE_BASE_URL=/
VITE_API_URL=http://localhost:3000
3. Testing Pipeline
Add testing to your GitHub Actions workflow:
- name: Test Build
run: |
cd dist
# Test that all assets are referenced correctly
grep -r "assets" index.html | head -5
# Check for double slashes in HTML
if grep -r "//" index.html | grep -v "http://" | grep -v "https://"; then
echo "Found potential double slash issues"
exit 1
fi
4. Monitoring and Logging
Implement monitoring to catch cache key issues early:
// Add to your service worker
const CACHE_KEY_LOGGER = {
logCacheOperation: (operation, url, cacheName) => {
console.log(`[${new Date().toISOString()}] ${operation}: ${url} in ${cacheName}`)
// Send to monitoring service if needed
}
}
// Wrap cache operations with logging
self.addEventListener('fetch', (event) => {
const url = event.request.url
const normalizedUrl = url.replace(/\/+/g, '/')
if (url !== normalizedUrl) {
CACHE_KEY_LOGGER.logCacheOperation('URL_NORMALIZATION', url, normalizedUrl)
}
})
Sources
- Workbox Documentation - Cache Keys and URL Normalization
- Vite Plugin PWA Configuration Guide
- GitHub Pages Deployment Best Practices
- Service Worker Scope and Context
- Workbox Runtime Caching Configuration
Conclusion
The double forward slash issue in Workbox cache keys for Vite PWA on GitHub Pages typically stems from URL normalization problems when the application is deployed to a subdirectory. Key takeaways include:
- Fix Vite base configuration to match your GitHub Pages deployment path and ensure consistency across all references
- Implement custom URL manipulation in Workbox configurations to handle path normalization properly
- Use environment-specific builds to handle development and production URL structures differently
- Test cache contents regularly using browser DevTools and custom debugging scripts
- Monitor service worker operations to catch URL issues early in the deployment process
For immediate resolution, start by implementing the custom URL manipulation in your Workbox configuration and verify that all cache keys are properly normalized before deploying to production. If the issue persists, consider creating a custom service worker that handles the specific URL patterns of your GitHub Pages deployment.
The key is ensuring that the service worker’s understanding of URL paths matches exactly how GitHub Pages serves your application, preventing any normalization conflicts that result in double slashes in cache keys.