From 5f561f8cfa4720801a5cf4598f193ab34539abb9 Mon Sep 17 00:00:00 2001 From: Zack Tanner <1939140+ztanner@users.noreply.github.com> Date: Thu, 23 May 2024 12:06:25 -0700 Subject: [PATCH] [next]: ensure user rewrites match to action outputs (#11628) The builder normalizes user rewrites that target pages that have special outputs (`.rsc`, `.prefetch.rsc`). When we added support for `.action` outputs, we need to perform this same normalization to ensure that user rewrites still match. If the rewrite was a greedy match (eg `/:path*`) it'd be ok, but more specific rewrites would have the issue. --- .changeset/smart-horses-rescue.md | 5 +++++ packages/next/src/server-build.ts | 12 ++++++++++- .../index.test.js | 20 +++++++++++++++++++ .../next.config.js | 15 +++++++++++++- 4 files changed, 50 insertions(+), 2 deletions(-) create mode 100644 .changeset/smart-horses-rescue.md diff --git a/.changeset/smart-horses-rescue.md b/.changeset/smart-horses-rescue.md new file mode 100644 index 000000000..481e9410e --- /dev/null +++ b/.changeset/smart-horses-rescue.md @@ -0,0 +1,5 @@ +--- +'@vercel/next': patch +--- + +Ensure user rewrites still match to action outputs diff --git a/packages/next/src/server-build.ts b/packages/next/src/server-build.ts index 5bdc15784..a41d5580c 100644 --- a/packages/next/src/server-build.ts +++ b/packages/next/src/server-build.ts @@ -252,9 +252,19 @@ export async function serverBuild({ for (const rewrite of afterFilesRewrites) { if (rewrite.src && rewrite.dest) { + // ensures that userland rewrites are still correctly matched to their special outputs + // PPR should match .prefetch.rsc, .rsc, and .action + // non-PPR should match .rsc and .action + // we only add `.action` handling to the regex if flagged on in the build + const rscSuffix = isAppPPREnabled + ? `(\\.prefetch)?\\.rsc${hasActionOutputSupport ? '|\\.action' : ''}` + : hasActionOutputSupport + ? '(\\.action|\\.rsc)' + : '\\.rsc'; + rewrite.src = rewrite.src.replace( /\/?\(\?:\/\)\?/, - `(?${isAppPPREnabled ? '(\\.prefetch)?' : ''}\\.rsc)?(?:/)?` + `(?${rscSuffix})?(?:/)?` ); let destQueryIndex = rewrite.dest.indexOf('?'); diff --git a/packages/next/test/fixtures/00-app-dir-actions-experimental-streaming/index.test.js b/packages/next/test/fixtures/00-app-dir-actions-experimental-streaming/index.test.js index 43c045386..494ebe14f 100644 --- a/packages/next/test/fixtures/00-app-dir-actions-experimental-streaming/index.test.js +++ b/packages/next/test/fixtures/00-app-dir-actions-experimental-streaming/index.test.js @@ -273,6 +273,26 @@ describe(`${__dirname.split(path.sep).pop()}`, () => { expect(body).toContain(JSON.stringify(['id', '1', 'd'])); expect(body).not.toContain(JSON.stringify(['id', '1.action', 'd'])); }); + + it('should work when a rewrite targets an action', async () => { + const targetPath = `${basePath}/rsc/static`; + const canonicalPath = `/rewrite/${basePath}/rsc/static`; + const actionId = findActionId(targetPath, runtime); + + const res = await fetch( + `${ctx.deploymentUrl}${canonicalPath}`, + generateFormDataPayload(actionId) + ); + + expect(res.status).toEqual(200); + expect(res.headers.get('x-matched-path')).toBe(targetPath + '.action'); + expect(res.headers.get('content-type')).toBe('text/x-component'); + if (runtime === 'node') { + expect(res.headers.get('x-vercel-cache')).toBe('MISS'); + } else { + expect(res.headers.get('x-edge-runtime')).toBe('1'); + } + }); }); describe('pages', () => { diff --git a/packages/next/test/fixtures/00-app-dir-actions-experimental-streaming/next.config.js b/packages/next/test/fixtures/00-app-dir-actions-experimental-streaming/next.config.js index f053ebf79..02977f3ac 100644 --- a/packages/next/test/fixtures/00-app-dir-actions-experimental-streaming/next.config.js +++ b/packages/next/test/fixtures/00-app-dir-actions-experimental-streaming/next.config.js @@ -1 +1,14 @@ -module.exports = {}; +module.exports = { + rewrites() { + return [ + { + source: '/rewrite/rsc/static', + destination: '/rsc/static', + }, + { + source: '/rewrite/edge/rsc/static', + destination: '/edge/rsc/static', + }, + ]; + }, +};