Skip to content

Commit 8eefe0b

Browse files
authored
feat: Add localeCookie option for middleware (amannn#1414)
1 parent 1073d48 commit 8eefe0b

File tree

5 files changed

+62
-9
lines changed

5 files changed

+62
-9
lines changed

.size-limit.ts

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -60,7 +60,7 @@ const config: SizeLimitConfig = [
6060
{
6161
name: 'import createMiddleware from \'next-intl/middleware\'',
6262
path: 'dist/production/middleware.js',
63-
limit: '9.63 KB'
63+
limit: '9.675 KB'
6464
},
6565
{
6666
name: 'import * from \'next-intl/routing\'',
@@ -71,7 +71,7 @@ const config: SizeLimitConfig = [
7171
name: 'import * from \'next-intl\' (react-client, ESM)',
7272
path: 'dist/esm/index.react-client.js',
7373
import: '*',
74-
limit: '14.265 kB'
74+
limit: '14.245 kB'
7575
},
7676
{
7777
name: 'import {NextIntlProvider} from \'next-intl\' (react-client, ESM)',

src/middleware/config.tsx

Lines changed: 32 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,38 @@
1+
import {NextResponse} from 'next/server';
2+
3+
type ResponseCookieOptions = Pick<
4+
NonNullable<Parameters<typeof NextResponse.prototype.cookies.set>['2']>,
5+
| 'maxAge'
6+
| 'domain'
7+
| 'expires'
8+
| 'partitioned'
9+
| 'path'
10+
| 'priority'
11+
| 'sameSite'
12+
| 'secure'
13+
// Not:
14+
// - 'httpOnly' (the client side needs to read the cookie)
15+
// - 'name' (the client side needs to know this as well)
16+
// - 'value' (only the middleware knows this)
17+
>;
18+
119
export type MiddlewareOptions = {
2-
/** Sets the `Link` response header to notify search engines about content in other languages (defaults to `true`). See https://developers.google.com/search/docs/specialty/international/localized-versions#http */
20+
/**
21+
* Sets the `Link` response header to notify search engines about content in other languages (defaults to `true`). See https://developers.google.com/search/docs/specialty/international/localized-versions#http
22+
* @see https://next-intl-docs.vercel.app/docs/routing/middleware#alternate-links
23+
**/
324
alternateLinks?: boolean;
425

5-
/** By setting this to `false`, the cookie as well as the `accept-language` header will no longer be used for locale detection. */
26+
/**
27+
* Can be used to disable the locale cookie or to customize it.
28+
* @see https://next-intl-docs.vercel.app/docs/routing/middleware#locale-cookie
29+
*/
30+
localeCookie?: boolean | ResponseCookieOptions;
31+
32+
/**
33+
* By setting this to `false`, the cookie as well as the `accept-language` header will no longer be used for locale detection.
34+
* @see https://next-intl-docs.vercel.app/docs/routing/middleware#locale-detection
35+
**/
636
localeDetection?: boolean;
737
};
838

src/middleware/middleware.test.tsx

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -294,6 +294,24 @@ describe('prefix-based routing', () => {
294294
});
295295
});
296296

297+
it('can turn off the cookie', () => {
298+
const response = createMiddleware(routing, {localeCookie: false})(
299+
createMockRequest('/')
300+
);
301+
expect(response.cookies.get('NEXT_LOCALE')).toBeUndefined();
302+
});
303+
304+
it('restricts which options of the cookie can be customized', () => {
305+
createMiddleware(routing, {
306+
localeCookie: {
307+
// @ts-expect-error
308+
httpOnly: true,
309+
name: 'custom',
310+
value: 'custom'
311+
}
312+
});
313+
});
314+
297315
it('retains request headers for the default locale', () => {
298316
middleware(
299317
createMockRequest('/', 'en', 'http://localhost:3000', undefined, {

src/middleware/middleware.tsx

Lines changed: 4 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -49,7 +49,8 @@ export default function createMiddleware<
4949
const resolvedOptions = {
5050
alternateLinks: options?.alternateLinks ?? routing.alternateLinks ?? true,
5151
localeDetection:
52-
options?.localeDetection ?? routing?.localeDetection ?? true
52+
options?.localeDetection ?? routing?.localeDetection ?? true,
53+
localeCookie: options?.localeCookie ?? routing?.localeCookie ?? true
5354
};
5455

5556
return function middleware(request: NextRequest) {
@@ -311,8 +312,8 @@ export default function createMiddleware<
311312
}
312313
}
313314

314-
if (resolvedOptions.localeDetection) {
315-
syncCookie(request, response, locale);
315+
if (resolvedOptions.localeDetection && resolvedOptions.localeCookie) {
316+
syncCookie(request, response, locale, resolvedOptions.localeCookie);
316317
}
317318

318319
if (

src/middleware/syncCookie.tsx

Lines changed: 6 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -4,19 +4,23 @@ import {
44
COOKIE_MAX_AGE,
55
COOKIE_SAME_SITE
66
} from '../shared/constants';
7+
import {MiddlewareOptions} from './config';
78

89
export default function syncCookie(
910
request: NextRequest,
1011
response: NextResponse,
11-
locale: string
12+
locale: string,
13+
localeCookie: MiddlewareOptions['localeCookie']
1214
) {
1315
const hasOutdatedCookie =
1416
request.cookies.get(COOKIE_LOCALE_NAME)?.value !== locale;
17+
1518
if (hasOutdatedCookie) {
1619
response.cookies.set(COOKIE_LOCALE_NAME, locale, {
1720
path: request.nextUrl.basePath || undefined,
1821
sameSite: COOKIE_SAME_SITE,
19-
maxAge: COOKIE_MAX_AGE
22+
maxAge: COOKIE_MAX_AGE,
23+
...(typeof localeCookie === 'object' && localeCookie)
2024
});
2125
}
2226
}

0 commit comments

Comments
 (0)