diff --git a/.changeset/warm-tigers-knock.md b/.changeset/warm-tigers-knock.md new file mode 100644 index 000000000000..051d268a7755 --- /dev/null +++ b/.changeset/warm-tigers-knock.md @@ -0,0 +1,5 @@ +--- +'@astrojs/vercel': patch +--- + +Fixes edge middleware `next()` dropping the HTTP method and body when forwarding requests to the serverless function, which caused non-GET API routes (POST, PUT, PATCH, DELETE) to return 404 diff --git a/packages/integrations/vercel/src/serverless/middleware.ts b/packages/integrations/vercel/src/serverless/middleware.ts index 0f215ee84aa4..058d78296ba3 100644 --- a/packages/integrations/vercel/src/serverless/middleware.ts +++ b/packages/integrations/vercel/src/serverless/middleware.ts @@ -127,12 +127,14 @@ export default async function middleware(request, context) { const next = async () => { const { vercel, ...locals } = ctx.locals; const response = await fetch(new URL('/${NODE_PATH}', request.url), { + method: request.method, headers: { ...Object.fromEntries(request.headers.entries()), '${ASTRO_MIDDLEWARE_SECRET_HEADER}': '${middlewareSecret}', '${ASTRO_PATH_HEADER}': request.url.replace(origin, ''), '${ASTRO_LOCALS_HEADER}': trySerializeLocals(locals) - } + }, + ...(request.body ? { body: request.body, duplex: 'half' } : {}), }); return new Response(response.body, { status: response.status, diff --git a/packages/integrations/vercel/test/edge-middleware.test.js b/packages/integrations/vercel/test/edge-middleware.test.js index d6313d483ab6..89c2a3c3f472 100644 --- a/packages/integrations/vercel/test/edge-middleware.test.js +++ b/packages/integrations/vercel/test/edge-middleware.test.js @@ -42,6 +42,33 @@ describe('Vercel edge middleware', () => { assert.ok((await response.text()).length, 'Body is included'); }); + it('edge middleware forwards HTTP method and body', async () => { + const entry = new URL( + '../.vercel/output/functions/_middleware.func/middleware.mjs', + build.config.outDir, + ); + const module = await import(entry); + + const originalFetch = globalThis.fetch; + let captured; + globalThis.fetch = async (_url, opts) => { + captured = opts; + return new Response('ok', { status: 200 }); + }; + try { + const request = new Request('http://example.com/api/test', { + method: 'POST', + body: '{"data":"test"}', + headers: { 'Content-Type': 'application/json' }, + }); + await module.default(request, {}); + assert.equal(captured.method, 'POST', 'forwards the HTTP method'); + assert.ok(captured.body, 'forwards the request body'); + } finally { + globalThis.fetch = originalFetch; + } + }); + // TODO: The path here seems to be inconsistent? it.skip('with edge handle file, should successfully build the middleware', async () => { const fixture = await loadFixture({