next.js
87c2c526 - feat(after): allow request APIs in after (actions/handlers) (#73345)

Commit
1 year ago
feat(after): allow request APIs in after (actions/handlers) (#73345) This PR relaxes some of the limitations around calling request APIs (`headers()`, `cookies()`, `connection()`) inside `unstable_after` (which was previously blanket-forbidden since #71231). We're now allowing it in server actions and route handlers[1]: ```tsx // route.ts ✅ export function POST() { unstable_after(async () => console.log(await headers())); } ``` ```tsx // action.ts ✅ export function myAction() { "use server" unstable_after(async () => console.log(await headers())); } ``` however using it in server components remains banned: ```tsx // page.tsx ❌ export default function Page() { unstable_after(async () => console.log(await headers())); } ``` ### Implementation notes The way to detect actions and route handlers is to check if `workUnitStore.phase === "action"` [2]. But we can't use the existing `workUnitStore.phase` for this because it's deliberately mutable, so it'd have changed by the time we get to executing the callbacks and the actual `headers()` call. To solve this, I'm introducing a new ALS store - `afterTaskStore`. It currently only stores the phase that the (topmost) `after` was called in. But this info is also specific to each "task"/callback (or rather, task chain -- nested afters inherit the context of their parents), so we can't use any of the existing stores. I 'm also going to need task-specific info for upcoming devtools work (more useful callstacks, see https://github.com/vercel/next.js/blob/54465dbe782f505fd0105258d2d053a6439457ed/packages/next/src/server/app-render/after-task-async-storage.external.ts#L14), so i think introducing a new store is justified. --- [1] - Note that if it's in a prerendered GET handler (with `dynamic = 'error'`), `headers()` & co. will still error, but that's because they're dynamic, not because they were called within `after`! [2] - `actionAsyncStorage` is a no-go, because `actionStore.isAction` can remain true in the render triggered by an action (or in promises passed into it), which was the reason we introduced `phase` in the first place.
Author
Parents
Loading