Skip to content

Commit

Permalink
fix: When using domains in routing config, handle unknown domains l…
Browse files Browse the repository at this point in the history
…ike `localhost:3000` more gracefully in middleware (#1389)

When a request from an unknown host like `localhost:3000` is made—as
it's often the case during development—, don't try to pick a better
domain when responding with redirects in the middleware. Instead, the
host is now only changed for redirects if the requested host is a known
one that is specified in `domains`.

Additionally, the port is now no longer removed automatically to
determine a domain. This allows e.g. to pick different ports locally to
test different locales.
  • Loading branch information
amannn authored Oct 1, 2024
1 parent 5243d97 commit 2fb56b1
Show file tree
Hide file tree
Showing 5 changed files with 62 additions and 19 deletions.
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

0 comments on commit 2fb56b1

Please sign in to comment.