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

feat: createNavigation #1316

Merged
merged 68 commits into from
Oct 1, 2024
Merged
Show file tree
Hide file tree
Changes from 62 commits
Commits
Show all changes
68 commits
Select commit Hold shift + click to select a range
7df9378
wip
amannn Sep 3, 2024
4898793
Fix some types
amannn Sep 3, 2024
faedffa
set up size limits
amannn Sep 3, 2024
978c560
keep legacy logic to fix test
amannn Sep 3, 2024
73d29ab
Fix redirect edge case
amannn Sep 3, 2024
243c41d
More tests, fix for `as-necessary`
amannn Sep 4, 2024
b3a81a2
got that initial render for link working 😏
amannn Sep 4, 2024
00fb58f
Some cleanup
amannn Sep 4, 2024
690fe46
Add another test
amannn Sep 5, 2024
4660545
Mandatory locale for getPathname, don't allow to pass a locale to red…
amannn Sep 5, 2024
5a6f7a1
initial react-client implementation
amannn Sep 5, 2024
c800f84
usePathname
amannn Sep 5, 2024
177528c
test usePathname with custom prefix
amannn Sep 5, 2024
43a0b12
strictly typed return type for usePathname
amannn Sep 5, 2024
4e6f19d
more tests for link
amannn Sep 5, 2024
5f01ba4
more tests for link
amannn Sep 5, 2024
9775157
Merge branch 'canary' into feat/create-navigation
amannn Sep 5, 2024
45bc9fc
Basic useRouter implementation
amannn Sep 6, 2024
129eff8
Merge remote-tracking branch 'origin/main' into feat/create-navigation
amannn Sep 17, 2024
4aa524d
Some simplification
amannn Sep 17, 2024
6ad0167
Few more tests
amannn Sep 17, 2024
a4c6975
migrate app router playground, add base path test
amannn Sep 17, 2024
6a8c958
comment
amannn Sep 17, 2024
52aaeeb
Migrate `getPathname` in playground
amannn Sep 17, 2024
b801833
no jsdoc
amannn Sep 17, 2024
99a144a
Fix invocation
amannn Sep 17, 2024
92f181d
fix test
amannn Sep 18, 2024
ee7e2eb
refactor playwright tests to use cases
amannn Sep 18, 2024
ba7acf4
first bit of domains support
amannn Sep 20, 2024
604d56c
domain progress with hydrating link
amannn Sep 20, 2024
4301e9b
fix lint
amannn Sep 20, 2024
49a75ef
fix test script
amannn Sep 20, 2024
63eafe6
more tests
amannn Sep 25, 2024
c9b7464
more tests
amannn Sep 25, 2024
dd053e8
first support for domains with useRouter
amannn Sep 25, 2024
bdd0c84
tests for usePathname
amannn Sep 25, 2024
2bc3940
improve comments
amannn Sep 25, 2024
10d22cf
Merge remote-tracking branch 'origin/main' into feat/create-navigation
amannn Sep 25, 2024
eb74554
Merge branch 'canary' into feat/create-navigation
amannn Sep 25, 2024
5aa506b
more tests
amannn Sep 25, 2024
9274cda
Strict typing for `domain` in `getPathname`
amannn Sep 26, 2024
82eaff3
remove _forcePrefix from public api
amannn Sep 26, 2024
4f5b70e
Merge remote-tracking branch 'origin/canary' into feat/create-navigation
amannn Sep 26, 2024
a215028
prettify link props
amannn Sep 26, 2024
c9398e1
more tests
amannn Sep 26, 2024
c02104f
JSDoc, cleanup
amannn Sep 26, 2024
0867598
size limit
amannn Sep 26, 2024
41f1b2e
fix types for useRouter
amannn Sep 26, 2024
0325aa2
docs
amannn Sep 26, 2024
0580664
note on different locale prefix settings per domain
amannn Sep 26, 2024
9e23771
legacy api docs
amannn Sep 26, 2024
fad49c6
wording
amannn Sep 26, 2024
fad59bf
Mandatory locale for `getPathname` and allow async locale from header…
amannn Sep 27, 2024
d42c926
fix build
amannn Sep 27, 2024
f2c61e3
accept `locale` as string for navigation apis for better compatibilit…
amannn Sep 27, 2024
a860224
bump sizes
amannn Sep 27, 2024
2479539
docs wording
amannn Sep 30, 2024
1cf2f97
compatibility with radix primitives
amannn Sep 30, 2024
07395d3
bump sizes
amannn Sep 30, 2024
c8f0761
migrate example-app-router
amannn Sep 30, 2024
adba9d5
migrate example-app-router-migration
amannn Sep 30, 2024
07f75e5
migrate example-app-router-next-auth
amannn Sep 30, 2024
8b63e05
wording
amannn Sep 30, 2024
b79466e
some domain e2e tests
amannn Oct 1, 2024
cc688c9
Merge remote-tracking branch 'origin/canary' into feat/create-navigation
amannn Oct 1, 2024
37b5872
simplify Link on react-client impl
amannn Oct 1, 2024
a08271a
fix playwright
amannn Oct 1, 2024
6b20873
bump sizes
amannn Oct 1, 2024
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
Original file line number Diff line number Diff line change
Expand Up @@ -107,7 +107,7 @@ To share the configuration between these two places, we'll set up `routing.ts`:

```ts filename="src/i18n/routing.ts"
import {defineRouting} from 'next-intl/routing';
import {createSharedPathnamesNavigation} from 'next-intl/navigation';
import {createNavigation} from 'next-intl/navigation';

export const routing = defineRouting({
// A list of all locales that are supported
Expand All @@ -120,7 +120,7 @@ export const routing = defineRouting({
// Lightweight wrappers around Next.js' navigation APIs
// that will consider the routing configuration
export const {Link, redirect, usePathname, useRouter} =
createSharedPathnamesNavigation(routing);
createNavigation(routing);
```

Depending on your requirements, you may wish to customize your routing configuration later—but let's finish with the setup first.
Expand Down
91 changes: 75 additions & 16 deletions docs/pages/docs/routing.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -41,7 +41,7 @@ Depending on your routing needs, you may wish to consider further settings.

In case you're building an app where locales can be added and removed at runtime, you can provide the routing configuration for the middleware [dynamically per request](/docs/routing/middleware#composing-other-middlewares).

To create the corresponding navigation APIs, you can [omit the `locales` argument](/docs/routing/navigation#locales-unknown) from `createSharedPathnamesNavigation` in this case.
To create the corresponding navigation APIs, you can [omit the `locales` argument](/docs/routing/navigation#locales-unknown) from `createNavigation` in this case.

</Details>

Expand Down Expand Up @@ -84,19 +84,16 @@ export const routing = defineRouting({

In this case, requests where the locale prefix matches the default locale will be redirected (e.g. `/en/about` to `/about`). This will affect both prefix-based as well as domain-based routing.

**Note that:**

1. If you use this strategy, you should make sure that your middleware matcher detects [unprefixed pathnames](/docs/routing/middleware#matcher-no-prefix).
2. If you use [the `Link` component](/docs/routing/navigation#link), the initial render will point to the prefixed version but will be patched immediately on the client once the component detects that the default locale has rendered. The prefixed version is still valid, but SEO tools might report a hint that the link points to a redirect.
**Note that:** If you use this strategy, you should make sure that your middleware matcher detects [unprefixed pathnames](/docs/routing/middleware#matcher-no-prefix) for the routing to work as expected.

#### Never use a locale prefix [#locale-prefix-never]

If you'd like to provide a locale to `next-intl`, e.g. based on user settings, you can consider setting up `next-intl` [without i18n routing](/docs/getting-started/app-router/without-i18n-routing). This way, you don't need to use the routing integration in the first place.

However, you can also configure the middleware to never show a locale prefix in the URL, which can be helpful in the following cases:

1. You're using [domain-based routing](#domains) and you support only a single locale per domain
2. You're using a cookie to determine the locale but would like to enable static rendering
1. You want to use [domain-based routing](#domains) and have only one locale per domain
2. You want to use a cookie to determine locale while enabling static rendering
amannn marked this conversation as resolved.
Show resolved Hide resolved

```tsx filename="routing.ts" {5}
import {defineRouting} from 'next-intl/routing';
Expand Down Expand Up @@ -153,8 +150,8 @@ function Component() {
// Assuming the locale is 'en-US'
const locale = useLocale();

// Returns 'US'
new Intl.Locale(locale).region;
// Extracts the "US" region
const {region} = new Intl.Locale(locale);
}
```

Expand Down Expand Up @@ -222,13 +219,6 @@ export const routing = defineRouting({

Localized pathnames map to a single internal pathname that is created via the file-system based routing in Next.js. In the example above, `/de/ueber-uns` will be handled by the page at `/[locale]/about/page.tsx`.

<Callout>
If you're using localized pathnames, you should use
`createLocalizedPathnamesNavigation` instead of
`createSharedPathnamesNavigation` for your [navigation
APIs](/docs/routing/navigation).
</Callout>

<Details id="localized-pathnames-revalidation">
<summary>How can I revalidate localized pathnames?</summary>

Expand Down Expand Up @@ -373,3 +363,72 @@ export const routing = defineRouting({
Learn more about this in the [locale detection for domain-based routing](/docs/routing/middleware#location-detection-domain) docs.

</Details>

<Details id="domains-localeprefix-individual">
<summary>Can I use a different `localePrefix` setting per domain?</summary>

Since such a configuration would require reading the domain at runtime, this would prevent the ability to render pages statically. Due to this, `next-intl` doesn't support this configuration out of the box.

However, you can still achieve this by building the app for each domain separately, while injecting diverging routing configuration via an environment variable.

**Example:**

```tsx filename="routing.ts"
import {defineRouting} from 'next-intl/routing';

export const routing = defineRouting({
locales: ['en', 'fr'],
defaultLocale: 'en',
localePrefix:
process.env.VERCEL_PROJECT_PRODUCTION_URL === 'us.example.com'
? 'never'
: 'always',
domains: [
{
domain: 'us.example.com',
defaultLocale: 'en',
locales: ['en']
},
{
domain: 'ca.example.com',
defaultLocale: 'en'
}
]
});
```

</Details>

<Details id="domains-localeprefix-asneeded">
<summary>Special case: Using `domains` with `localePrefix: 'as-needed'`</summary>

Since domains can have different default locales, this combination requires some tradeoffs that apply to the [navigation APIs](/docs/routing/navigation) in order for `next-intl` to avoid reading the current host on the server side (which would prevent the usage of static rendering).

1. [`<Link />`](/docs/routing/navigation#link): This component will always render a locale prefix on the server side, even for the default locale of a given domain. However, during hydration on the client side, the prefix is potentially removed, if the default locale of the current domain is used. Note that the temporarily prefixed pathname will always be valid, however the middleware will potentially clean up a superfluous prefix via a redirect if the user clicks on a link before hydration.
2. [`redirect`](/docs/routing/navigation#redirect): When calling this function, a locale prefix is always added, regardless of the current locale and domain. However, similar to the handling with `<Link />`, the middleware will potentially clean up a superfluous prefix.
amannn marked this conversation as resolved.
Show resolved Hide resolved
3. [`getPathname`](/docs/routing/navigation#getpathname): This function requires that a `domain` is passed as part of the arguments in order to avoid ambiguity. This can either be provided statically (e.g. when used in a sitemap), or read from a header like [`x-forwarded-host`](https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/X-Forwarded-Host).

```tsx
import {getPathname} from '@/i18n/routing';
import {headers} from 'next/headers';

// Case 1: Statically known domain
const domain = 'ca.example.com';

// Case 2: Read at runtime (dynamic rendering)
const domain = headers().get('x-forwarded-host');

// Assuming the current domain is `ca.example.com`,
// the returned pathname will be `/about`
const pathname = getPathname({
href: '/about',
locale: 'en',
domain
});
```

A `domain` can optionally also be passed to `redirect` in the same manner to ensure that a prefix is only added when necessary. Alternatively, you can also consider redirecting in the middleware or via [`useRouter`](/docs/routing/navigation#usrouter) on the client side.

If you need to avoid these tradeoffs, you can consider building the same app for each domain separately, while injecting diverging routing configuration via an [environment variable](#domains-localeprefix-individual).

</Details>
2 changes: 2 additions & 0 deletions docs/pages/docs/routing/middleware.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,8 @@ The middleware receives a [`routing`](/docs/routing#define-routing) configuratio
2. Applying relevant redirects & rewrites
3. Providing [alternate links](#alternate-links) for search engines

**Example:**

```tsx filename="middleware.ts"
import createMiddleware from 'next-intl/middleware';
import {routing} from './i18n/routing';
Expand Down
Loading
Loading