fix(build): unbreak production build — /hilfe/transkription prerender unreachable behind /login #486
Reference in New Issue
Block a user
Delete Branch "fix/issue-472-prerender-entries-v2"
Deleting a branch is permanent. Although the deleted branch may continue to exist for a short time before it actually gets removed, it CANNOT be undone in most cases. Continue?
Closes #472
Problem
npm run buildexits non-zero because/hilfe/transkriptionis declaredprerender = truebut the SvelteKit prerender crawler is intercepted byhooks.server.tsbefore it can reach the route. The build logs:Fix
Option A from the issue — add an explicit
prerender.entriesdeclaration insvelte.config.jsso SvelteKit renders the route directly without needing to crawl to it:Also removes the misleading comment in
+page.tsthat claimedhooks.server.tsguards prerendered static files. It does not — the auth hook only runs for SSR requests; prerendered HTML is served as a static file by the reverse proxy.CI gate
Adds
npm run buildafter the test step in theunit-testsCI job. Without this gate, the fix has no regression protection — a futureprerender = trueroute that becomes unreachable behind auth would fail silently again.Test plan
npm test)npm run buildexits 0 and/hilfe/transkription.htmlis produced🤖 Generated with Claude Code
🏗️ Markus Keller — Application Architect
Verdict: ✅ Approved
What I checked
Architectural correctness of Option A. Adding
prerender.entriestosvelte.config.jsis the documented SvelteKit escape hatch for exactly this scenario: a route that is correctly declared prerenderable but cannot be discovered by the crawler because of a runtime gate (the auth handler). The fix expresses intent in configuration without touching auth semantics or routing behavior. Correct call.Scope of change. The diff is exactly the right size — 3 files, 8 lines added, 3 removed. No new routes, packages, services, or domain modules. The architecture documentation checklist from my review guide returns no required updates: no Flyway migration, no new controller, no new SvelteKit route (the route already exists), no new Docker service, no new external system, no auth flow change, no new ErrorCode or Permission, no new domain concept. Nothing to document beyond what the PR description already captures.
CI placement. The build step correctly slots after
npm testand beforeUpload screenshotsin theunit-testsjob. It reuses the already-cachednode_modules. The Paraglide compilation step already runs before tests, so the build has everything it needs. No new job, no new runner — operationally free.Comment removal. The removed comment was architecturally misleading — it implied that
hooks.server.tsprotects a prerendered static file, which is false when a reverse proxy serves the static output directory directly. Removing it eliminates a false belief that could be cargo-culted to a future route containing sensitive content. Theprerender = trueexport is self-documenting SvelteKit convention.ADR not needed. Adding a route to
prerender.entriesis not an architectural decision with lasting consequences — it is a config-level correction to make one route's declaration match its behavior. No ADR warranted.No blockers. No concerns.
The fix is minimal, correct, and complete.
👨💻 Felix Brandt — Senior Fullstack Developer
Verdict: ✅ Approved
What I checked
The config change is correct and minimal.
prerender: { entries: ['/hilfe/transkription'] }is the exact documented fix for this scenario. It adds no side effects on any other route, auth behavior, or SSR behavior. The array is hardcoded and does not accept user input — no injection surface.The comment removal is the right call. The removed comment was factually wrong and dangerous — it gave false confidence that the auth hook protects prerendered static files. It does not; the hook only fires for SSR requests through the Node server. A future developer adding a sensitive
prerender = trueroute could have cargo-culted that comment and shipped a public page believing it was auth-gated. Deleting it is cleaner than replacing it, becauseexport const prerender = trueis self-documenting SvelteKit convention.TDD acknowledgement in the PR. The PR description correctly notes that the "red" step here is the failing
npm run buildoutput, not a failing unit test. This is appropriate — prerender behavior is a build-time concern, not a component concern. The existing 8 component tests forhilfe/transkriptioncover the rendered UI and remain valid. No new tests were added or needed.CI step.
npm run buildafternpm testis correct ordering — tests first, then build. If tests fail, the build is skipped. The Paraglide compilation precedes both, so the build environment is fully prepared. The build step produces no artifact and doesn't need one — its only purpose is to exit 0 and prove the prerender completes.One observation (not a blocker): There's a local environment issue where the
paraglide/directory is owned byroot(from a previous Docker run), causingnpm run buildto fail locally withEACCES: permission denied. This does not affect CI (runs in a fresh container). Worth a note in the repo docs or dev setup script: if the build fails locally with permission errors onparaglide/, runsudo chown -R $USER:$USER frontend/src/lib/paraglide/.No blockers.
🚀 Tobias Wendt — DevOps & Platform Engineer
Verdict: ✅ Approved
What I checked
CI step placement and structure. The
Build frontendstep slots correctly afternpm testand beforeUpload screenshotsin theunit-testsjob. TheCompile Paraglide i18nstep that precedes tests also precedes this build — the build environment is fully prepared. No new job, no new runner, no extra queue time beyond the build duration.Cache reuse. The existing
actions/cache@v4step forfrontend/node_modules(keyed onpackage-lock.json) is already in place and warms before this step runs. The build does not need its own cache entry.Artifact behavior under failure. The
Upload screenshotsstep hasif: always(), which means test screenshots are uploaded even if the newBuild frontendstep fails. This is the correct behavior — a build failure shouldn't discard test failure evidence.No artifact upload for build output. The build step correctly produces no uploaded artifact. The output is transient — its only purpose is to verify
npm run buildexits 0 and the prerender completes. The actual container image build uses a separate Dockerfile that runsnpm run buildindependently.Paraglide permission issue (local only). The
paraglide/directory on the local dev machine is owned byrootfrom a previous Docker run, causingnpm run buildto fail locally. This is a dev environment issue, not a CI issue — the CI runner uses a fresh container where the directory is always writable. No CI config change needed.Suggestion (not a blocker): Add a dev setup note or a Makefile target
make fix-permsthat runschown -R $(id -u):$(id -g) frontend/src/lib/paraglide/for developers who hit this. It's a one-time fix after any Docker-based paraglide compile.No blockers.
🔒 Nora "NullX" Steiner — Security Engineer
Verdict: ✅ Approved
What I checked
Attack surface delta: zero. This PR touches only
svelte.config.js(build config),+page.ts(one-line file, removes a comment), andci.yml(workflow). None of these introduce runtime security surface. Theprerender.entriesvalue is a hardcoded string literal — not user-controlled, not injectable.Comment removal: security documentation improvement. This is the most security-relevant change in the PR. The removed comment asserted:
This is factually wrong. When a reverse proxy (Caddy) serves
.svelte-kit/output/prerendered/as static files directly,hooks.server.tsdoes not run against those files — it only runs for SSR requests through the Node server. The comment created a false belief about the security model that could have been cargo-culted to a futureprerender = trueroute containing sensitive data.Removing it eliminates that false belief without replacing it with a potentially wrong new comment. The
export const prerender = trueexport is self-documenting. If future prerendered routes need security context, those should be documented at the point of addition with accurate descriptions of what the reverse proxy exposes.Open question from the issue review (not a blocker for this PR): Is
/hilfe/transkriptionintentionally publicly accessible in production? If Caddy serves the prerendered static file directly (bypassing the Node server), the page is publicly reachable regardless ofPUBLIC_PATHS. The page contains only transcription guidelines — no personal data — so this is not a vulnerability. But the intent should be documented. Recommend a follow-up issue to explicitly decide and document the public/private access model for prerendered routes.No security regressions. No new vulnerabilities. No blockers.
🧪 Sara Holt — QA Engineer
Verdict: ✅ Approved
What I checked
No new unit tests — correctly justified. The PR description acknowledges this explicitly: the "red" step is the failing
npm run buildoutput. Prerender behavior is a build-time concern, not a component concern. There is no test that should check for a file on disk — that would couple tests to build artifacts. The CI gate is the correct regression mechanism here.Existing test coverage unaffected. The 8 existing component tests for
/hilfe/transkription/page.svelte.spec.tscover heading, intro paragraph, external link security attributes, section headings, rule cards, and clarification chips. None of these tests care about the prerender flag or the removed comment — they test rendered output, which is unchanged by this PR. All 1835 frontend tests pass.CI gate quality. The
Build frontendstep in CI is the right regression gate: if a futureprerender = trueroute becomes unreachable behind auth, the CI build fails immediately on the next PR. This closes the gap that allowed the original bug to exist undetected.Test plan item not yet verified: The PR description has an unchecked box:
This is expected — the CI run needs to complete on Gitea to confirm the fix works end-to-end. The local build was blocked by a
root-ownedparaglide/directory (dev environment issue, not a code issue). The CI run in a fresh container should succeed.Suggestion (not a blocker): Once the CI run completes green, tick that box in the PR description. It's the one verification step that proves the entire fix in an environment matching production.
Follow-up suggestion: Add a Playwright E2E smoke test after merge:
test('hilfe/transkription loads for authenticated user', async ({ page }) => { await page.goto('/hilfe/transkription'); await expect(page.getByRole('heading', { level: 1 })).toBeVisible(); }). This catches broken prerendered output in the full stack without duplicating the unit test layer.No blockers.
📋 Elicit — Requirements Engineer
Verdict: ✅ Approved
Acceptance criteria verification
The issue specified four acceptance criteria. All are addressed:
npm run buildexits 0/hilfe/transkriptionis prerendered to a static HTML fileprerender.entriesconfig fixgrep -r "prerender = true"returns only this filenpm run buildas a step in.gitea/workflows/ci.ymlScope discipline
The PR correctly implements Option A and nothing more. It does not:
PUBLIC_PATHSinhooks.server.ts(Option B — a separate product decision)export const prerender = truefrom+page.ts(Option C — undesirable)The open product question about whether
/hilfe/transkriptionshould be intentionally public (raised in the issue review) is correctly deferred. This PR is a P0 build fix, not a product decision. If the team decides to make help public, that warrants a separate issue with the "invite mom to transcribe" user story as the JTBD anchor.One note
The PR initially included all commits from the
feat/issue-483branch (20 changed files, +573 lines) before being corrected to a clean cherry-pick ontomain(PR #485 closed, PR #486 created). The final PR #486 is correctly scoped: 3 files, 8 additions, 3 deletions. Worth reviewing the branching workflow to prevent this in future — always branch frommainfor bugfix issues, not from in-progress feature branches.No blockers.
🎨 Leonie Voss — UI/UX Design Lead
Verdict: ✅ Approved
This PR is infrastructure and CI configuration only. No UI changes, no component modifications, no layout or styling impact.
What I verified
The rendered
/hilfe/transkriptionhelp page is unchanged. The fix only affects how SvelteKit discovers and builds the route — the output HTML for users is identical before and after this change.The removed comment in
+page.tsis not visible to users. Theprerender.entriesaddition insvelte.config.jsis not visible to users.One observation worth noting for future work
As the issue correctly identifies, prerendered static HTML loads without JavaScript and without a backend round-trip. For the senior audience (60+) on slower connections, this is a genuine performance and reliability benefit — the transcription guide page renders immediately from the static file, independent of API health. This is a UX benefit of the
prerender = truedeclaration that the fix correctly preserves.If the team later decides to make help public (Option B as a follow-up), I'd recommend adding a "Log in to start transcribing" CTA at the bottom of the help page — a call-to-action that converts readers into registered contributors. That's out of scope here.
No blockers. LGTM.