From 530308033b0f604b05c421e260c1eeef8cf1c3fe Mon Sep 17 00:00:00 2001 From: Alexander Bocken Date: Sun, 24 May 2026 15:41:39 +0200 Subject: [PATCH] fix(build): disable prerender crawl so build stops OOMing `/hikes` is `prerender = true` and carries the global nav, so the prerender crawler followed those links and tried to statically render the whole dynamic, DB-/ML-backed app. SvelteKit prerenders inside a heap-capped worker_threads worker, so this exhausted its heap (ERR_WORKER_OUT_OF_MEMORY) and failed the build. - svelte.config.js: prerender.crawl = false. The intended static set is fully described by `prerender = true` (/hikes) + the /errors/[status] EntryGenerator, so crawling is unneeded. Add a defensive handleHttpError that ignores /hikes/*/images/* 404s (those binaries live in hikes-assets/, served by nginx/dev-middleware, not /static). - hooks.server.ts: skip init when `building` so builds don't connect to Mongo, start the payment scheduler, or warm the romcal cache. - hikes/[slug]: set `prerender = false`, enforcing the intent its comment already stated. Version 1.86.1 -> 1.86.2. --- package.json | 2 +- src/hooks.server.ts | 10 ++++++++++ src/routes/hikes/[slug]/+page.ts | 6 +++++- svelte.config.js | 19 +++++++++++++++++++ 4 files changed, 35 insertions(+), 2 deletions(-) diff --git a/package.json b/package.json index fbdc00ca..eca6ee40 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "homepage", - "version": "1.86.1", + "version": "1.86.2", "private": true, "type": "module", "scripts": { diff --git a/src/hooks.server.ts b/src/hooks.server.ts index ea3b05c3..27867d82 100644 --- a/src/hooks.server.ts +++ b/src/hooks.server.ts @@ -1,6 +1,7 @@ import type { Handle, HandleServerError, ServerInit } from "@sveltejs/kit" import { redirect } from "@sveltejs/kit" import { sequence } from "@sveltejs/kit/hooks" +import { building } from "$app/environment" import * as auth from "./auth" import { initializeScheduler } from "./lib/server/scheduler" import { dbConnect } from "./utils/db" @@ -122,6 +123,15 @@ async function timing({ event, resolve }: Parameters[0]) { } export const init: ServerInit = async () => { + // SvelteKit runs prerendering/analysis inside a worker_threads worker (see + // @sveltejs/kit utils/fork.js) whose JS heap is capped well below the main + // thread's. `init` fires there too, so warming the romcal cache during a + // build exhausts that worker's heap → ERR_WORKER_OUT_OF_MEMORY and a failed + // build. None of it is needed at build time: no prerendered route touches the + // DB, and connecting to Mongo / starting the payment scheduler from a build + // is undesirable regardless. Skip startup work while building. + if (building) return; + console.log('🚀 Server starting - initializing database connection...'); try { await dbConnect(); diff --git a/src/routes/hikes/[slug]/+page.ts b/src/routes/hikes/[slug]/+page.ts index 3653fc2b..9d0ef368 100644 --- a/src/routes/hikes/[slug]/+page.ts +++ b/src/routes/hikes/[slug]/+page.ts @@ -4,7 +4,11 @@ import type { PageLoad } from './$types'; // Not prerendered: the page needs the live session so private images can be // gated behind login. Performance hit is small — the page is mostly hashed -// static assets (track JSON, image variants). +// static assets (track JSON, image variants). Without this flag the prerender +// crawler still followed the overview's hike links and rendered every detail +// page at build time, which exhausted the prerender worker's heap +// (ERR_WORKER_OUT_OF_MEMORY); `false` keeps the crawler from prerendering them. +export const prerender = false; // Glob the .svx modules so Vite can pre-bundle them and we can resolve // the matching one synchronously at load time. diff --git a/svelte.config.js b/svelte.config.js index 3a895ad3..4cd10587 100644 --- a/svelte.config.js +++ b/svelte.config.js @@ -21,6 +21,25 @@ const config = { adapter: adapter({ precompress: true // Enable brotli and gzip compression }), + prerender: { + // The only intentionally-static pages are /hikes (prerender=true) and + // the /errors/[status] set (via that route's EntryGenerator). With the + // crawler on, it follows the global nav out of /hikes and tries to + // prerender the whole dynamic, DB-/ML-backed app — which runs the + // forked prerender worker out of heap (ERR_WORKER_OUT_OF_MEMORY) and + // fails the build. Disable crawling: the prerendered set is then driven + // entirely by `prerender = true` + EntryGenerator. + crawl: false, + handleHttpError: ({ path, message }) => { + // Defensive: hike image binaries live in `hikes-assets/`, outside + // `/static` (nginx serves them in prod, a Vite middleware in dev — + // see vite.config.ts), so the crawler can't fetch them. Harmless + // while crawl is off, but keeps a 404 from failing the build if + // crawling is ever re-enabled. + if (/^\/hikes\/[^/]+\/images\//.test(path)) return; + throw new Error(message); + } + }, alias: { $models: 'src/models', $utils: 'src/utils',