Skip to content

Commit b39fa61

Browse files
authored
docs: Getting started with all messages on the client side (#1046)
1 parent 2423eb3 commit b39fa61

File tree

3 files changed

+91
-55
lines changed

3 files changed

+91
-55
lines changed

docs/pages/docs/environments/server-client-components.mdx

Lines changed: 23 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -23,7 +23,7 @@ Moving internationalization to the server side unlocks new levels of performance
2323

2424
**Benefits of server-side internationalization:**
2525

26-
1. Your messages never leave the server and don't need to be serialized for the client side
26+
1. Your messages never leave the server and don't need to be passed to the client side
2727
2. Library code for internationalization doesn't need to be loaded on the client side
2828
3. No need to split your messages, e.g. based on routes or components
2929
4. No runtime cost on the client side
@@ -123,7 +123,22 @@ In regard to performance, async functions and hooks can be used very much interc
123123

124124
## Using internationalization in Client Components
125125

126-
Depending on your situation, you may need to handle internationalization in Client Components as well. There are several options for using translations or other functionality from `next-intl` in Client Components, listed here in order of recommendation.
126+
Depending on your situation, you may need to handle internationalization in Client Components as well. While providing all messages to the client side is typically the easiest way to [get started](/docs/getting-started/app-router#layout) and a reasonable approach for many apps, you can be more selective about which messages are passed to the client side if you're interested in optimizing the performance of your app.
127+
128+
<Details id="client-messages-performance">
129+
<summary>How does loading messages on the client side relate to performance?</summary>
130+
131+
Depending on the requirements for your app, you might want to monitor your [Core Web Vitals](https://web.dev/articles/vitals) to ensure your app meets your performance goals.
132+
133+
If you pass messages to `NextIntlClientProvider`, Next.js will emit them during the streaming render to the markup of the page so that they can be used by Client Components. This can contribute to the [total blocking time](https://web.dev/articles/tbt), which in turn can relate to the [interaction to next paint](https://web.dev/articles/inp) metric. If you're seeking to improve these metrics in your app, you can be more selective about which messages are passed to the client side.
134+
135+
However, as the general rule for optimization goes: Always measure before you optimize. If your app already performs well, there's no need for optimization.
136+
137+
Note that an automatic, compiler-driven approach for automatically splitting messages is being evaluated in [`next-intl#1`](https://github.com/amannn/next-intl/issues/1).
138+
139+
</Details>
140+
141+
There are several options for using translations from `next-intl` in Client Components, listed here in order of enabling the best performance:
127142

128143
### Option 1: Passing translations to Client Components
129144

@@ -221,15 +236,15 @@ To keep internationalization on the server side, it can be helpful to structure
221236
```tsx filename="app/register/page.tsx"
222237
import {useTranslations} from 'next-intl';
223238

224-
// A Client Component, so that it can use `useFormState` to
225-
// potentially display errors received after submission.
239+
// A Client Component, so that `useFormState` can be used
240+
// to potentially display errors received after submission.
226241
import RegisterForm from './RegisterForm';
227242

228-
// A Client Component, so that it can use `useFormStatus`
243+
// A Client Component, so that `useFormStatus` can be used
229244
// to disable the input field during submission.
230245
import FormField from './FormField';
231246

232-
// A Client Component, so that it can use `useFormStatus`
247+
// A Client Component, so that `useFormStatus` can be used
233248
// to disable the submit button during submission.
234249
import FormSubmitButton from './FormSubmitButton';
235250

@@ -310,14 +325,12 @@ export default function Counter() {
310325
}
311326
```
312327

313-
In case you prefer to make all messages available to the client side, you can [configure `NextIntlClientProvider` in the root layout](#option-4-providing-all-messages) instead.
314-
315328
<Details id="messages-client-namespaces">
316329
<summary>How can I know the messages I need to provide to the client side?</summary>
317330

318331
Currently, the messages you select for being passed to the client side need to be picked based on knowledge about the implementation of the wrapped components.
319332

320-
An automatic, compiler-driven approach is being evaluated in [`next-intl#2`](https://github.com/amannn/next-intl/issues/1).
333+
An automatic, compiler-driven approach is being evaluated in [`next-intl#1`](https://github.com/amannn/next-intl/issues/1).
321334

322335
</Details>
323336

@@ -347,11 +360,6 @@ export default function LocaleLayout({children, params: {locale}}) {
347360
}
348361
```
349362

350-
<Callout type="warning">
351-
Note that this is a tradeoff in regard to performance (see the bullet points
352-
at the top of this page).
353-
</Callout>
354-
355363
## Troubleshooting
356364

357365
### "Failed to call `useTranslations` because the context from `NextIntlClientProvider` was not found." [#missing-context]
@@ -404,4 +412,4 @@ export default function MyCustomNextIntlClientProvider({
404412

405413
By doing this, your custom provider will already be part of the client-side bundle and can therefore define and pass functions as props.
406414

407-
**Important:** Be sure to pass explicit `locale`, `timeZone` and `now` props to `NextIntlClientProvider` in this case, since the props aren't automatically inherited from a Server Component when you import `NextIntlClientProvider` from a Client Component.
415+
**Important:** Be sure to pass explicit `locale`, `formats`, `timeZone` and `now` props to `NextIntlClientProvider` in this case, since the props aren't automatically inherited from a Server Component when you import `NextIntlClientProvider` from a Client Component.

docs/pages/docs/getting-started/app-router.mdx

Lines changed: 16 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -5,8 +5,6 @@ import Details from 'components/Details';
55

66
# Next.js App Router Internationalization (i18n)
77

8-
The Next.js App Router introduces support for [React Server Components](https://nextjs.org/docs/app/building-your-application/rendering/server-components) and unlocks [many benefits](/docs/environments/server-client-components) when handling internationalization on the server side.
9-
108
## Getting started
119

1210
If you haven't done so already, [create a Next.js app](https://nextjs.org/docs/getting-started/installation) that uses the App Router.
@@ -56,8 +54,6 @@ Now, set up the plugin which creates an alias to provide your i18n configuration
5654
<Tabs items={['next.config.mjs', 'next.config.js']}>
5755
<Tab>
5856

59-
If you're using ECMAScript modules for your Next.js config, you can use the plugin as follows:
60-
6157
```js filename="next.config.mjs"
6258
import createNextIntlPlugin from 'next-intl/plugin';
6359

@@ -72,8 +68,6 @@ export default withNextIntl(nextConfig);
7268
</Tab>
7369
<Tab>
7470

75-
If you're using CommonJS for your Next.js config, you can use the plugin as follows:
76-
7771
```js filename="next.config.js"
7872
const createNextIntlPlugin = require('next-intl/plugin');
7973

@@ -90,7 +84,7 @@ module.exports = withNextIntl(nextConfig);
9084

9185
### `i18n.ts` [#i18nts]
9286

93-
`next-intl` creates a configuration once per request. Here you can provide messages and other options depending on the locale of the user.
87+
`next-intl` creates a request-scoped configuration object that can be used to provide messages and other options depending on the locale of the user for usage in Server Components.
9488

9589
```tsx filename="src/i18n.ts"
9690
import {notFound} from 'next/navigation';
@@ -148,24 +142,36 @@ export const config = {
148142

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

151-
The `locale` that was matched by the middleware is available via the `locale` param and can be used to configure the document language.
145+
The `locale` that was matched by the middleware is available via the `locale` param and can be used to configure the document language. Additionally, we can use this place to pass configuration from `i18n.ts` to Client Components via `NextIntlClientProvider`.
152146

153147
```tsx filename="app/[locale]/layout.tsx"
154-
export default function LocaleLayout({
148+
import {getMessages} from 'next-intl/server';
149+
150+
export default async function LocaleLayout({
155151
children,
156152
params: {locale}
157153
}: {
158154
children: React.ReactNode;
159155
params: {locale: string};
160156
}) {
157+
// Providing all messages to the client
158+
// side is the easiest way to get started
159+
const messages = await getMessages();
160+
161161
return (
162162
<html lang={locale}>
163-
<body>{children}</body>
163+
<body>
164+
<NextIntlClientProvider messages={messages}>
165+
{children}
166+
</NextIntlClientProvider>
167+
</body>
164168
</html>
165169
);
166170
}
167171
```
168172

173+
Note that `NextIntlClientProvider` automatically inherits most configuration from `i18n.ts` here.
174+
169175
### `app/[locale]/page.tsx` [#page]
170176

171177
Use translations in your page components or anywhere else!
@@ -191,10 +197,6 @@ In case you ran into an issue, have a look at [the App Router example](https://n
191197

192198
<ul className="ml-4 list-disc">
193199
<li>[Usage guide](/docs/usage): Format messages, dates and times</li>
194-
<li>
195-
[Environments](/docs/environments): Explore usage in Server & Client
196-
Components and the Metadata API
197-
</li>
198200
<li>
199201
[Routing](/docs/routing): Integrate i18n routing with `<Link />` & friends
200202
</li>

docs/pages/docs/usage/configuration.mdx

Lines changed: 52 additions & 26 deletions
Original file line numberDiff line numberDiff line change
@@ -39,10 +39,11 @@ The configuration object is created once for each request by internally using Re
3939
`NextIntlClientProvider` can be used to provide configuration for **Client Components**.
4040

4141
```tsx filename="app/[locale]/layout.tsx" /NextIntlClientProvider/
42-
import {NextIntlClientProvider, useMessages} from 'next-intl';
42+
import {NextIntlClientProvider} from 'next-intl';
43+
import {getMessages} from 'next-intl/server';
4344

44-
export default function LocaleLayout({children, params: {locale}}) {
45-
const messages = useMessages();
45+
export default async function LocaleLayout({children, params: {locale}}) {
46+
const messages = await getMessages();
4647

4748
return (
4849
<html lang={locale}>
@@ -56,13 +57,18 @@ export default function LocaleLayout({children, params: {locale}}) {
5657
}
5758
```
5859

59-
`NextIntlClientProvider` inherits the props `locale`, `now` and `timeZone` when the component is rendered from a Server Component. Other configuration like `messages` and `formats` can be provided as necessary.
60+
These props are inherited if you're rendering `NextIntlClientProvider` from a Server Component:
61+
62+
1. `locale`
63+
2. `now`
64+
3. `timeZone`
65+
66+
In contrast, these props can be provided as necessary:
6067

61-
<Callout>
62-
Before passing all messages to the client side, learn more about the options
63-
you have to [use internationalization in Client
64-
Components](/docs/environments/server-client-components).
65-
</Callout>
68+
1. `messages` (see [Internationalization in Client Components](/docs/environments/server-client-components#using-internationalization-in-client-components))
69+
2. `formats`
70+
3. `defaultTranslationValues`
71+
4. `onError` and `getMessageFallback`
6672

6773
## Messages
6874

@@ -95,29 +101,37 @@ export default getRequestConfig(async ({locale}) => {
95101
});
96102
```
97103

98-
To read configured messages in a component, you can use the `useMessages` hook:
104+
After messages are configured, they can be used via `useTranslations`.
105+
106+
In case you require access to messages in a component, you can use a convenience API to read them from your configuration:
99107

100108
```tsx
109+
// Regular components
101110
import {useMessages} from 'next-intl';
102-
103111
const messages = useMessages();
112+
113+
// Async Server Components
114+
import {getMessages} from 'next-intl/server';
115+
const messages = await getMessages();
104116
```
105117

106118
</Tab>
107119
<Tab>
108120

109121
```tsx
110-
import {NextIntlClientProvider, useMessages} from 'next-intl';
122+
import {NextIntlClientProvider} from 'next-intl';
123+
import {getMessages} from 'next-intl/server';
111124

112-
// Read messages configured via `i18n.ts`. Alternatively,
113-
// messages can be fetched from any other source too.
114-
const messages = useMessages();
125+
async function Component({children}) {
126+
// Read messages configured via `i18n.ts`
127+
const messages = await getMessages();
115128

116-
return (
117-
<NextIntlClientProvider messages={messages}>
118-
{children}
119-
</NextIntlClientProvider>
120-
);
129+
return (
130+
<NextIntlClientProvider messages={messages}>
131+
{children}
132+
</NextIntlClientProvider>
133+
);
134+
}
121135
```
122136

123137
</Tab>
@@ -211,12 +225,16 @@ const timeZone = 'Europe/Vienna';
211225

212226
The available time zone names can be looked up in [the tz database](https://en.wikipedia.org/wiki/List_of_tz_database_time_zones).
213227

214-
To read the time zone in a component, you can use the `useTimeZone` hook:
228+
The configured time zone can be read from components:
215229

216230
```tsx
231+
// Regular components
217232
import {useTimeZone} from 'next-intl';
233+
const messages = useTimeZone();
218234
219-
const timeZone = useTimeZone();
235+
// Async Server Components
236+
import {getTimeZone} from 'next-intl/server';
237+
const timeZone = await getTimeZone();
220238
```
221239

222240
The time zone in Client Components is automatically inherited from the server
@@ -261,12 +279,16 @@ const now = new Date('2020-11-20T10:36:01.516Z');
261279
</Tab>
262280
</Tabs>
263281

264-
To read the now value in a component, you can use the `useNow` hook:
282+
The configured `now` value can be read from components:
265283

266284
```tsx
285+
// Regular components
267286
import {useNow} from 'next-intl';
268-
269287
const now = useNow();
288+
289+
// Async Server Components
290+
import {getNow} from 'next-intl/server';
291+
const now = await getNow();
270292
```
271293

272294
Similarly to the `timeZone`, the `now` value in Client Components is
@@ -527,12 +549,16 @@ function getMessageFallback({namespace, key, error}) {
527549
528550
The current locale of your app is automatically incorporated into hooks like `useTranslations` & `useFormatter` and will affect the rendered output.
529551
530-
In case you need to use this value in other places of your app, you can read it via the `useLocale` hook:
552+
In case you need to use this value in other places of your app, you can read it in components:
531553
532554
```tsx
555+
// Regular components
533556
import {useLocale} from 'next-intl';
534-
535557
const locale = useLocale();
558+
559+
// Async Server Components
560+
import {getLocale} from 'next-intl/server';
561+
const locale = await getLocale();
536562
```
537563
538564
<Details id="locale-change">

0 commit comments

Comments
 (0)