fix: invalidate API route handler cache on file changes - #406
Conversation
The compiled API route handler cache was never cleared during invalidateProjectCaches(), causing stale handlers in proxy/production mode after file edits or deploys, and 404s for newly created API routes.
There was a problem hiding this comment.
Pull request overview
Fixes stale Pages Router API route behavior in proxy/production by ensuring cached APIRouteHandler instances are invalidated when project files change, so route discovery/initialization is re-run after edits/deploys.
Changes:
- Add
resetApiHandlerForProject(projectSlug)to selectively destroy cached API handlers for a given project slug. - Extend the
HandlerCacheinjection interface to support iterating cache entries. - Invoke API handler invalidation as part of
invalidateProjectCaches().
Reviewed changes
Copilot reviewed 3 out of 3 changed files in this pull request and generated 1 comment.
| File | Description |
|---|---|
| src/server/handlers/request/api/pages-api-handler.ts | Adds per-project API handler cache reset by iterating cache entries and destroying matching handlers. |
| src/server/handlers/request/api/index.ts | Re-exports the new resetApiHandlerForProject helper. |
| src/server/context/cache-invalidation.ts | Calls resetApiHandlerForProject(projectSlug) during per-project cache invalidation. |
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
| logger.debug("Clearing API route handler cache (per-project)", { projectSlug }); | ||
| await resetApiHandlerForProject(projectSlug); | ||
|
|
There was a problem hiding this comment.
invalidateProjectCaches() is invoked in a fire-and-forget way (e.g. from the HMR ReloadNotifier callback), so any rejection here becomes an unhandled promise rejection. Adding another awaited step (resetApiHandlerForProject) increases the surface area for that. Consider wrapping the per-project invalidation sequence in a top-level try/catch (logging and swallowing), or ensuring all call sites await/.catch() the returned promise so failures don't crash the process or get dropped silently.
There was a problem hiding this comment.
Addressed — wrapped the resetApiHandlerForProject call in a try/catch that logs and swallows (47c4970).
Tests slug suffix matching, exact key matching, multi-entry cleanup, no-match safety, and partial-suffix rejection. Also wraps the resetApiHandlerForProject call in try/catch per review feedback.
Summary
initialize()which never re-ranRoot Cause
invalidateProjectCaches()clears SSR caches, module caches, renderer caches, and snippet caches — but never destroys the cachedAPIRouteHandlerinstance. In local dev mode this works becauseinvalidateRuntimeHandler()callsresetApiHandler()directly, but in proxy/production mode the handler cache was never touched.Changes
pages-api-handler.ts: AddresetApiHandlerForProject(projectSlug)— iterates the handler cache and destroys entries matching the project slug (handles both${projectDir}:${projectSlug}proxy keys and plain slug keys). Addentries()toHandlerCacheinterface.cache-invalidation.ts: CallresetApiHandlerForProject(projectSlug)insideinvalidateProjectCaches()alongside existing cache clears.Test plan
tests/server/context/cache-invalidation.test.ts)tests/integration/core/api-handler.test.ts,tests/integration/adapters/cache-invalidation.test.ts)