diff --git a/cypress/integration/middleware/standard.spec.ts b/cypress/integration/middleware/standard.spec.ts index 97387b7ca0..780f9f0c85 100644 --- a/cypress/integration/middleware/standard.spec.ts +++ b/cypress/integration/middleware/standard.spec.ts @@ -37,6 +37,14 @@ describe('Standard middleware', () => { cy.getCookie('netlifyCookie').should('have.property', 'value', 'true') }) }) + + // https://github.com/netlify/pillar-support/issues/350 + it('MiddlewareResponse adds cookies', () => { + cy.request('/cookies/middleware').then((response) => { + cy.getCookie('middlewareCookie').should('have.property', 'value', 'true') + expect(response.headers).to.have.property('x-foo', 'bar') + }) + }) }) describe('Middleware matchers', () => { diff --git a/demos/middleware/middleware.ts b/demos/middleware/middleware.ts index d0c7841997..5950c98671 100644 --- a/demos/middleware/middleware.ts +++ b/demos/middleware/middleware.ts @@ -53,6 +53,13 @@ export async function middleware(req: NextRequest) { return request.rewrite('/api/hello') } + if (pathname.startsWith('/cookies/middleware')) { + const response = await new MiddlewareRequest(req).next() + response.cookies.set('middlewareCookie', 'true') + response.headers.set('x-foo', 'bar') + return response + } + if (pathname.startsWith('/cookies')) { response = NextResponse.next() response.cookies.set('netlifyCookie', 'true') @@ -129,8 +136,8 @@ export const config = { matcher: [ '/api/:all*', '/headers', + '/cookies/:path*', { source: '/static' }, - { source: '/cookies' }, { source: '/matcher-cookie'}, { source: '/shows/((?!99|88).*)' }, { diff --git a/demos/middleware/pages/cookies/middleware.js b/demos/middleware/pages/cookies/middleware.js new file mode 100644 index 0000000000..15f61a1b36 --- /dev/null +++ b/demos/middleware/pages/cookies/middleware.js @@ -0,0 +1,9 @@ +const Cookies = () => { + return ( +
+

The cookie "middlewareCookie" should be set to true

+
+ ) +} + +export default Cookies diff --git a/packages/next/src/middleware/response.ts b/packages/next/src/middleware/response.ts index 392171bec0..2e3eb519b9 100644 --- a/packages/next/src/middleware/response.ts +++ b/packages/next/src/middleware/response.ts @@ -11,7 +11,15 @@ export class MiddlewareResponse extends NextResponse { private readonly dataTransforms: NextDataTransform[] private readonly elementHandlers: Array<[selector: string, handlers: ElementHandlers]> constructor(public originResponse: Response) { - super() + // we need to propagate the set-cookie header, so response.cookies.get works correctly + const initHeaders = new Headers() + if (originResponse.headers.has('set-cookie')) { + initHeaders.set('set-cookie', originResponse.headers.get('set-cookie')) + } + + super(undefined, { + headers: initHeaders, + }) // These are private in Node when compiling, but we access them in Deno at runtime Object.defineProperty(this, 'dataTransforms', { diff --git a/packages/runtime/src/templates/edge-shared/utils.ts b/packages/runtime/src/templates/edge-shared/utils.ts index c1e5004168..35dbd3bb5a 100644 --- a/packages/runtime/src/templates/edge-shared/utils.ts +++ b/packages/runtime/src/templates/edge-shared/utils.ts @@ -57,10 +57,15 @@ export const addMiddlewareHeaders = async ( return response } +interface ResponseCookies { + readonly _headers: Headers +} + interface MiddlewareResponse extends Response { originResponse: Response dataTransforms: NextDataTransform[] elementHandlers: Array<[selector: string, handlers: ElementHandlers]> + get cookies(): ResponseCookies } interface MiddlewareRequest { @@ -184,6 +189,12 @@ export const buildResponse = async ({ if (request.method === 'HEAD' || request.method === 'OPTIONS') { return response.originResponse } + + // NextResponse doesn't set cookies onto the originResponse, so we need to copy them over + if (response.cookies._headers.has('set-cookie')) { + response.originResponse.headers.set('set-cookie', response.cookies._headers.get('set-cookie')!) + } + // If it's JSON we don't need to use the rewriter, we can just parse it if (response.originResponse.headers.get('content-type')?.includes('application/json')) { const props = await response.originResponse.json()