Both subprojects ship from the same commit, so ordinary CI is now a single ci.yml; only the tag-driven release stands apart. The web app is built twice per run — once per base path — and every consumer downloads the artifact instead of rebuilding, replacing three redundant base-"" builds on main. Nothing deploys unless the test job is green, and android-release runs the suite before signing (ci.yml does not fire on tags, so it was the only gap). Shared toolchain setup moves into composite actions, which puts the web build and the APK on the same Node version for the first time. The Firebase PR path was still on npm ci against a stale web/package-lock.json that could resolve a different tree than pnpm-lock.yaml; drop the lockfile and the npm path with it. Also: least-privilege permissions widened per job, persist-credentials off on every checkout, concurrency groups that cancel superseded PRs but never a live deploy, npm caching for android, and the Firebase action pinned by commit SHA to match how the release actions were already pinned.
5.6 KiB
Deployment Guide
Build Profiles
| Script | basePath | Target |
|---|---|---|
npm run build |
"" (root) |
Local preview / generic static host |
npm run build:gh |
/loto |
GitHub Pages → https://tiennm99.github.io/loto (canonical) |
Implementation: svelte.config.js reads BUILD_PROFILE env. Default is empty
basePath; BUILD_PROFILE=gh npm run build switches to /loto.
Internal links use import { base } from '$app/paths' so they survive
either profile without code changes.
Production Deployment — GitHub Pages
Canonical deploy. Wired via the deploy-pages job in
.github/workflows/ci.yml: on push to main, the build (gh) job runs
pnpm build:gh and uploads build/; deploy-pages downloads that artifact
and publishes it. Both are gated on the test job.
One-time setup (already done; documented for restoration):
- Repo → Settings → Pages → Source: GitHub Actions.
- Push to
maintriggers the workflow; the deploy job posts the live URL on completion.
URL: https://tiennm99.github.io/loto/
No external secrets; the workflow uses GitHub's built-in pages and
id-token permissions (declared in the workflow YAML).
Development Environment
Local Dev
npm install
npm run dev
Access at http://localhost:3000 (no basePath).
HMR works automatically.
Code-Server Dev
For browser-based development (VS Code in browser):
1. Start code-server with Node.js environment:
code-server --no-auth
2. Create .env.local in project root:
VITE_DEV_PROFILE=codeserver
CODESERVER_HOST=your-machine.example.com
CODESERVER_PORT=3000
Replace your-machine.example.com with your actual hostname/IP (must match the proxy URL you'll access).
3. Run dev server:
npm run dev:codeserver
This reads vite.config.js codeserver config (basePath /absproxy/{PORT}, HMR proxy).
4. Access via browser: Navigate to:
https://your-codeserver-host/absproxy/3000/
Key Points:
/absproxy/{port}(NOT/proxy/{port}) preserves basePath through the proxy.- HMR socket connects to
CODESERVER_HOSTfor live reload. - If HMR fails, manually refresh the page (Vite HMR still compiles server-side).
Manual Refresh Workaround
If HMR over proxy is unreliable:
- Make code changes
- Manually refresh browser (F5)
- Dev server has already compiled the changes
This is normal in proxy environments.
Build & Output
Build Command
npm run build:gh
Generates:
build/— Complete static HTML + JS export with/lotobasePath.svelte-kit/— Build cache (not needed for deployment)
Export Settings
adapter-staticinsvelte.config.js- No server-side rendering (SSR disabled via
ssr: false) - All pages pre-rendered to HTML + JS bundles
Asset Hosting
basepath matches deployment target (GH:/loto, root for local preview, codeserver:/absproxy/{port})- CSS, JS, fonts all prefixed correctly
- GitHub Pages serves the project at
/loto, so/loto/_app/*paths resolve correctly
Environment Variables
Development (code-server only)
VITE_DEV_PROFILE— set to "codeserver" to enable proxy modeCODESERVER_HOST— hostname for HMR proxyCODESERVER_PORT— port (default 3000)
Build-Time
BUILD_PROFILE— set toghfor GitHub Pages build (basePath/loto). The deploy workflow sets this vianpm run build:gh. Default empty (root basePath) is for local preview / non-GH static hosts.
Not Used at Runtime
- No database URL, API keys, or secrets (all client-side, localStorage)
.env.localis.gitignored and safe for local config
Troubleshooting
| Issue | Cause | Fix |
|---|---|---|
| 404 on assets after deploy | basePath mismatch | Workflow runs npm run build:gh — check the deploy job log emits /loto/_app/... URLs |
| HMR not connecting (code-server) | CODESERVER_HOST not set | Add CODESERVER_HOST=... to .env.local |
| Assets 404 (code-server) | Wrong proxy URL | Use /absproxy/{port}, not /proxy/{port} |
| Page blank after refresh | State not persisted | Check browser localStorage is enabled |
| Stale CSS (code-server) | HMR failed | Manually refresh page (F5) |
CI/CD Pipeline
One workflow, .github/workflows/ci.yml, covers PRs and pushes to main:
test—pnpm test. Every other job depends on it, so a red suite blocks all deploys.build— a two-entry matrix producing the only two web builds in the run:pnpm build(base"", artifactweb-build) andpnpm build:gh(base/loto, artifactweb-build-gh).deploy-pages— canonical deploy; publishesweb-build-gh.deploy-firebase/preview-firebase— Firebase live channel onmain, preview channel on same-repo PRs; both consumeweb-build.android-debug— syncsweb-buildinto the Capacitor project and assembles an unsigned APK.
Tags run .github/workflows/android-release.yml instead, which builds the
web app itself because no ci run exists to take artifacts from.
Performance Checklist
- Static export via adapter-static (no server overhead)
- Tailwind 4 purged for production size
- localStorage reduces bundle—no API calls
- Images minimal (mostly CSS gradients + emojis)
- Fonts: Roboto Condensed self-hosted via @fontsource
Bundle analysis: Run npm run build && ls -lh build/ to inspect file sizes.
Security Considerations
- No sensitive data in code (no API keys, secrets)
.env.localis local-only, not committed- localStorage scoped to origin
- No external API calls (offline-capable)
- GitHub Pages serves HTTPS by default
Last reviewed: 2026-05-09