feat: switch to adapter-static + nginx for SPA deployment #57

Merged
forgejo_admin merged 1 commit from 53-adapter-static-nginx into main 2026-03-27 03:24:08 +00:00

Summary

  • Completes the adapter-node to adapter-static migration after PR #55 removed all server-side code
  • Swaps build/deploy infrastructure from Node.js runtime to nginx serving static files, matching the mcd-tracker-app pattern
  • Removes server secrets and env vars from k8s manifests since auth is now client-side PKCE

Changes

  • svelte.config.js: swap @sveltejs/adapter-node for @sveltejs/adapter-static with SPA fallback to index.html
  • package.json / package-lock.json: replace adapter-node dep with adapter-static (removes ~16 transitive deps)
  • src/routes/+layout.ts: new file, sets ssr = false and prerender = false for pure client-side SPA mode
  • Dockerfile: replace Node.js runtime stage with nginx:alpine, serve static build on port 80 with try_files SPA fallback
  • k8s/deployment.yaml: remove envFrom, env (server secrets/env vars), port 3000 -> 80, lower memory 128Mi -> 64Mi, faster probe timing
  • k8s/service.yaml: port 3000 -> 80
  • k8s/kustomization.yaml: remove pal-e-auth-secrets.enc.yaml reference
  • k8s/pal-e-auth-secrets.enc.yaml: deleted (no longer needed)

Test Plan

  • npm run check passes (0 errors, 1 pre-existing a11y warning)
  • npm run build passes, adapter-static writes site to build/ with index.html fallback
  • CI runs check + lint + build on PR branch
  • After merge: verify nginx container starts on port 80, SPA routing works for deep links

Review Checklist

  • Passed automated review-fix loop
  • No secrets committed
  • No unnecessary file changes
  • Commit messages are descriptive
  • Closes #53
  • pal-e-app -- the project this work belongs to
  • Follows PR #55 (client-side auth migration)

Discovered Scope

  • pal-e-deployments overlay at overlays/pal-e-app/prod/ still references port 3000 in kustomize patches and ingress. Needs a companion PR to update ports 3000 -> 80 and remove the pal-e-auth-secrets.enc.yaml resource.
## Summary - Completes the adapter-node to adapter-static migration after PR #55 removed all server-side code - Swaps build/deploy infrastructure from Node.js runtime to nginx serving static files, matching the mcd-tracker-app pattern - Removes server secrets and env vars from k8s manifests since auth is now client-side PKCE ## Changes - `svelte.config.js`: swap `@sveltejs/adapter-node` for `@sveltejs/adapter-static` with SPA fallback to `index.html` - `package.json` / `package-lock.json`: replace adapter-node dep with adapter-static (removes ~16 transitive deps) - `src/routes/+layout.ts`: new file, sets `ssr = false` and `prerender = false` for pure client-side SPA mode - `Dockerfile`: replace Node.js runtime stage with nginx:alpine, serve static build on port 80 with `try_files` SPA fallback - `k8s/deployment.yaml`: remove `envFrom`, `env` (server secrets/env vars), port 3000 -> 80, lower memory 128Mi -> 64Mi, faster probe timing - `k8s/service.yaml`: port 3000 -> 80 - `k8s/kustomization.yaml`: remove `pal-e-auth-secrets.enc.yaml` reference - `k8s/pal-e-auth-secrets.enc.yaml`: deleted (no longer needed) ## Test Plan - [x] `npm run check` passes (0 errors, 1 pre-existing a11y warning) - [x] `npm run build` passes, adapter-static writes site to `build/` with `index.html` fallback - [ ] CI runs check + lint + build on PR branch - [ ] After merge: verify nginx container starts on port 80, SPA routing works for deep links ## Review Checklist - [x] Passed automated review-fix loop - [x] No secrets committed - [x] No unnecessary file changes - [x] Commit messages are descriptive ## Related Notes - Closes #53 - `pal-e-app` -- the project this work belongs to - Follows PR #55 (client-side auth migration) ## Discovered Scope - `pal-e-deployments` overlay at `overlays/pal-e-app/prod/` still references port 3000 in kustomize patches and ingress. Needs a companion PR to update ports 3000 -> 80 and remove the `pal-e-auth-secrets.enc.yaml` resource.
feat: switch to adapter-static + nginx for SPA deployment
Some checks failed
ci/woodpecker/pr/woodpecker Pipeline failed
814173229e
All server-side code was removed in PR #55. This completes the migration
by swapping the build/deploy infrastructure from Node.js to static files
served by nginx.

- Swap @sveltejs/adapter-node for @sveltejs/adapter-static with SPA fallback
- Add +layout.ts with ssr=false for pure client-side rendering
- Replace Node.js Dockerfile with nginx multi-stage build (port 80)
- Remove server env vars, secrets, and pal-e-auth-secrets.enc.yaml from k8s
- Update k8s deployment/service ports 3000 -> 80 with nginx-appropriate probes
- Reduce memory limits (128Mi -> 64Mi) since nginx is lighter than Node

Closes #53

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Author
Owner

QA Review -- PR #57

Diff Analysis (9 files, +54/-295)

svelte.config.js -- Correct adapter swap. fallback: 'index.html' enables SPA mode. Config matches mcd-tracker-app reference exactly (with strict: true, precompress: false).

src/routes/+layout.ts -- Required for adapter-static SPA mode. ssr = false and prerender = false match the mcd-tracker-app pattern. Correctly placed at root layout level.

package.json / package-lock.json -- Clean swap from @sveltejs/adapter-node to @sveltejs/adapter-static. Removes ~16 transitive dependencies (rollup plugins, resolve, etc.). No unrelated dependency changes.

Dockerfile -- Clean replacement of Node.js runtime with nginx:alpine. Build stage unchanged. nginx config includes try_files $uri $uri/ /index.html for SPA fallback and static asset caching with 1y expiry. Matches mcd-tracker-app Dockerfile exactly.

k8s/deployment.yaml -- All server env vars (PAL_E_DOCS_API_URL, PAL_E_DOCS_API_KEY, AUTH_TRUST_HOST) and envFrom removed. Port 3000 -> 80. Probe timings tightened (nginx starts faster than Node). Memory reduced 128Mi -> 64Mi (appropriate for nginx). Image tag line format preserved so CI sed pattern still works.

k8s/service.yaml -- Port 3000 -> 80 on both port and targetPort.

k8s/kustomization.yaml -- pal-e-auth-secrets.enc.yaml reference removed.

k8s/pal-e-auth-secrets.enc.yaml -- Deleted. No longer needed since auth is client-side PKCE.

Verification

  • npm run check -- 0 errors (1 pre-existing a11y warning)
  • npm run build -- passes, adapter-static writes to build/ with index.html fallback

Discovered Scope (acknowledged)

The pal-e-deployments overlay still references port 3000 and pal-e-auth-secrets.enc.yaml. Correctly identified in PR body as needing a companion PR.

VERDICT: APPROVE

Clean, focused infrastructure swap. All changes are consistent and match the proven mcd-tracker-app pattern. No secrets committed, no unnecessary file changes.

## QA Review -- PR #57 ### Diff Analysis (9 files, +54/-295) **svelte.config.js** -- Correct adapter swap. `fallback: 'index.html'` enables SPA mode. Config matches mcd-tracker-app reference exactly (with `strict: true`, `precompress: false`). **src/routes/+layout.ts** -- Required for adapter-static SPA mode. `ssr = false` and `prerender = false` match the mcd-tracker-app pattern. Correctly placed at root layout level. **package.json / package-lock.json** -- Clean swap from `@sveltejs/adapter-node` to `@sveltejs/adapter-static`. Removes ~16 transitive dependencies (rollup plugins, resolve, etc.). No unrelated dependency changes. **Dockerfile** -- Clean replacement of Node.js runtime with nginx:alpine. Build stage unchanged. nginx config includes `try_files $uri $uri/ /index.html` for SPA fallback and static asset caching with 1y expiry. Matches mcd-tracker-app Dockerfile exactly. **k8s/deployment.yaml** -- All server env vars (`PAL_E_DOCS_API_URL`, `PAL_E_DOCS_API_KEY`, `AUTH_TRUST_HOST`) and `envFrom` removed. Port 3000 -> 80. Probe timings tightened (nginx starts faster than Node). Memory reduced 128Mi -> 64Mi (appropriate for nginx). Image tag line format preserved so CI `sed` pattern still works. **k8s/service.yaml** -- Port 3000 -> 80 on both `port` and `targetPort`. **k8s/kustomization.yaml** -- `pal-e-auth-secrets.enc.yaml` reference removed. **k8s/pal-e-auth-secrets.enc.yaml** -- Deleted. No longer needed since auth is client-side PKCE. ### Verification - `npm run check` -- 0 errors (1 pre-existing a11y warning) - `npm run build` -- passes, adapter-static writes to `build/` with `index.html` fallback ### Discovered Scope (acknowledged) The `pal-e-deployments` overlay still references port 3000 and `pal-e-auth-secrets.enc.yaml`. Correctly identified in PR body as needing a companion PR. ### VERDICT: APPROVE Clean, focused infrastructure swap. All changes are consistent and match the proven mcd-tracker-app pattern. No secrets committed, no unnecessary file changes.
Author
Owner

PR #57 Review

DOMAIN REVIEW

Tech stack: SvelteKit 2 (adapter-static), nginx:alpine Docker image, Kubernetes manifests (Deployment, Service, Kustomization), SOPS-encrypted secrets deletion.

SvelteKit / adapter-static:

  • svelte.config.js: Correctly configured with fallback: 'index.html' for SPA routing, pages and assets both point to build/, strict: true is good practice. This is textbook adapter-static SPA config.
  • src/routes/+layout.ts: Sets ssr = false and prerender = false -- correct for pure client-side SPA mode. File is .ts (not .server.ts), which is required for adapter-static.
  • No +page.server.ts, +layout.server.ts, or hooks.server.ts files remain in src/ -- confirmed clean.
  • package.json: adapter-node replaced with adapter-static v3.0.10. Lock file removes ~16 transitive deps (rollup plugins, resolve, commondir, etc.). Clean dependency reduction.

Dockerfile (nginx:alpine):

  • Pattern matches mcd-tracker-app/Dockerfile exactly: same build stage, same nginx stage, same printf config injection, same try_files SPA fallback, same static asset cache headers.
  • try_files $uri $uri/ /index.html is correct for SPA deep-link support.
  • Static asset caching (expires 1y, Cache-Control: public, immutable) is appropriate for hashed SvelteKit output.
  • EXPOSE 80 and CMD ["nginx", "-g", "daemon off;"] are correct.

Kubernetes manifests:

  • deployment.yaml: All server env vars removed (envFrom, env block with PAL_E_DOCS_API_URL, PAL_E_DOCS_API_KEY, AUTH_TRUST_HOST). No secrets references remain. Port correctly changed to 80. Memory reduced from 128Mi to 64Mi -- appropriate for nginx serving static files. Probe timing tightened (nginx starts faster than Node.js).
  • service.yaml: Port 3000 -> 80 on both port and targetPort.
  • kustomization.yaml: pal-e-auth-secrets.enc.yaml reference removed.
  • pal-e-auth-secrets.enc.yaml: Deleted. SOPS-encrypted file containing AUTH_SECRET, AUTH_KEYCLOAK_ID, AUTH_KEYCLOAK_SECRET, AUTH_KEYCLOAK_ISSUER, PAL_E_DOCS_API_KEY -- all server-side auth secrets that are no longer needed after PKCE migration.

Security: The deletion of server secrets is a positive security change. Client-side auth uses PKCE (no client secret). The api-client.ts correctly uses VITE_ prefixed env vars and the public Tailscale funnel URL, with Bearer token auth from keycloak-js. No secrets leak risk.

BLOCKERS

None. This is a clean infrastructure swap with no new functionality requiring test coverage. The PR is a build/deploy infrastructure change -- the application code itself is unchanged. Existing Playwright E2E tests in CI cover the app behavior, and the PR correctly notes that post-merge validation (nginx starts, SPA routing works) is needed.

NITS

  1. docker-compose.yml port mapping is stale (/home/ldraney/pal-e-app/docker-compose.yml line 5): Still maps 3000:3000 but the container now exposes port 80. Should be "3000:80" (or "80:80") to work for local Docker Compose usage. The environment block also still passes PAL_E_DOCS_API_URL and NODE_ENV=production which are no longer consumed by the nginx container.

  2. CLAUDE.md is completely stale (/home/ldraney/pal-e-app/CLAUDE.md): Still references adapter-node (lines 8, 29), Tailwind CSS (line 9), server-side data loading via +page.server.ts (lines 11, 26, 58-61), and server-only api.ts (line 52). After this PR and PR #55, every convention listed is wrong. This will actively mislead agents working on this repo.

  3. .env.example references server-side vars (/home/ldraney/pal-e-app/.env.example): Documents PAL_E_DOCS_API_URL (no VITE_ prefix, server-side) and PAL_E_DOCS_API_KEY. Neither is consumed anymore. The client-side equivalent is VITE_PAL_E_DOCS_API_URL (used in api-client.ts).

  4. README.md references .env.example (/home/ldraney/pal-e-app/README.md line 9): The cp .env.example .env instruction sets a server-side var that no longer does anything.

  5. Discovered scope is well-documented: The pal-e-deployments overlay port update is correctly called out. Good practice.

Note: Nits 1-4 are all documentation/config staleness from the broader adapter-node -> adapter-static + PKCE migration spanning PRs #55 and #57. They should be tracked as a follow-up issue (or folded into existing #56 "QA nits from PR #55").

SOP COMPLIANCE

  • Branch named after issue: 53-adapter-static-nginx references issue #53
  • PR body has Summary, Changes, Test Plan, Related sections
  • Related references parent issue: "Closes #53"
  • Related references plan slug -- no plan slug referenced (acceptable if this is board-driven work, not plan-driven)
  • No secrets committed -- SOPS-encrypted file deleted, no plaintext secrets in diff
  • No unnecessary file changes -- all 9 changed files are directly related to the adapter swap
  • Commit messages are descriptive (PR title: "feat: switch to adapter-static + nginx for SPA deployment")
  • Discovered scope documented in PR body

PROCESS OBSERVATIONS

  • Deployment frequency: This is a clean infrastructure simplification (Node.js runtime -> static files + nginx). Reduces attack surface, lowers memory footprint (128Mi -> 64Mi), and removes a class of server-side bugs entirely. Positive DORA impact.
  • Change failure risk: Low. The adapter-static config is identical to the battle-tested mcd-tracker-app pattern. The k8s manifest changes are straightforward port/resource updates. The pal-e-deployments companion PR (noted as discovered scope) must be merged in coordination.
  • Documentation gap: CLAUDE.md, .env.example, README.md, and docker-compose.yml are all stale. This is a documentation debt item that should be tracked. Agents working on this repo will get wrong instructions from CLAUDE.md.

VERDICT: APPROVED

The core infrastructure change is clean, correct, and follows the established mcd-tracker-app pattern exactly. No blockers. The stale documentation files (CLAUDE.md, .env.example, README.md, docker-compose.yml) should be tracked as a follow-up issue -- they predate this PR and are cumulative debt from the #55 + #57 migration.

## PR #57 Review ### DOMAIN REVIEW **Tech stack**: SvelteKit 2 (adapter-static), nginx:alpine Docker image, Kubernetes manifests (Deployment, Service, Kustomization), SOPS-encrypted secrets deletion. **SvelteKit / adapter-static**: - `svelte.config.js`: Correctly configured with `fallback: 'index.html'` for SPA routing, `pages` and `assets` both point to `build/`, `strict: true` is good practice. This is textbook adapter-static SPA config. - `src/routes/+layout.ts`: Sets `ssr = false` and `prerender = false` -- correct for pure client-side SPA mode. File is `.ts` (not `.server.ts`), which is required for adapter-static. - No `+page.server.ts`, `+layout.server.ts`, or `hooks.server.ts` files remain in `src/` -- confirmed clean. - `package.json`: adapter-node replaced with adapter-static v3.0.10. Lock file removes ~16 transitive deps (rollup plugins, resolve, commondir, etc.). Clean dependency reduction. **Dockerfile (nginx:alpine)**: - Pattern matches `mcd-tracker-app/Dockerfile` exactly: same build stage, same nginx stage, same `printf` config injection, same `try_files` SPA fallback, same static asset cache headers. - `try_files $uri $uri/ /index.html` is correct for SPA deep-link support. - Static asset caching (`expires 1y`, `Cache-Control: public, immutable`) is appropriate for hashed SvelteKit output. - `EXPOSE 80` and `CMD ["nginx", "-g", "daemon off;"]` are correct. **Kubernetes manifests**: - `deployment.yaml`: All server env vars removed (`envFrom`, `env` block with `PAL_E_DOCS_API_URL`, `PAL_E_DOCS_API_KEY`, `AUTH_TRUST_HOST`). No secrets references remain. Port correctly changed to 80. Memory reduced from 128Mi to 64Mi -- appropriate for nginx serving static files. Probe timing tightened (nginx starts faster than Node.js). - `service.yaml`: Port 3000 -> 80 on both `port` and `targetPort`. - `kustomization.yaml`: `pal-e-auth-secrets.enc.yaml` reference removed. - `pal-e-auth-secrets.enc.yaml`: Deleted. SOPS-encrypted file containing `AUTH_SECRET`, `AUTH_KEYCLOAK_ID`, `AUTH_KEYCLOAK_SECRET`, `AUTH_KEYCLOAK_ISSUER`, `PAL_E_DOCS_API_KEY` -- all server-side auth secrets that are no longer needed after PKCE migration. **Security**: The deletion of server secrets is a positive security change. Client-side auth uses PKCE (no client secret). The `api-client.ts` correctly uses `VITE_` prefixed env vars and the public Tailscale funnel URL, with Bearer token auth from keycloak-js. No secrets leak risk. ### BLOCKERS None. This is a clean infrastructure swap with no new functionality requiring test coverage. The PR is a build/deploy infrastructure change -- the application code itself is unchanged. Existing Playwright E2E tests in CI cover the app behavior, and the PR correctly notes that post-merge validation (nginx starts, SPA routing works) is needed. ### NITS 1. **`docker-compose.yml` port mapping is stale** (`/home/ldraney/pal-e-app/docker-compose.yml` line 5): Still maps `3000:3000` but the container now exposes port 80. Should be `"3000:80"` (or `"80:80"`) to work for local Docker Compose usage. The `environment` block also still passes `PAL_E_DOCS_API_URL` and `NODE_ENV=production` which are no longer consumed by the nginx container. 2. **`CLAUDE.md` is completely stale** (`/home/ldraney/pal-e-app/CLAUDE.md`): Still references adapter-node (lines 8, 29), Tailwind CSS (line 9), server-side data loading via `+page.server.ts` (lines 11, 26, 58-61), and server-only `api.ts` (line 52). After this PR and PR #55, every convention listed is wrong. This will actively mislead agents working on this repo. 3. **`.env.example` references server-side vars** (`/home/ldraney/pal-e-app/.env.example`): Documents `PAL_E_DOCS_API_URL` (no `VITE_` prefix, server-side) and `PAL_E_DOCS_API_KEY`. Neither is consumed anymore. The client-side equivalent is `VITE_PAL_E_DOCS_API_URL` (used in `api-client.ts`). 4. **`README.md` references `.env.example`** (`/home/ldraney/pal-e-app/README.md` line 9): The `cp .env.example .env` instruction sets a server-side var that no longer does anything. 5. **Discovered scope is well-documented**: The `pal-e-deployments` overlay port update is correctly called out. Good practice. Note: Nits 1-4 are all documentation/config staleness from the broader adapter-node -> adapter-static + PKCE migration spanning PRs #55 and #57. They should be tracked as a follow-up issue (or folded into existing #56 "QA nits from PR #55"). ### SOP COMPLIANCE - [x] Branch named after issue: `53-adapter-static-nginx` references issue #53 - [x] PR body has Summary, Changes, Test Plan, Related sections - [x] Related references parent issue: "Closes #53" - [ ] Related references plan slug -- no plan slug referenced (acceptable if this is board-driven work, not plan-driven) - [x] No secrets committed -- SOPS-encrypted file deleted, no plaintext secrets in diff - [x] No unnecessary file changes -- all 9 changed files are directly related to the adapter swap - [x] Commit messages are descriptive (PR title: "feat: switch to adapter-static + nginx for SPA deployment") - [x] Discovered scope documented in PR body ### PROCESS OBSERVATIONS - **Deployment frequency**: This is a clean infrastructure simplification (Node.js runtime -> static files + nginx). Reduces attack surface, lowers memory footprint (128Mi -> 64Mi), and removes a class of server-side bugs entirely. Positive DORA impact. - **Change failure risk**: Low. The adapter-static config is identical to the battle-tested mcd-tracker-app pattern. The k8s manifest changes are straightforward port/resource updates. The pal-e-deployments companion PR (noted as discovered scope) must be merged in coordination. - **Documentation gap**: CLAUDE.md, .env.example, README.md, and docker-compose.yml are all stale. This is a documentation debt item that should be tracked. Agents working on this repo will get wrong instructions from CLAUDE.md. ### VERDICT: APPROVED The core infrastructure change is clean, correct, and follows the established mcd-tracker-app pattern exactly. No blockers. The stale documentation files (CLAUDE.md, .env.example, README.md, docker-compose.yml) should be tracked as a follow-up issue -- they predate this PR and are cumulative debt from the #55 + #57 migration.
forgejo_admin deleted branch 53-adapter-static-nginx 2026-03-27 03:24:08 +00:00
Sign in to join this conversation.
No reviewers
No milestone
No project
No assignees
1 participant
Notifications
Due date
The due date is invalid or out of range. Please use the format "yyyy-mm-dd".

No due date set.

Dependencies

No dependencies set.

Reference
forgejo_admin/pal-e-docs-app!57
No description provided.