From f2a6a556e8bad5748d6e49cf9ca51a265c3b863b Mon Sep 17 00:00:00 2001 From: Rhys Sullivan <39114868+RhysSullivan@users.noreply.github.com> Date: Tue, 18 Aug 2026 16:19:05 -0700 Subject: [PATCH] Time Start module evaluation separately from first-request app init A cold isolate pays both: evaluating the 2.56MB module graph and building the app's Effect layers for the first time. Those have different fixes. Import the same virtual ids loadEntries uses so its cache finds the module already evaluated, leaving handlerMs to cover only the rest. --- apps/cloud/src/server.ts | 17 +++++++++++++++++ apps/cloud/src/start-virtual-entries.d.ts | 9 +++++++++ 2 files changed, 26 insertions(+) create mode 100644 apps/cloud/src/start-virtual-entries.d.ts diff --git a/apps/cloud/src/server.ts b/apps/cloud/src/server.ts index b8d4186a1..99333d0ea 100644 --- a/apps/cloud/src/server.ts +++ b/apps/cloud/src/server.ts @@ -134,6 +134,22 @@ const fetchHandler = async ( // 0ms for work that actually took seconds — which is exactly what the // first run of this probe showed (handlerMs 0 against 6002ms wall). await scheduler.wait(0); + + // "First Start request in this isolate" does TWO things: it evaluates the + // 2.56MB module graph, and it builds the app's Effect layers (DB, WorkOS) + // for the first time. Those have different fixes, so time them apart. + // Importing the same virtual ids `loadEntries` uses means its cache finds + // the module already evaluated, so `handlerMs` below excludes the load. + const moduleStartedAt = Date.now(); + // oxlint-disable-next-line executor/no-try-catch-or-throw -- temporary diagnostic: a probe failure must not affect the request + try { + await Promise.all([import("#tanstack-router-entry"), import("#tanstack-start-entry")]); + } catch { + // ignored — the timing is the signal + } + await scheduler.wait(0); + const moduleMs = Date.now() - moduleStartedAt; + const startedAt = Date.now(); const response = await rawFetchHandler(request, env, ctx); await scheduler.wait(0); @@ -143,6 +159,7 @@ const fetchHandler = async ( probe: "start-graph", path: new URL(request.url).pathname, wasWarm, + moduleMs, handlerMs, }), ); diff --git a/apps/cloud/src/start-virtual-entries.d.ts b/apps/cloud/src/start-virtual-entries.d.ts new file mode 100644 index 000000000..c76881feb --- /dev/null +++ b/apps/cloud/src/start-virtual-entries.d.ts @@ -0,0 +1,9 @@ +// TanStack Start's internal virtual server-entry modules (registered by the +// Start vite plugin; the same ids `start-server-core`'s `loadEntries` +// imports). server.ts imports them to time module evaluation separately from +// first-request app initialization — only the evaluation side effect matters, +// so the value shape is left untyped. Kept in a standalone declaration file: +// shorthand ambient modules only register from a non-module file +// (env-augment.d.ts is a module). +declare module "#tanstack-router-entry"; +declare module "#tanstack-start-entry";