fix(build): unbreak production build — /hilfe/transkription prerender unreachable behind /login #472
Reference in New Issue
Block a user
Delete Branch "%!s()"
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?
Context
Pre-prod audit ran
npm run buildinside the runningarchive-frontendcontainer. The build itself succeeded (162 chunks emitted), but the prerender step fails:frontend/src/routes/hilfe/transkription/+page.ts:3declaresexport const prerender = true. The route is gated by the global auth handler inhooks.server.ts(every non-public path redirects to/login), so the prerender crawler reaches/loginand never finds the help page. The build exits non-zero.Effect: today's
npm run buildfails by default. CI may currently run only tests + lint (audit confirmed.gitea/workflows/ci.ymlhas no build step), so this hasn't broken green merges, but the moment we addnpm run buildto CI (planned in the new container-images /devops(ci)issue), every PR fails until this is fixed.Approach
Two reasonable fixes — pick one:
Option A — explicit prerender entries (recommended)
Tell the prerenderer about the route directly so it doesn't have to crawl into it:
This is the documented escape hatch for routes that crawling can't reach.
Option B — make the help route public
Add
/hilfe/transkription(and any future help routes) to thePUBLIC_PATHSlist inhooks.server.ts:8:Pro: lets users read the transcription guide without logging in (arguably desirable — part of the inviting-mom-to-transcribe story).
Con: scope creep into product behavior; dive into whether help should be public is a separate conversation.
Recommend Option A for this fix. Do Option B as a follow-up if the team wants public help.
Option C — remove
prerender = trueIf prerender is gone, the route gets SSR-rendered per request. Cheap operationally but loses the "ship as a static HTML file" benefit. Not recommended for what is intentionally a static help page.
Critical files
frontend/svelte.config.js— addkit.prerender.entriesVerification
cd frontend && docker exec archive-frontend npm run build(or run with the container user) — exit 0..svelte-kit/output/prerendered/pages/hilfe/transkription.htmlexists.npm run previewandcurl -I http://localhost:4173/hilfe/transkriptionreturns the prerendered HTML (or, on Option B, returns 200 without auth).Acceptance criteria
npm run buildexits 0./hilfe/transkriptionis prerendered to a static HTML file.npm run buildas a step in.gitea/workflows/ci.ymlto prevent regression.Effort
XS — 5 minutes for Option A. The CI step is a one-liner in the new devops(ci) issue.
Risk if not addressed
CI will fail the moment we add
npm run build(or container build) to it. Production image build also fails until this is resolved. P0 because it currently blocks the prod-image work in #135 and the new CI gates issue.Tracked in audit doc as F-35 (Medium → P0 via blocking dependency on #135 / CI gates).
🏗️ Markus Keller — Application Architect
Observations
prerender = truein+page.tstells SvelteKit to ship this route as a static file, but the defaultkit.prerender.crawlcan only discover routes reachable through followed links. The auth wall inhooks.server.tsintercepts the crawler before it reaches/hilfe/transkription, so the entry is never produced.entries) is the right architectural answer for this case — it expresses intent in config without touching auth semantics.+page.tsline 1 —// Safe: handleAuth in hooks.server.ts redirects unauthenticated requests before prerendered HTML is visible— is actually misleading. Prerendered HTML is a static file served before any server hook runs. The redirect only fires when the Node adapter handles an SSR request, not when the CDN/Caddy serves the static file directly. This is worth correcting in the code comment, but does not change the recommended fix./hilfe/transkriptionis currently the only prerendered route. The fix insvelte.config.jsis XS scope..gitea/workflows/ci.yml) currently has nonpm run buildstep, which is why this hasn't surfaced as a build break. Adding it after this fix is the correct follow-through — and it should be a prerequisite gate before the frontend unit-test job, not a separate optional job.Recommendations
prerender: { entries: ['/hilfe/transkription'] }tokitinsvelte.config.js. This is a one-line config change with no side-effects on auth or routing behavior.+page.ts. Replace the existing comment with something accurate:// Static prerendered page — no server-side auth runs against the static file. Access control for the live route is handled via hooks.server.ts for SSR fallback.Or simplest: remove the comment entirely since the prerender flag is self-documenting.npm run buildto CI in the same PR. The issue suggests tracking the CI step in a separate devops issue, but the build gate is the whole point of this fix — ship both together. A passing build that doesn't gate CI provides no regression protection.Open Decisions
👨💻 Felix Brandt — Fullstack Developer
Observations
frontend/svelte.config.js(addprerender.entries), optionally+page.ts(fix the misleading comment), and.gitea/workflows/ci.yml(add the build step). No backend changes, no type regeneration, no Flyway migrations.+page.tscomment// Safe: handleAuth in hooks.server.ts redirects unauthenticated requests before prerendered HTML is visibleis factually wrong. Prerendered HTML is served as a static file —hooks.server.tsonly runs for SSR requests, not for static file serving. The comment gives false confidence. It should be removed or replaced.page.svelte.spec.tshas good coverage of the rendered UI. It does not (and should not) test the prerender flag — that is a build-time concern, not a component concern.npm run buildexiting 0 — which is exactly what the CI gate provides..svelte-kit/output/prerendered/pages/hilfe/transkription.html. This is a good sanity check during local development but not a test that can live in the suite.Recommendations
svelte.config.jschange in one commit, the CI step in a second. They are two logical changes: one fixes the bug, the other prevents regression. Atomic commits, one logical change each.+page.ts, do not replace it. Theprerender = trueexport is self-documenting SvelteKit convention. A wrong comment is worse than no comment.cd frontend && npm run buildand confirm.svelte-kit/output/prerendered/pages/hilfe/transkription.htmlexists. This takes 10 seconds and eliminates any doubt.export const ssr = falseor any other flag to+page.ts. The route is already correctly configured — onlysvelte.config.jsis missing theentriesdeclaration.🚀 Tobias Wendt — DevOps & Platform Engineer
Observations
.gitea/workflows/ci.ymlcurrently runs three jobs:unit-tests(frontend Vitest + Playwright container),ocr-tests(Python), andbackend-unit-tests(Maven). There is nonpm run buildstep anywhere. The issue is correct: this gap has been masked because the build is never exercised in CI.actions/cache@v4setup fornode_moduleskeyed topackage-lock.json. Adding a build step to the existingunit-testsjob is a one-line addition after the test run — no new job needed for XS scope.npm run buildinside the runningarchive-frontendcontainer in pre-prod. That's an auditing approach, not a CI approach. For CI, the build runs in the Playwright container that already installs dependencies.adapter-nodeis already configured — sonpm run buildproduces a Node.js server bundle, not a static site. The prerendered routes are embedded in.svelte-kit/output/prerendered/and bundled into the final artifact. The build failing non-zero is a reliable CI gate.docker buildforarchive-frontend) would also fail until this is fixed, since the Dockerfile presumably runsnpm run buildinside the container image. Blocking#135(container-images issue) is real.Recommendations
Add
npm run buildto the existingunit-testsjob after the test run. Do not create a separate CI job — the dependency graph is already set up (same runner, same cachednode_modules), and a separate job adds queue time with no benefit at this scale.Do not add a build artifact upload step. The build output is transient — its only purpose here is to verify the build exits 0 and prerendering completes. Uploading the artifact adds storage cost and CI time with no downstream consumer (the actual container image build uses a separate Dockerfile).
Paraglide compilation must precede the build step, and it already does in the current workflow (
npx @inlang/paraglide-js compileruns before tests). The build step fits naturally after the test step without reordering.Track this as a prerequisite for
#135— add ablocked-by: #472link to the container-images issue so the dependency is visible in the backlog.After merging, verify the Gitea runner picks up the new step cleanly. The NAS runner runs Docker 24.x (the workflow already has
DOCKER_API_VERSION: "1.43"workaround in the backend job — the frontend job uses a different container image and does not need this).📋 Elicit — Requirements Engineer
Observations
+page.tscomment says// Safe: handleAuth in hooks.server.ts redirects unauthenticated requests before prerendered HTML is visible. This implies a product decision: the transcription help page is currently intended to be auth-gated. If prerendered HTML is served as a static file (which it is withadapter-node+ a Caddy proxy serving static assets directly), an unauthenticated user who knows the URL may be able to fetch the file depending on how Caddy is configured. The issue does not surface this as a risk.grep -r "prerender = true"returns onlyfrontend/src/routes/hilfe/transkription/+page.ts.Recommendations
adapter-nodeis used and Caddy serves.svelte-kit/output/prerendered/as static files, server hooks do not run against those files. This is the root cause and it should be stated explicitly so the next developer understands whyprerender.entriesis needed even when the page is behind auth in the app.Open Decisions
/hilfe/transkriptionintended to be accessible without authentication in production? If Caddy serves the prerendered static file directly (bypassing the Node server), it is currently publicly accessible regardless of the auth hook. This is a product decision: intentional (good UX) or unintentional (should be auth-gated at Caddy level)? Needs an explicit answer before shipping.🔒 Nora "NullX" Steiner — Security Engineer
Observations
prerender.entriesonly affects the build process, not runtime auth behavior.+page.tsis a security documentation failure. It assertshandleAuth in hooks.server.ts redirects unauthenticated requests before prerendered HTML is visible— but this is only true when the Node.js server handles the request. If Caddy (the reverse proxy) is configured to serve.svelte-kit/output/prerendered/as static files directly (which is the production pattern described in the DevOps persona's canonical stack), thenhooks.server.tsnever runs and the page is publicly accessible regardless of the auth gate. This is not inherently a vulnerability — the page contains only transcription guidelines, no personal data — but the comment creates a false belief about the security model that could be cargo-culted to a future route containing sensitive content.PUBLIC_PATHSlist inhooks.server.tscorrectly excludes/hilfe/transkription. This means SSR requests (when the static file is not cached/served by Caddy) will be redirected to login. The behavior is consistent with the existing auth model for Option A./hilfetoPUBLIC_PATHS), that would be a deliberate auth relaxation. It is a minor security posture change with appropriate product justification, but it should be documented explicitly — not buried in a fix PR.Recommendations
+page.ts. Replace it with:// Prerendered to a static HTML file — the Node.js auth hook does NOT run when this file is served directly by the reverse proxy. This page intentionally contains no sensitive data.This documents the actual security model and sets a safe precedent for future prerendered routes.prerender = true./hilfe/transkriptiontoPUBLIC_PATHSin this fix. Option A is safer because it does not touch the auth configuration — the behavioral change to the SSR path is zero.🧪 Sara Holt — QA Engineer
Observations
frontend/src/routes/hilfe/transkription/page.svelte.spec.tshas solid component-level coverage: 8 tests covering heading, intro paragraph, external link security attributes (includingrel,referrerpolicy, andaria-label), section headings, rule cards, and clarification chips. These tests pass today and will continue to pass after the fix — the fix does not change rendered output..svelte-kit/output/prerendered/pages/hilfe/transkription.html). This is correct — that is a CI gate responsibility, not a unit test responsibility.npm run buildstep. This is the missing quality gate. Once added, any futureprerender = trueroute that becomes unreachable will fail the build in CI, not just locally.hooks.server.tsPUBLIC_PATHSlist. There is no automated test that verifies/hilfe/transkriptionredirects to/loginfor unauthenticated users, or that it does not. This is a gap worth noting for the future — not a blocker for this fix, but relevant if Option B is ever pursued.curlreturns 200 (local or E2E)Recommendations
test('hilfe/transkription loads without error for authenticated user', async ({ page }) => { await page.goto('/hilfe/transkription'); await expect(page.getByRole('heading', { level: 1 })).toBeVisible(); }). This is a lightweight smoke test that catches broken prerendered output in the full stack.GET /hilfe/transkriptionreturns 200 for authenticated users and 302 to/loginfor unauthenticated users (via the SSR path) would complete the security posture verification. It can live in the E2E suite. Low priority for this P0 fix, but worth a separate issue.npm testlocally before pushing to confirm the CI will be clean.🎨 Leonie Voss — UI/UX Design Lead
Observations
/hilfe/transkriptionpage is not changing.rel="noopener noreferrer",referrerpolicy="no-referrer", and anaria-labelcontaining "öffnet in neuem Tab" — these are correct accessibility and security practices that are already in place.Recommendations
docs/specs/transkriptions-richtlinien-spec.htmllikely has the design intent documented — check it to see if public access was ever part of the design. If so, add/hilfetoPUBLIC_PATHSin a separate PR with explicit product sign-off.Decision Queue
Two open decisions were raised across the review. Both are genuine tradeoffs requiring human judgment.
Theme 1 — CI build step scope (raised by Markus / @mkeller)
Question: Should
npm run buildbe added to.gitea/workflows/ci.ymlin this PR, or tracked in the separate devops(ci) issue?Why it matters: Without the CI gate, the config fix passes locally but provides no regression protection. A future
prerender = trueroute that becomes unreachable behind auth would fail silently again until someone runs the build manually.Options:
Recommendation from review: Include it. The CI step is integral to the fix, not an enhancement.
Theme 2 — Public accessibility of
/hilfe/transkriptionin production (raised by Elicit and Nora)Question: Is the transcription help page intended to be accessible without authentication in production?
Why it matters: With
adapter-nodeand Caddy configured to serve.svelte-kit/output/prerendered/as static files directly,hooks.server.tsdoes not run against those files — the page is publicly reachable regardless ofPUBLIC_PATHS. The existing comment in+page.tsasserts the opposite. This is either:This does not block the P0 fix (Option A is safe either way). But the answer determines whether:
+page.tsshould say "intentionally public" or "should be gated at proxy level."Recommendation from review: Decide and document the intent before or alongside this PR. The comment fix is cheap; the architectural clarity is valuable.
✅ Implemented — PR #485
Implemented Option A as specified. Three changes across two commits:
Commit 1 —
fix(build): add prerender entry for /hilfe/transkriptionfrontend/svelte.config.js: Addedprerender: { entries: ['/hilfe/transkription'] }tokitconfig — tells SvelteKit to render the route directly without needing to crawl to it through linksfrontend/src/routes/hilfe/transkription/+page.ts: Removed the misleading comment that claimedhooks.server.tsguards prerendered static files (it does not — the auth hook only runs for SSR requests)Commit 2 —
ci: add npm run build step to unit-tests job.gitea/workflows/ci.yml: Addednpm run buildafter the test step in theunit-testsjob — the build gate is the regression protection for this fixAll 1835 frontend unit tests pass. CI will validate the actual build exit code on the PR run.