Skip to content

Commit

Permalink
docs: Fix suggested middleware matcher regex and add matcher docs (#505)
Browse files Browse the repository at this point in the history
Fixes #504
  • Loading branch information
amannn authored Sep 20, 2023
1 parent fc051fd commit cd4e5e4
Show file tree
Hide file tree
Showing 7 changed files with 1,479 additions and 1,139 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -56,12 +56,14 @@ export default createMiddleware({
});

export const config = {
// Skip all paths that should not be internationalized. This example skips the
// folders "api", "_next" and all files with an extension (e.g. favicon.ico)
matcher: ['/((?!api|_next|.*\\..*).*)']
// Skip all paths that should not be internationalized. This example skips
// certain folders and all pathnames with a dot (e.g. favicon.ico)
matcher: ['/((?!api|_next|_vercel|.*\\..*).*)']
};
```

**Note:** If you have pages that contain the character `.` in the pathname (e.g. `/users/jane.doe`), you might want to consider them in your [matcher config](/docs/routing/middleware#matcher-config).

### `app/[locale]/layout.tsx` [#next-intl-client-provider]

Provide the document layout and set up `NextIntlClientProvider`.
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -113,12 +113,14 @@ export default createMiddleware({
});

export const config = {
// Skip all paths that should not be internationalized. This example skips the
// folders "api", "_next" and all files with an extension (e.g. favicon.ico)
matcher: ['/((?!api|_next|.*\\..*).*)']
// Skip all paths that should not be internationalized. This example skips
// certain folders and all pathnames with a dot (e.g. favicon.ico)
matcher: ['/((?!api|_next|_vercel|.*\\..*).*)']
};
```

**Note:** If you have pages that contain the character `.` in the pathname (e.g. `/users/jane.doe`), you might want to consider them in your [matcher config](/docs/routing/middleware#matcher-config).

### `app/[locale]/layout.tsx`

The `locale` that was matched by the middleware is available via `useLocale` and can be used to configure the document language.
Expand Down
39 changes: 36 additions & 3 deletions docs/pages/docs/routing/middleware.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -16,9 +16,9 @@ export default createMiddleware({
});

export const config = {
// Skip all paths that should not be internationalized. This example skips the
// folders "api", "_next" and all files with an extension (e.g. favicon.ico)
matcher: ['/((?!api|_next|.*\\..*).*)']
// Skip all paths that should not be internationalized. This example skips
// certain folders and all pathnames with a dot (e.g. favicon.ico)
matcher: ['/((?!api|_next|_vercel|.*\\..*).*)']
};
```

Expand Down Expand Up @@ -261,6 +261,39 @@ export default createMiddleware({
APIs](/docs/routing/navigation#localized-pathnames) in your components.
</Callout>

### Matcher config

The middleware is intended to only run on pages, not on arbitrary files that you serve independently of the user locale (e.g. `/favicon.ico`).

Because of this, the following config is generally recommended:

```tsx filename="middleware.ts"
export const config = {
// Skip all paths that should not be internationalized. This example skips
// certain folders and all pathnames with a dot (e.g. favicon.ico)
matcher: ['/((?!api|_next|_vercel|.*\\..*).*)']
};
```

However, this can lead to false negatives if you have pages that contain the character `.` (e.g. `/users/jane.doe`). To make sure these are processed by the middleware, you can add corresponding entries to the matcher config:

```tsx filename="middleware.ts"
export const config = {
// Matcher entries are linked with a logical "or", therefore
// if one of them matches, the middleware will be invoked.
matcher: [
// Match all pathnames without `.`
'/((?!api|_next|_vercel|.*\\..*).*)',
// Match all pathnames within `/users`, optionally with a locale prefix
'/(.+)?/users/(.+)'
]
};
```

{/* Keep this in sync with `packages/next-intl/test/middleware/middleware.test.tsx` */}

Additionally, some third-party providers like [Vercel Analytics](https://vercel.com/analytics) and [umami](https://umami.is/docs/running-on-vercel) typically use internal endpoints that are then rewritten to an external URL (e.g. `/_vercel/insights/view`). Make sure to exclude such requests from your middleware matcher so they aren't accidentally rewritten.

## Composing other middlewares

By calling `createMiddleware`, you'll receive a function of the following type:
Expand Down
2 changes: 1 addition & 1 deletion examples/example-next-13/src/middleware.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -7,5 +7,5 @@ export default createMiddleware({

export const config = {
// Skip all paths that should not be internationalized
matcher: ['/((?!api|_next|.*\\..*).*)']
matcher: ['/((?!api|_next|_vercel|.*\\..*).*)']
};
1 change: 1 addition & 0 deletions packages/next-intl/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -85,6 +85,7 @@
"eslint-config-molindo": "^7.0.0",
"eslint-plugin-deprecation": "^1.4.1",
"next": "13.5.1",
"path-to-regexp": "^6.2.1",
"react": "^18.2.0",
"react-dom": "^18.2.0",
"size-limit": "^8.2.6",
Expand Down
54 changes: 54 additions & 0 deletions packages/next-intl/test/middleware/middleware.test.tsx
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
import {RequestCookies} from 'next/dist/compiled/@edge-runtime/cookies';
import {NextRequest, NextResponse} from 'next/server';
import {pathToRegexp} from 'path-to-regexp';
import {it, describe, vi, beforeEach, expect, Mock} from 'vitest';
import createIntlMiddleware from '../../src/middleware';
import {COOKIE_LOCALE_NAME} from '../../src/shared/constants';
Expand Down Expand Up @@ -68,6 +69,59 @@ beforeEach(() => {
vi.clearAllMocks();
});

it('has docs that suggest a reasonable matcher', () => {
const matcherFromDocs = [
// Match all pathnames without `.`
'/((?!api|_next|_vercel|.*\\..*).*)',
// Match all pathnames within `/users`, optionally with a locale prefix
'/(.+)?/users/(.+)'
];

const test = [
['/', true],
['/test', true],
['/de/test', true],
['/something/else', true],
['/encoded%20BV', true],
['/users/jane.doe', true],
['/de/users/jane.doe', true],
['/users/jane.doe/profile', true],

['/favicon.ico', false],
['/icon.ico', false],
['/icon.png', false],
['/icon.jpg', false],
['/icon.jpeg', false],
['/icon.svg', false],
['/apple-icon.png', false],
['/manifest.json', false],
['/manifest.webmanifest', false],
['/opengraph-image.gif', false],
['/twitter-image.png', false],
['/robots.txt', false],
['/sitemap.xml', false],
['/portraits/jane.webp', false],
['/something/dot.', false],
['/.leading-dot', false],
['/api/auth', false],
['/_vercel/insights/script.js', false],
['/_vercel/insights/view', false],
['/test.html', false],
['/_next/static/chunks/main-app-123.js?23', false],
['/test.html?searchParam=2', false],
['/hello/text.txt', false]
] as const;

expect(
test.map(([pathname]) => {
const matches = matcherFromDocs.some((pattern) =>
pathname.match(pathToRegexp(pattern))
);
return pathname + ': ' + matches;
})
).toEqual(test.map(([pathname, expected]) => pathname + ': ' + expected));
});

describe('prefix-based routing', () => {
describe('localePrefix: as-needed', () => {
const middleware = createIntlMiddleware({
Expand Down
Loading

2 comments on commit cd4e5e4

@vercel
Copy link

@vercel vercel bot commented on cd4e5e4 Sep 20, 2023

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Successfully deployed to the following URLs:

next-intl-example-next-13 – ./examples/example-next-13

next-intl-example-next-13.vercel.app
next-intl-example-next-13-git-main-next-intl.vercel.app
next-intl-example-next-13-next-intl.vercel.app

@vercel
Copy link

@vercel vercel bot commented on cd4e5e4 Sep 20, 2023

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Successfully deployed to the following URLs:

next-intl-docs – ./docs

next-intl-docs.vercel.app
next-intl-docs-git-main-next-intl.vercel.app
next-intl-docs-next-intl.vercel.app

Please sign in to comment.