Two kinds of Pages sites
A user or organization site comes from a repository named user.github.io and is served at https://user.github.io/. A project site comes from any other repository and is served at https://user.github.io/repo/. A project site with a custom domain, set with a CNAME file, is served at the domain root again. For a PWA the difference is everything: the manifest link, the service worker, the icons and the start URL all depend on the path the site lives under.
The base path
On a project site, an absolute link such as /sw.js resolves to https://user.github.io/sw.js, which belongs to a different site, and the request fails. Every URL in the head must include /repo/, or be relative. installkite handles both:
npx installkite init --yes # reads the repository from git, writes /repo/
npx installkite init --yes --base /repo/ # or say it yourself
npx installkite init --yes --relative # ./ and ../ per page, works under any pathDetection looks at a homepage in package.json, then the base of Vite or Astro or the basePath of Next.js, then your git remote together with signs that the repository publishes to Pages: a Pages workflow, a docs/ site or the gh-pages package. A CNAME file switches back to /. The manifest itself uses relative URLs, so "start_url": "./" and "scope": "./" are right in every case.
Service worker scope
A service worker controls the folder it is served from and everything below. At /repo/sw.js it controls /repo/, which is exactly the project site and nothing else, so neighbouring projects on the same user.github.io origin are not affected. Keep the worker at the root of the published folder. Note that all project sites of one user share an origin, and so share storage quotas and the caches API; installkite prefixes its cache names and only deletes its own old caches.
Publishing from a folder or from Actions
For a site committed as plain files, publish from main and the /docs folder or the root, and run installkite before you commit. For a site built by a tool, build and deploy in GitHub Actions and run installkite build between the two steps so the precache matches the built files:
name: Deploy to GitHub Pages
on:
push:
branches: [main]
permissions:
contents: read
pages: write
id-token: write
jobs:
deploy:
runs-on: ubuntu-latest
environment: github-pages
steps:
- uses: actions/checkout@v4
- uses: actions/setup-node@v4
with:
node-version: 22
- run: npm ci
- run: npm run build # vite build, astro build, next build
- run: npx installkite build # re-hash the precache for this build
- run: npx installkite doctor
- uses: actions/upload-pages-artifact@v3
with:
path: dist
- uses: actions/deploy-pages@v4Jekyll and underscore folders
Pages runs Jekyll on published folders unless told otherwise, and Jekyll skips files and folders whose names start with an underscore. Frameworks put their bundles in folders like _next and _astro, which would vanish and break both the site and the precache install. Add an empty .nojekyll file to the published folder. When you deploy with the Pages actions shown above, Jekyll does not run.
Caching and updates
Pages sends Cache-Control: max-age=600, so browsers may use a cached sw.js for up to ten minutes. Browsers always revalidate the worker at least once a day and usually on navigation, so a deploy reaches installed apps quickly. When the worker changes, installkite's version downloads only files whose hash changed, copies the rest from the previous cache, and the runtime offers Refresh to update.
Custom domains
With a custom domain such as bakery.example, the project site moves to the domain root and the base path becomes / again. Keep the CNAME file in the published folder so installkite and GitHub both see it, and run npx installkite init --yes after adding the domain so the head tags lose the /repo/ prefix. Installed apps keep their identity as long as the origin stays the same, so set the domain before people install the app: moving from user.github.io/repo/ to a custom domain is a new origin, and visitors would need to install again.
Check the live site
After the deploy, audit the real URL. Doctor fetches the page, the manifest, every icon and the worker exactly as a browser would, and checks HTTPS, the scope and that start_url loads:
npx installkite doctor --url https://user.github.io/repo/This site, the installkite docs, is itself a GitHub Pages project site set up this way. Install it from the address bar and try it in airplane mode.
Questions
Why does my PWA work locally but not on GitHub Pages?
A project site lives under /repo-name/, so links like /manifest.webmanifest or /sw.js point outside it and return 404. Use the base path or relative URLs. installkite detects the repository from your git remote and writes the right base.
Does GitHub Pages support service workers?
Yes. Pages serves HTTPS, which service workers need, and sends JavaScript files with the right content type. Tick Enforce HTTPS in the repository Pages settings.
Why do visitors see an old version after I deploy?
GitHub Pages lets browsers cache files for ten minutes. Browsers check sw.js for updates on navigation and at least once a day, and installkite's worker downloads only the changed files, then the runtime shows Refresh to update.
Do I need a .nojekyll file?
Only if your site has files or folders that start with an underscore, such as _next or _astro, which Jekyll would skip. Add an empty .nojekyll to the published folder.