Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

fix: Make locale cookie a session cookie #1634

Merged
merged 4 commits into from
Dec 23, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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
Loading