mirror of
https://github.com/hanzoai/next.js.git
synced 2026-10-30 14:46:38 +00:00
Fixes a bug where Route Handlers and Server Actions were prevented from
calling `headers()` (and other request APIs) if
- the call happened after the response is finished
- the call *did not happen* inside an after-callback
generally, this happens when you pass a promise to after.
```ts
const longRunning = () => {
// assume this resolves after the response
await new Promise((resolve) => setTimeout(resolve, 100))
// response is done
after(() => {
await headers() // ❌ should be allowed, but throws
})
}
after(longRunning()) // or waitUntil, doesn't matter
```
The logic we used in the check was incorrect and assumed that an
`afterTaskStore` was present, but we can only wrap callbacks in this ALS
- we cannot change the async context of already-running promises.
One noteworthy change is that i had to exit `actionAsyncStorage` when we
do a render after the server action. Otherwise the checks in
`isRequestApiAllowedInCurrentPhase` might believe that they're still in
a server action when they aren't.
39 lines
1.0 KiB
TypeScript
39 lines
1.0 KiB
TypeScript
import { cookies } from 'next/headers'
|
|
import { testApiInPromisePassedToAfter, REQUEST_API_NAMES } from '../common'
|
|
import { Suspense } from 'react'
|
|
|
|
const COOKIE = 'api-name'
|
|
|
|
async function action(apiName: string) {
|
|
'use server'
|
|
const cookieStore = await cookies()
|
|
// The page only runs the test when the cookie is set,
|
|
// so it won't run it on initial load, only when
|
|
// we're rerendering after an action.
|
|
cookieStore.set(COOKIE, apiName)
|
|
}
|
|
|
|
export default async function Page() {
|
|
return (
|
|
<main>
|
|
<Suspense>
|
|
<TestAfterIfCookieSet />
|
|
</Suspense>
|
|
{REQUEST_API_NAMES.map((apiName) => (
|
|
<form data-api-name={apiName} action={action.bind(null, apiName)}>
|
|
<button type="submit">Submit - {apiName}</button>
|
|
</form>
|
|
))}
|
|
</main>
|
|
)
|
|
}
|
|
|
|
async function TestAfterIfCookieSet() {
|
|
const cookieStore = await cookies()
|
|
const apiName = cookieStore.get(COOKIE)?.value
|
|
if (apiName !== undefined) {
|
|
testApiInPromisePassedToAfter('render after action', apiName)
|
|
}
|
|
return null
|
|
}
|