From 3b79271874b53cb88fe1a937489f10819626372f Mon Sep 17 00:00:00 2001 From: dtonon Date: Tue, 18 Aug 2026 21:53:29 +0100 Subject: [PATCH] Add build and deploy recipes for the server-rendered target --- README.md | 20 ++++++++++++++------ justfile | 31 ++++++++++++++++++++++++++++++- 2 files changed, 44 insertions(+), 7 deletions(-) diff --git a/README.md b/README.md index 5ff1493..db3cf8f 100644 --- a/README.md +++ b/README.md @@ -90,12 +90,20 @@ npm run dev -- --open ## Building -To create a production version of your app: +Two deployment targets share the same code base, selected by `PUBLIC_SSR`: -```sh -npm run build -``` +- **Static (default, `PUBLIC_SSR=no`)** — `npm run build` (or `just build`) writes a single-page bundle to `build/`; serve it from any web server with `index.html` as the fallback for unknown paths. Everything is fetched by the browser. +- **Server-rendered (`PUBLIC_SSR=yes`)** — `just build-ssr` writes a Node app to `build/`. Pages arrive as crawlable HTML (threads, rooms, resources, contacts, with description/Open Graph tags, JSON-LD, a live `robots.txt` and `sitemap.xml`, and real 404s), then the browser takes over exactly as in the static build. The server reads the relay anonymously, so it only ever renders public content; members see their private rooms once the client is running. -You can preview the production build with `npm run preview`. +Preview a build locally with `npm run preview` (static) or `node --env-file=.env.production build` (server). -> To deploy your app, you may need to install an [adapter](https://svelte.dev/docs/kit/adapters) for your target environment. +## Deploying + +`just deploy ` rsyncs the static bundle to `~/squalk/` on the host and purges the Cloudflare cache. + +`just deploy-ssr ` ships the Node build, `package.json`/`package-lock.json` and `.env.production` (as `~/squalk/.env`, since the server reads the `PUBLIC_*` values at runtime), runs `npm ci --omit=dev` and restarts the `squalk` systemd unit. On the host you need: + +- Node 22 or newer (the relay client uses the built-in `WebSocket`). +- The unit from [`deploy/squalk.service`](deploy/squalk.service), with `ORIGIN` set to the public URL — it feeds canonical links, `robots.txt` and the sitemap. +- A reverse proxy (Caddy, nginx) in front of the port in `PORT`. +- If Cloudflare sits in front, a cache rule that caches HTML and respects origin headers: pages and snapshots are sent with `Cache-Control: public, max-age=0, s-maxage=300, stale-while-revalidate=3600`, so the edge serves them for five minutes and refreshes in the background for an hour after that. `just deploy-ssr` purges the cache after each release. diff --git a/justfile b/justfile index 2326605..ee4e5b1 100644 --- a/justfile +++ b/justfile @@ -1,8 +1,37 @@ +set dotenv-load + +# Cloudflare credentials (set these as environment variables) +CF_ZONE_ID := env_var_or_default("CF_ZONE_ID", "") +CF_API_TOKEN := env_var_or_default("CF_API_TOKEN", "") +CF_HOST := env_var_or_default("CF_HOST", "") + dev: npm run dev +# Static single-page bundle (served by any web server) build: - npm run build + PUBLIC_SSR=no npm run build + +# Server-rendered bundle (needs Node 22+ on the host, see deploy/squalk.service) +build-ssr: + PUBLIC_SSR=yes npm run build deploy target: build rsync -av --delete --progress build/ {{target}}:~/squalk/ + @just purge-web-cache + +# Ships the Node build plus its runtime deps and env, then restarts the unit +deploy-ssr target: build-ssr + rsync -av --delete --progress --exclude node_modules build/ {{target}}:~/squalk/build/ + rsync -av package.json package-lock.json {{target}}:~/squalk/ + rsync -av .env.production {{target}}:~/squalk/.env + ssh {{target}} 'cd ~/squalk && npm ci --omit=dev && sudo systemctl restart squalk' + @just purge-web-cache + +purge-web-cache: + @echo "\nPurging Cloudflare cache... for zone {{CF_ZONE_ID}}" + @curl -s -X POST "https://api.cloudflare.com/client/v4/zones/{{CF_ZONE_ID}}/purge_cache" \ + -H "Authorization: Bearer {{CF_API_TOKEN}}" \ + -H "Content-Type: application/json" \ + --data '{"purge_everything": true}' \ + | jq -r 'if .success then "✅ Cache purged successfully" else "‼️ Error: " + (.errors[0].message // "Unknown error") end'