Skip to content

Commit

Permalink
Do not invoke server actions twice when dynamicIO is enabled (#70784)
Browse files Browse the repository at this point in the history
In dev mode, when dynamic IO is enabled, we seed a prefetch cache scope
for non-prefetch requests (via #70408).

We must omit server action requests from this cache seeding though,
because it would lead to the action handler being called twice. This is
not intended in general for any action, and specificially fails in the
second invocation while decoding the request form data with `Error:
Unexpected end of JSON input` in `initializeModelChunk`, because the
request body was already consumed in the first invocation.
  • Loading branch information
unstubbable authored and kdy1 committed Oct 10, 2024
1 parent 9db4287 commit 7ca420e
Show file tree
Hide file tree
Showing 4 changed files with 41 additions and 1 deletion.
3 changes: 2 additions & 1 deletion packages/next/src/server/base-server.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3038,7 +3038,8 @@ export default abstract class Server<
if (
!cache &&
!isPrefetchRSCRequest &&
routeModule?.definition.kind === RouteKind.APP_PAGE
routeModule?.definition.kind === RouteKind.APP_PAGE &&
!isServerAction
) {
req.headers[RSC_HEADER] = '1'
req.headers[NEXT_ROUTER_PREFETCH_HEADER] = '1'
Expand Down
5 changes: 5 additions & 0 deletions test/e2e/app-dir/dynamic-io/app/server-action/action.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
'use server'

export async function action(value: string) {
return value
}
16 changes: 16 additions & 0 deletions test/e2e/app-dir/dynamic-io/app/server-action/page.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
'use client'

import { useActionState } from 'react'
import { action } from './action'

export default function Page() {
const [result, formAction] = useActionState(() => action('result'), 'initial')

return (
<form action={formAction}>
<h1>Server Action with Dynamic IO</h1>
<button>Submit</button>
<p>{result}</p>
</form>
)
}
18 changes: 18 additions & 0 deletions test/e2e/app-dir/dynamic-io/dynamic-io.server-action.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
import { nextTestSetup } from 'e2e-utils'
import { retry } from 'next-test-utils'

describe('dynamic-io', () => {
const { next } = nextTestSetup({
files: __dirname,
})

it('should not fail decoding server action arguments', async () => {
const browser = await next.browser('/server-action')
expect(await browser.elementByCss('p').text()).toBe('initial')
await browser.elementByCss('button').click()

await retry(async () => {
expect(await browser.elementByCss('p').text()).toBe('result')
})
})
})

0 comments on commit 7ca420e

Please sign in to comment.