Skip to content

Commit

Permalink
fix: Make locale cookie a session cookie (#1634)
Browse files Browse the repository at this point in the history
Initially, I planned to add an expiration of 5 hours to the locale
cookie for `next-intl@4` to comply with GDPR regulations. However, this
has the implication that if the browser remains open for longer than 5
hours, the cookie can be reset in the middle of a session.

Due to this, it seems more reasonable to not set an expiration at all,
turning the cookie into a session cookie. Session cookies expiry only
when a browser is closed. On mobile, this can be even more beneficial,
as browsers are rarely closed (the browser can clear cookies though if
memory is constrained).
  • Loading branch information
amannn authored Dec 23, 2024
1 parent e65f4f9 commit ddd5ae5
Show file tree
Hide file tree
Showing 4 changed files with 7 additions and 16 deletions.
7 changes: 3 additions & 4 deletions docs/src/pages/docs/routing.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -473,11 +473,10 @@ In this case, only the locale prefix and a potentially [matching domain](#domain

### Locale cookie [#locale-cookie]

If a user changes the locale to a value that doesn't match the `accept-language` header, `next-intl` will set a cookie called `NEXT_LOCALE` that contains the most recently detected locale. This is used to [remember the user's locale](/docs/routing/middleware#locale-detection) preference for future requests.
If a user changes the locale to a value that doesn't match the `accept-language` header, `next-intl` will set a session cookie called `NEXT_LOCALE` that contains the most recently detected locale. This is used to [remember the user's locale](/docs/routing/middleware#locale-detection) preference for subsequent requests.

By default, the cookie will be configured with the following attributes:

1. [**`maxAge`**](https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Set-Cookie#max-agenumber): This value is set to 5 hours in order to be [GDPR-compliant](#locale-cookie-gdpr) out of the box.
2. [**`sameSite`**](https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Set-Cookie#samesitesamesite-value): This value is set to `lax` so that the cookie can be set when coming from an external site.
3. [**`path`**](https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Set-Cookie#pathpath-value): This value is not set by default, but will use the value of your [`basePath`](#base-path) if configured.

Expand Down Expand Up @@ -514,9 +513,9 @@ export const routing = defineRouting({
<Details id="locale-cookie-gdpr">
<summary>Which `maxAge` value should I consider for GDPR compliance?</summary>

The [Article 29 Working Party opinion 04/2012](https://ec.europa.eu/justice/article-29/documentation/opinion-recommendation/files/2012/wp194_en.pdf) provides a guideline for the expiration of cookies that are used to remember the user's language in section 3.6 "UI customization cookies".
The [Article 29 Working Party opinion 04/2012](https://ec.europa.eu/justice/article-29/documentation/opinion-recommendation/files/2012/wp194_en.pdf) provides a guideline for the expiration of cookies that are used to remember the user's language in section 3.6 "UI customization cookies". In this policy, a language preference cookie set as a result of an explicit user action, such as using a language switcher, is allowed to remain active for "a few additional hours" after a browser session has ended.

In this policy, a language preference cookie set as a result of an explicit user action, such as using a language switcher, is allowed to remain active for "a few additional hours" after a browser session has ended. To be compliant out of the box, `next-intl` sets the `maxAge` value of the cookie to 5 hours.
To be compliant out of the box, `next-intl` does not set the `max-age` value of the cookie, making it a session cookie that expires when a browser is closed.

However, the Working Party also states that if additional information about the use of cookies is provided in a prominent location (e.g. a "uses cookies" notice next to the language switcher), the cookie can be configured to remember the user's preference for "a longer duration". If you're providing such a notice, you can consider increasing `maxAge` accordingly.

Expand Down
2 changes: 0 additions & 2 deletions examples/example-app-router/tests/main.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -95,8 +95,6 @@ it("sets a cookie when requesting a locale that doesn't match the `accept-langua
expect(value).toContain('NEXT_LOCALE=de;');
expect(value).toContain('Path=/;');
expect(value).toContain('SameSite=lax');
expect(value).toContain('Max-Age=18000;');
expect(value).toContain('Expires=');
});

it('serves a robots.txt', async ({page}) => {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -273,8 +273,8 @@ describe("localePrefix: 'always', with `localeCookie`", () => {
expect(cookieSpy).toHaveBeenCalledWith(
[
'NEXT_LOCALE=de',
'max-age=60',
'sameSite=strict',
'max-age=60',
'domain=example.com',
'partitioned',
'path=/nested',
Expand All @@ -297,8 +297,8 @@ describe("localePrefix: 'always', with `localeCookie`", () => {
expect(cookieSpy).toHaveBeenCalledWith(
[
'NEXT_LOCALE=de',
'max-age=60',
'sameSite=strict',
'max-age=60',
'domain=example.com',
'partitioned',
'path=/nested',
Expand Down Expand Up @@ -345,12 +345,7 @@ describe("localePrefix: 'always', with `basePath`", () => {
invokeRouter((router) => router.push('/about', {locale: 'de'}));

expect(cookieSpy).toHaveBeenCalledWith(
[
'NEXT_LOCALE=de',
'max-age=18000',
'sameSite=lax',
'path=/base/path'
].join(';') + ';'
['NEXT_LOCALE=de', 'sameSite=lax', 'path=/base/path'].join(';') + ';'
);
cookieSpy.mockRestore();
});
Expand Down
3 changes: 1 addition & 2 deletions packages/next-intl/src/routing/config.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -157,7 +157,6 @@ function receiveLocaleCookie(
return (localeCookie ?? true)
? {
name: 'NEXT_LOCALE',
maxAge: 5 * 60 * 60, // 5 hours
sameSite: 'lax',
...(typeof localeCookie === 'object' && localeCookie)

Expand All @@ -173,7 +172,7 @@ export type LocaleCookieConfig = Omit<
CookieAttributes,
'name' | 'maxAge' | 'sameSite'
> &
Required<Pick<CookieAttributes, 'name' | 'maxAge' | 'sameSite'>>;
Required<Pick<CookieAttributes, 'name' | 'sameSite'>>;

function receiveLocalePrefixConfig<
AppLocales extends Locales,
Expand Down

0 comments on commit ddd5ae5

Please sign in to comment.