diff --git a/.changeset/cold-pianos-check.md b/.changeset/cold-pianos-check.md new file mode 100644 index 000000000..1047a35ec --- /dev/null +++ b/.changeset/cold-pianos-check.md @@ -0,0 +1,5 @@ +--- +"@vercel/next": patch +--- + +[next] add .action handling for dynamic routes diff --git a/packages/next/src/utils.ts b/packages/next/src/utils.ts index c3e7c3ade..3e13930c2 100644 --- a/packages/next/src/utils.ts +++ b/packages/next/src/utils.ts @@ -432,6 +432,15 @@ export async function getDynamicRoutes({ dest: route.dest?.replace(/($|\?)/, '.rsc$1'), }); + routes.push({ + ...route, + src: route.src.replace( + new RegExp(escapeStringRegexp('(?:/)?$')), + '(?:\\.action)(?:/)?$' + ), + dest: route.dest?.replace(/($|\?)/, '.action$1'), + }); + routes.push(route); } diff --git a/packages/next/test/fixtures/00-app-dir-actions/app/[dynamic-static]/page.js b/packages/next/test/fixtures/00-app-dir-actions/app/[dynamic-static]/page.js new file mode 100644 index 000000000..b1ccbaa9a --- /dev/null +++ b/packages/next/test/fixtures/00-app-dir-actions/app/[dynamic-static]/page.js @@ -0,0 +1,17 @@ +import { revalidatePath } from 'next/cache' + +export const dynamic = 'force-static' + +export default async function Page() { + async function serverAction() { + 'use server'; + await new Promise((resolve) => setTimeout(resolve, 1000)); + revalidatePath('/dynamic'); + } + + return ( +
+ ); +} diff --git a/packages/next/test/fixtures/00-app-dir-actions/index.test.js b/packages/next/test/fixtures/00-app-dir-actions/index.test.js index f50da5551..1c8585f26 100644 --- a/packages/next/test/fixtures/00-app-dir-actions/index.test.js +++ b/packages/next/test/fixtures/00-app-dir-actions/index.test.js @@ -65,4 +65,25 @@ describe(`${__dirname.split(path.sep).pop()}`, () => { expect(res.headers.get('x-matched-path')).toBe('/other.action'); expect(res.headers.get('x-vercel-cache')).toBe('MISS'); }); + + it('should match the server action to the streaming prerender function (force-static dynamic segment)', async () => { + const data = await fetch( + `${ctx.deploymentUrl}/server-reference-manifest.json` + ).then(res => res.json()); + + const actionId = findActionId(data, 'app/dynamic-static/page'); + + const res = await fetch(`${ctx.deploymentUrl}/dynamic-static`, { + method: 'POST', + body: `------WebKitFormBoundary8xC9UKOVzHBaGYkR\r\nContent-Disposition: form-data; name=\"1_$ACTION_ID_${actionId}\"\r\n\r\n\r\n------WebKitFormBoundary8xC9UKOVzHBaGYkR\r\nContent-Disposition: form-data; name=\"0\"\r\n\r\n[\"$K1\"]\r\n------WebKitFormBoundary8xC9UKOVzHBaGYkR--\r\n`, + headers: { + 'Content-Type': + 'multipart/form-data; boundary=----WebKitFormBoundary8xC9UKOVzHBaGYkR', + }, + }); + + expect(res.status).toEqual(200); + expect(res.headers.get('x-matched-path')).toBe('/[dynamic-static].action'); + expect(res.headers.get('x-vercel-cache')).toBe('MISS'); + }); });