diff --git a/packages/nuxt/src/app/composables/cookie.ts b/packages/nuxt/src/app/composables/cookie.ts index a05773cd79..3e3a5dfd10 100644 --- a/packages/nuxt/src/app/composables/cookie.ts +++ b/packages/nuxt/src/app/composables/cookie.ts @@ -2,7 +2,7 @@ import type { Ref } from 'vue' import { ref, watch } from 'vue' import type { CookieParseOptions, CookieSerializeOptions } from 'cookie-es' import { parse, serialize } from 'cookie-es' -import { appendResponseHeader } from 'h3' +import { deleteCookie, getCookie, setCookie } from 'h3' import type { H3Event } from 'h3' import destr from 'destr' import { isEqual } from 'ohash' @@ -80,7 +80,16 @@ function writeClientCookie (name: string, value: any, opts: CookieSerializeOptio function writeServerCookie (event: H3Event, name: string, value: any, opts: CookieSerializeOptions = {}) { if (event) { - // TODO: Try to smart join with existing Set-Cookie headers - appendResponseHeader(event, 'Set-Cookie', serializeCookie(name, value, opts)) + // update if value is set + if (value !== null && value !== undefined) { + return setCookie(event, name, value, opts) + } + + // delete if cookie exists in browser and value is null/undefined + if (getCookie(event, name) !== undefined) { + return deleteCookie(event, name, opts) + } + + // else ignore if cookie doesn't exist in browser and value is null/undefined } } diff --git a/test/basic.test.ts b/test/basic.test.ts index c44f5569d0..656d3ab5cc 100644 --- a/test/basic.test.ts +++ b/test/basic.test.ts @@ -399,6 +399,21 @@ describe('nuxt composables', () => { const html = await $fetch('/url') expect(html).toContain('path: /url') }) + it('sets cookies correctly', async () => { + const res = await fetch('/cookies', { + headers: { + cookie: Object.entries({ + 'browser-accessed-but-not-used': 'provided-by-browser', + 'browser-accessed-with-default-value': 'provided-by-browser', + 'browser-set': 'provided-by-browser', + 'browser-set-to-null': 'provided-by-browser', + 'browser-set-to-null-with-default': 'provided-by-browser' + }).map(([key, value]) => `${key}=${value}`).join('; ') + } + }) + const cookies = res.headers.get('set-cookie') + expect(cookies).toMatchInlineSnapshot('"set-in-plugin=true; Path=/, set=set; Path=/, browser-set=set; Path=/, browser-set-to-null=; Max-Age=0; Path=/, browser-set-to-null-with-default=; Max-Age=0; Path=/"') + }) }) describe('rich payloads', () => { diff --git a/test/fixtures/basic/pages/cookies.vue b/test/fixtures/basic/pages/cookies.vue new file mode 100644 index 0000000000..45c67460df --- /dev/null +++ b/test/fixtures/basic/pages/cookies.vue @@ -0,0 +1,19 @@ + + +