|
1 | 1 | import Callout from 'components/Callout'; |
2 | 2 |
|
3 | | -# Internationalization of Server & Client Components in Next.js 13 |
| 3 | +# Internationalization of Server & Client Components |
4 | 4 |
|
5 | | -With the introduction of the App Router in Next.js 13, [React Server Components](https://nextjs.org/docs/getting-started/react-essentials) became publicly available. This new paradigm allows components that don’t require React’s interactive features, such as `useState` and `useEffect`, to remain server-side only. |
| 5 | +[React Server Components](https://nextjs.org/docs/app/building-your-application/rendering/server-components) allow you to implement components that remain server-side only if they don’t require React’s interactive features, such as `useState` and `useEffect`. |
6 | 6 |
|
7 | 7 | This applies to handling internationalization too. |
8 | 8 |
|
9 | 9 | ```tsx filename="app/[locale]/page.tsx" |
10 | 10 | import {useTranslations} from 'next-intl'; |
11 | 11 |
|
12 | 12 | // Since this component doesn't use any interactive features |
13 | | -// from React, it can be implemented as a Server Component. |
| 13 | +// from React, it can be run as a Server Component. |
14 | 14 |
|
15 | 15 | export default function Index() { |
16 | 16 | const t = useTranslations('Index'); |
17 | 17 | return <h1>{t('title')}</h1>; |
18 | 18 | } |
19 | 19 | ``` |
20 | 20 |
|
21 | | -Depending on if you import `useTranslations`, `useFormatter`, `useLocale`, `useNow` and `useTimeZone` from a Server or Client Component, `next-intl` will automatically provide an implementation that works best for the given environment. |
| 21 | +Moving internationalization to the server side unlocks new levels of performance, leaving the client side for interactive features. |
22 | 22 |
|
23 | | -<details> |
24 | | -<summary>Deep dive: How does the Server Components integration work?</summary> |
| 23 | +**Benefits of server-side internationalization:** |
25 | 24 |
|
26 | | -`next-intl` uses [`react-server` conditional exports](https://github.com/reactjs/rfcs/blob/main/text/0227-server-module-conventions.md#react-server-conditional-exports) to load code that is optimized for the usage in Server or Client Components. While configuration for hooks like `useTranslations` is read via `useContext` on the client side, on the server side it is loaded via [`i18n.ts`](/docs/usage/configuration#i18nts). |
| 25 | +1. Your messages never leave the server and don't need to be serialized for the client side |
| 26 | +2. Library code for internationalization doesn't need to be loaded on the client side |
| 27 | +3. No need to split your messages, e.g. based on routes or components |
| 28 | +4. No runtime cost on the client side |
| 29 | +5. No need to handle environment differences like different time zones on the server and client |
| 30 | + |
| 31 | +## Using internationalization in Server Components |
| 32 | + |
| 33 | +Server Components can be declared in two ways: |
| 34 | + |
| 35 | +1. Async components |
| 36 | +2. Non-async, regular components |
| 37 | + |
| 38 | +In a typical app, you'll likely find both types of components. `next-intl` provides corresponding APIs that work for the given component type. |
| 39 | + |
| 40 | +### Async components |
27 | 41 |
|
28 | | -Hooks are currently primarly known for being used in Client Components since they are typically stateful or don't apply to a server environment. However, hooks like [`useId`](https://react.dev/reference/react/useId) can be used in Server Components too. Similarly, `next-intl` uses a hooks-based API that looks identical, regardless of if it's used in a Server or Client Component. This allows to use hooks like `useTranslations` in [shared components](https://github.com/reactjs/rfcs/blob/bf51f8755ddb38d92e23ad415fc4e3c02b95b331/text/0000-server-components.md#sharing-code-between-server-and-client), which can run both as a Server or a Client Component, depending on where they are imported from. |
| 42 | +These are primarly concerned with fetching data and [can not use hooks](https://github.com/reactjs/rfcs/blob/main/text/0188-server-components.md#capabilities--constraints-of-server-and-client-components). Due to this, `next-intl` provides a set of awaitable versions of the functions that you usually call as hooks from within components. |
29 | 43 |
|
30 | | -The one restriction that currently comes with this pattern is that hooks can not be called from `async` components. To resolve this, |
31 | | -you can split your component into two, leaving the async code in the first one and |
32 | | -moving the usage of the hook to the second one. |
| 44 | +```tsx filename="[locale]/profile/page.tsx" |
| 45 | +import {getTranslations} from 'next-intl/server'; |
33 | 46 |
|
34 | | -**Example:** |
| 47 | +export default async function ProfilePage() { |
| 48 | + const user = await fetchUser(); |
| 49 | + const t = await getTranslations('ProfilePage'); |
35 | 50 |
|
36 | | -```tsx filename="app/[locale]/profile/page.tsx" |
37 | | -export default async function Profile() { |
38 | | - // Use this component for all async code ... |
39 | | - const user = await getUser(); |
40 | | - return <ProfileContent user={user} />; |
| 51 | + return ( |
| 52 | + <PageLayout title={t('title', {username: user.name})}> |
| 53 | + <UserDetails user={user} /> |
| 54 | + </PageLayout> |
| 55 | + ); |
41 | 56 | } |
| 57 | +``` |
| 58 | + |
| 59 | +These functions are available: |
| 60 | + |
| 61 | +```tsx |
| 62 | +import { |
| 63 | + getTranslations, |
| 64 | + getFormatter, |
| 65 | + getNow, |
| 66 | + getTimeZone, |
| 67 | + getMessages, |
| 68 | + getLocale |
| 69 | +} from 'next-intl/server'; |
| 70 | + |
| 71 | +const t = await getTranslations('ProfilePage'); |
| 72 | +const format = await getFormatter(); |
| 73 | +const now = await getNow(); |
| 74 | +const timeZone = await getTimeZone(); |
| 75 | +const messages = await getMessages(); |
| 76 | +``` |
| 77 | + |
| 78 | +### Non-async components [#shared-components] |
| 79 | + |
| 80 | +Components that aren't declared with the `async` keyword and don't use interactive features like `useState`, are referred to as [shared components](https://github.com/reactjs/rfcs/blob/main/text/0188-server-components.md#sharing-code-between-server-and-client). These can render either as a Server or Client Component, depending on where they are imported from. |
42 | 81 |
|
43 | | -function ProfileContent({user}) { |
44 | | - // ... and use this one for rendering the fetched data |
45 | | - const t = useTranslations('ProfileContent'); |
46 | | - return <p><{t('title', {userName: user.name})}/p> |
| 82 | +In Next.js, Server Components are the default, and therefore shared components will typically execute as Server Components. |
| 83 | + |
| 84 | +```tsx filename="UserDetails.tsx" |
| 85 | +import {useTranslations} from 'next-intl'; |
| 86 | + |
| 87 | +export default function UserDetails({user}) { |
| 88 | + const t = useTranslations('UserProfile'); |
| 89 | + |
| 90 | + return ( |
| 91 | + <section> |
| 92 | + <h2>{t('title')}</h2> |
| 93 | + <p>{t('followers', {count: user.numFollowers})}</p> |
| 94 | + </section> |
| 95 | + ); |
47 | 96 | } |
48 | 97 | ``` |
49 | 98 |
|
50 | | -As a benefit, the extracted component now works both in Server as well as Client Components, depending on where it is rendered. |
| 99 | +If you import `useTranslations`, `useFormatter`, `useLocale`, `useNow` and `useTimeZone` from a shared component, `next-intl` will automatically provide an implementation that works best for the environment this component executes in (server or client). |
51 | 100 |
|
52 | | -For edge cases in server-only components, you can use [awaitable APIs from `next-intl`](/docs/environments/metadata-route-handlers). |
| 101 | +<details> |
| 102 | +<summary>How does the Server Components integration work?</summary> |
| 103 | + |
| 104 | +`next-intl` uses [`react-server` conditional exports](https://github.com/reactjs/rfcs/blob/main/text/0227-server-module-conventions.md#react-server-conditional-exports) to load code that is optimized for the usage in Server or Client Components. While configuration for hooks like `useTranslations` is read via `useContext` on the client side, on the server side it is loaded via [`i18n.ts`](/docs/usage/configuration#i18nts). |
| 105 | + |
| 106 | +Hooks are currently primarly known for being used in Client Components since they are typically stateful or don't apply to a server environment. However, hooks like [`useId`](https://react.dev/reference/react/useId) can be used in Server Components too. Similarly, `next-intl` provides a hooks-based API that looks identical, regardless of if it's used in a Server or Client Component. |
| 107 | + |
| 108 | +The one restriction that currently comes with this pattern is that hooks can not be called from `async` components. `next-intl` therefore provides a separate set of [awaitable APIs](#async-components) for this use case. |
53 | 109 |
|
54 | 110 | </details> |
55 | 111 |
|
56 | | -## Benefits of handling i18n in Server Components [#server-components-benefits] |
| 112 | +<details> |
| 113 | +<summary>Should I use async or non-async functions for my components?</summary> |
57 | 114 |
|
58 | | -Moving internationalization to the server side unlocks new levels of performance, leaving the client side for interactive features. |
| 115 | +If you implement components that qualify as shared components, it can be beneficial to implement them as non-async functions. This allows to use these components either in a server or client environment, making them really flexible. Even if you don't intend to to ever run a particular component on the client side, this compatibility can still be helpful, e.g. for simplified testing. However, there's no need to dogmatically use non-async functions exclusively for handling internationalization—use what fits your app best. |
59 | 116 |
|
60 | | -<Callout emoji="✅" title="Benefits of server-side internationalization:"> |
61 | | - <ol className="ml-4 list-decimal"> |
62 | | - <li> |
63 | | - Your messages never leave the server and don't need to be serialized for |
64 | | - the client side |
65 | | - </li> |
66 | | - <li> |
67 | | - Library code for internationalization doesn't need to be loaded on the |
68 | | - client side |
69 | | - </li> |
70 | | - <li>No need to split your messages, e.g. based on routes or components</li> |
71 | | - <li>No runtime cost on the client side</li> |
72 | | - <li> |
73 | | - No need to handle environment differences like different time zones on the |
74 | | - server and client |
75 | | - </li> |
76 | | - </ol> |
77 | | -</Callout> |
| 117 | +</details> |
78 | 118 |
|
79 | 119 | ## Using internationalization in Client Components |
80 | 120 |
|
@@ -211,8 +251,8 @@ export default async function LocaleLayout({children, params: {locale}}) { |
211 | 251 | ``` |
212 | 252 |
|
213 | 253 | <Callout type="warning"> |
214 | | - Note that this is a tradeoff in regard to performance (see [the bullet points |
215 | | - above](#server-components-benefits)). |
| 254 | + Note that this is a tradeoff in regard to performance (see the bullet points |
| 255 | + at the top of this page). |
216 | 256 | </Callout> |
217 | 257 |
|
218 | 258 | ## Troubleshooting |
|
0 commit comments