A client-only React 19 + Vite workspace for browser-based utilities. The app currently ships JSON and timestamp tools, stores state in localStorage, and builds to static assets in dist/.
- Node.js 22+
- pnpm 10+
pnpm installpnpm devpnpm buildpnpm lintpnpm preview
Production is intentionally static:
- GitHub Actions runs
pnpm build - the generated
dist/directory is uploaded to the VPS - the VPS serves those files with
ghcr.io/caddybuilds/caddy-cloudflare:latest - there is no Node.js runtime container in production
Hash routes such as #/json and #/timestamp do not need SPA rewrite rules because the server only ever receives /.
.github/workflows/deploy.ymlbuilds and deploys on pushes tomasterdeploy/compose.yamlruns the Caddy container on the VPSdeploy/Caddyfileserves the static files, enables compression, and sets cache headers
Add these repository secrets before enabling the workflow:
VPS_HOST: VPS hostname or IPVPS_USER: SSH user used for deploymentsVPS_SSH_KEY: private key for that deploy userVPS_DEPLOY_PATH: target path on the VPS, for example/opt/web-toolsVPS_DOMAIN: production domain Caddy should serve
The workflow currently deploys on pushes to master. If your live branch changes later, update the branch list in .github/workflows/deploy.yml.
The VPS needs:
- Docker Engine
docker composeplugin
sudo mkdir -p /opt/web-tools/site /opt/web-tools/caddy_data /opt/web-tools/caddy_config
sudo chown -R <deploy-user>:<deploy-user> /opt/web-toolsIf you use a path other than /opt/web-tools, keep it in sync with the VPS_DEPLOY_PATH GitHub secret.
Create /opt/web-tools/.env on the VPS with the DNS token used by the Cloudflare-enabled Caddy image:
CLOUDFLARE_API_TOKEN=your-cloudflare-api-tokenKeep this file on the server only. The GitHub Actions workflow does not need the Cloudflare token.
After the first workflow run has copied compose.yaml, Caddyfile, and the built site:
docker compose -f /opt/web-tools/compose.yaml --project-directory /opt/web-tools up -dThe workflow also writes /opt/web-tools/site.env on each deploy with the configured DOMAIN value from the VPS_DOMAIN GitHub secret.
/opt/web-tools/
├── .env
├── site.env
├── Caddyfile
├── compose.yaml
├── caddy_config/
├── caddy_data/
└── site/
pnpm buildConfirm the build succeeds and outputs files in dist/.
docker compose -f /opt/web-tools/compose.yaml --project-directory /opt/web-tools ps- verify Caddy is up and ports
80and443are published - verify your domain points at the VPS
- verify HTTPS loads successfully
- open the deployed site
- refresh on
#/json - refresh on
#/timestamp - confirm browser-only features such as
localStorage, clipboard actions, theme persistence, and locale persistence still work
- push a small change to
master - confirm the
Deployworkflow runspnpm build - confirm the workflow uploads the new
dist/bundle and restarts the Caddy stack - confirm the live site reflects the change