From b531eee3c624cd4112f935e204320da7cd07eaf5 Mon Sep 17 00:00:00 2001 From: Nathan Nguyen <146415969+NathanDrake2406@users.noreply.github.com> Date: Fri, 3 Jul 2026 20:33:56 +1000 Subject: [PATCH] perf(app-router): cache route-handler dispatch import Route handler requests currently await the generated RSC entry's dynamic import of the dispatch runtime on every request, even after the module has loaded. That keeps an import/promise boundary in the steady-state App Route path. Cache the resolved dispatch function after the first import and call it directly on later route-handler dispatches while keeping the dispatch runtime in its lazy chunk. This preserves page and server-action deferral and avoids the bundle-size regression from a static import. --- packages/vinext/src/entries/app-rsc-entry.ts | 12 +++++++++--- tests/entry-templates.test.ts | 8 ++++++-- 2 files changed, 15 insertions(+), 5 deletions(-) diff --git a/packages/vinext/src/entries/app-rsc-entry.ts b/packages/vinext/src/entries/app-rsc-entry.ts index bfcabea919..eaeb18a836 100644 --- a/packages/vinext/src/entries/app-rsc-entry.ts +++ b/packages/vinext/src/entries/app-rsc-entry.ts @@ -330,7 +330,13 @@ ${ } from ${JSON.stringify(appRouteHandlerResponsePath)};` : "" } -const __loadAppRouteHandlerDispatch = () => import(${JSON.stringify(appRouteHandlerDispatchPath)}); +let __dispatchAppRouteHandlerFn; +const __loadAppRouteHandlerDispatch = async () => { + if (__dispatchAppRouteHandlerFn) return __dispatchAppRouteHandlerFn; + const { dispatchAppRouteHandler } = await import(${JSON.stringify(appRouteHandlerDispatchPath)}); + __dispatchAppRouteHandlerFn = dispatchAppRouteHandler; + return __dispatchAppRouteHandlerFn; +}; ${ hasServerActions ? `const __loadAppServerActionExecution = () => import(${JSON.stringify(appServerActionExecutionPath)});` @@ -947,8 +953,8 @@ export default createAppRscHandler({ route, searchParams, }) { - const { dispatchAppRouteHandler: __dispatchAppRouteHandler } = - await __loadAppRouteHandlerDispatch(); + const __dispatchAppRouteHandler = + __dispatchAppRouteHandlerFn ?? (await __loadAppRouteHandlerDispatch()); return __dispatchAppRouteHandler({ basePath: __basePath, cleanPathname, diff --git a/tests/entry-templates.test.ts b/tests/entry-templates.test.ts index 3832411954..1e464b9b32 100644 --- a/tests/entry-templates.test.ts +++ b/tests/entry-templates.test.ts @@ -962,9 +962,13 @@ describe("App Router entry templates", () => { it("generateRscEntry defers route-handler and server-action runtimes", () => { const code = generateRscEntry("/tmp/test/app", minimalAppRoutes, null, [], null, "", false); - expect(code).toContain('const __loadAppRouteHandlerDispatch = () => import("'); + expect(code).toContain("let __dispatchAppRouteHandlerFn;"); + expect(code).toContain("const __loadAppRouteHandlerDispatch = async () => {"); + expect(code).toContain('const { dispatchAppRouteHandler } = await import("'); expect(code).toContain('const __loadAppServerActionExecution = () => import("'); - expect(code).toContain("await __loadAppRouteHandlerDispatch()"); + expect(code).toContain( + "__dispatchAppRouteHandlerFn ?? (await __loadAppRouteHandlerDispatch())", + ); expect(code).toContain("await __loadAppServerActionExecution()"); expect(code).not.toMatch(/import \{\s*dispatchAppRouteHandler as __dispatchAppRouteHandler,/); expect(code).not.toMatch(