2026-06-02infra

Worker CPU-time reductions (route preloading, DB pool teardown, image optimization)

  • Change: Three independent changes to keep the Cloudflare Worker idle between requests and stop tripping "Worker exceeded CPU time limit":
    1. Route preloading disabled. open-next.config.ts routePreloadingBehavior changed from "withWaitUntil" to "none". The previous setting ran nextServer.unstable_preloadEntries() (loads/compiles every route's server bundle) inside waitUntil on every request; that work counts against the invocation's CPU budget and blew the limit on cold isolates.
    2. Request-scoped DB pool now closed explicitly. lib/db.ts createDb now returns { db, pool }; the per-request (ctx-keyed) Hyperdrive pool registers after(() => pool.end()) so the Postgres socket is torn down once the response finishes streaming instead of lingering on the idle timeout. The globalThis fallback pool (dev/build/scripts) is unchanged.
    3. next/image optimization disabled. next.config.ts images switched from a custom loader (app/image-loader.ts/api/img Cloudflare Images transform) to unoptimized: true. <Image> now serves the original R2 bytes directly; the loader file and /api/img route remain on disk but are dormant.
  • Why: On-the-fly route entry preloading and per-<Image> resize/webp encoding both ran on the Worker CPU, and the DB pool never closed — together they produced intermittent CPU-limit errors during navigation. The goal is for the Worker to do only the requested page's work and then go idle.
  • Affected Modules: open-next.config.ts, lib/db.ts, next.config.ts (and dormant: app/image-loader.ts, app/api/img/route.ts)
  • Trade-offs:
    • Pro: Worker stays idle between requests; no per-request preload or image-transform CPU; Postgres connections are released promptly, easing the Hyperdrive 6-connection-per-invocation budget.
    • Con: No route warming (first hit of a cold route pays its own load cost); images are no longer resized/format-converted or width-tailored, so larger original bytes are sent to the browser and r2.dev serves them directly (revisit with a custom domain / Cloudflare image resizing at the edge if bandwidth becomes a concern).