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: When using domains, handle unknown domains more gracefully #1389

Merged
merged 2 commits into from
Oct 1, 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
32 changes: 31 additions & 1 deletion docs/pages/docs/routing.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -370,6 +370,36 @@ export const routing = defineRouting({
<Details id="domains-testing">
<summary>How can I locally test if my setup is working?</summary>

Learn more about this in the [locale detection for domain-based routing](/docs/routing/middleware#location-detection-domain) docs.
To test your domain setup locally, you can conditionally adapt the domains to refer to hosts that are available locally:

```tsx filename="routing.ts"
export const routing = defineConfig({
// ...
domains: [
{
domain: process.env.NODE_ENV === 'development'
? 'localhost:3000'
: 'us.example.com'
// ...
},
{
domain: process.env.NODE_ENV === 'development'
? 'localhost:3001'
: 'ca.example.com'
// ...
}
]
});
```

Now, you can run your development server on one of the configured ports and test the routing for different use cases:

```sh
# Like `us.example.com`
PORT=3000 npm run dev

# Like `ca.example.com`
PORT=3001 npm run dev
```

</Details>
13 changes: 0 additions & 13 deletions docs/pages/docs/routing/middleware.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -99,19 +99,6 @@ The bestmatching domain is detected based on these priorities:

</Details>

<Details id="domain-local-testing">
<summary>How can I locally test if my setup is working?</summary>

Since the negotiated locale depends on the host of the request, you can test your setup by attaching a corresponding `x-forwarded-host` header. To achieve this in the browser, you can use a browser extension like [ModHeader in Chrome](https://chromewebstore.google.com/detail/modheader-modify-http-hea/idgpnmonknjnojddfkpgkljpfnnfcklj) and add a setting like:

```
X-Forwarded-Host: example.com
```

With this, your domain config for this particular domain will be used.

</Details>

## Configuration

Apart from the [`routing`](/docs/routing#shared-configuration) configuration that is shared with the [navigation APIs](/docs/routing/navigation), the middleware accepts a few additional options that can be used for customization.
Expand Down
29 changes: 29 additions & 0 deletions packages/next-intl/src/middleware/middleware.test.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -2319,6 +2319,15 @@ describe('domain-based routing', () => {
);
});

it('serves requests for unknown domains based on the global `defaultLocale`', () => {
middleware(createMockRequest('/', 'en', 'http://localhost:3000'));
expect(MockedNextResponse.next).not.toHaveBeenCalled();
expect(MockedNextResponse.redirect).not.toHaveBeenCalled();
expect(MockedNextResponse.rewrite.mock.calls[0][0].toString()).toBe(
'http://localhost:3000/en'
);
});

it('serves requests for the default locale at sub paths', () => {
middleware(createMockRequest('/about', 'en', 'http://en.example.com'));
expect(MockedNextResponse.next).not.toHaveBeenCalled();
Expand Down Expand Up @@ -2367,6 +2376,16 @@ describe('domain-based routing', () => {
);
});

it('removes a superfluous locale prefix of a secondary locale that is the default locale of the domain', () => {
middleware(createMockRequest('/fr', 'fr', 'http://fr.example.com'));
expect(MockedNextResponse.next).not.toHaveBeenCalled();
expect(MockedNextResponse.rewrite).not.toHaveBeenCalled();
expect(MockedNextResponse.redirect).toHaveBeenCalled();
expect(MockedNextResponse.redirect.mock.calls[0][0].toString()).toBe(
'http://fr.example.com/'
);
});

it('returns alternate links', () => {
const response = middleware(createMockRequest('/'));
expect(response.headers.get('link')).toBe(
Expand Down Expand Up @@ -2434,6 +2453,16 @@ describe('domain-based routing', () => {
'http://localhost/fr/about'
);
});

it('keeps the host of an unknown domain for easier local development', () => {
middleware(createMockRequest('/en', 'en', 'http://localhost:3000'));
expect(MockedNextResponse.next).not.toHaveBeenCalled();
expect(MockedNextResponse.rewrite).not.toHaveBeenCalled();
expect(MockedNextResponse.redirect).toHaveBeenCalled();
expect(MockedNextResponse.redirect.mock.calls[0][0].toString()).toBe(
'http://localhost:3000/'
);
});
});

describe('locales-restricted domain', () => {
Expand Down
2 changes: 1 addition & 1 deletion packages/next-intl/src/middleware/middleware.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -91,7 +91,7 @@ export default function createMiddleware<
function redirect(url: string, redirectDomain?: string) {
const urlObj = new URL(normalizeTrailingSlash(url), request.url);

if (domainsConfig.length > 0 && !redirectDomain) {
if (domainsConfig.length > 0 && !redirectDomain && domain) {
const bestMatchingDomain = getBestMatchingDomain(
domain,
locale,
Expand Down
5 changes: 1 addition & 4 deletions packages/next-intl/src/middleware/resolveLocale.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -16,10 +16,7 @@ function findDomainFromHost<AppLocales extends Locales>(
requestHeaders: Headers,
domains: DomainsConfig<AppLocales>
) {
let host = getHost(requestHeaders);

// Remove port (easier for local development)
host = host?.replace(/:\d+$/, '');
const host = getHost(requestHeaders);

if (host && domains) {
return domains.find((cur) => cur.domain === host);
Expand Down
Loading