Skip to content

Commit a0ad04c

Browse files
committed
fix reloadOnPrerender for both routers #2123
Pages Router: was calling reloadResources() on the browser-side i18next instance returned by appWithTranslation, which has no FS backend and is often null during getStaticProps/getServerSideProps. Now reloads on the node-side instance, scoped to the locales × namespaces being shipped, gated on NODE_ENV !== 'production'. App Router: reloadOnPrerender was declared in the config types but never consumed. Now wired up in getT with the same dev-only semantics, deduplicated across getT calls within a render via React's cache(). Examples: app-router-* configs use a dev/prod-split resourceLoader so hot-reload is unaffected by Turbopack/Webpack caching of dynamic import() JSON modules. README documents the same caveat.
1 parent 7ce84a0 commit a0ad04c

17 files changed

Lines changed: 339 additions & 186 deletions

File tree

CHANGELOG.md

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,8 @@
1+
## 16.0.6
2+
3+
- **Pages Router: `reloadOnPrerender` now actually reloads** — the option was previously calling `reloadResources()` on the browser-side i18next instance returned by `appWithTranslation` (which has no FS backend and is often `null` during `getStaticProps`/`getServerSideProps`). It now reloads on the disk-backed node-side instance, scoped to exactly the locales × namespaces being shipped, so edits to locale files appear without restarting `next dev`. Gated behind `NODE_ENV !== 'production'` to keep custom HTTP/locize/chained backends from being refetched on every prerender call. [#2123](https://github.com/i18next/next-i18next/issues/2123)
4+
- **App Router: `reloadOnPrerender` is now wired up** — previously declared in the config types but never consumed. Same dev-only semantics as Pages Router: refetches translations from the configured backend (default FS or `use`-provided custom backend) before each render in development, deduplicated across `getT` calls within a single render via React's `cache()`. No effect in production builds. Note: when using a dynamic `import()`-based `resourceLoader`, hot-reload is bundler-dependent and may stall after the first edit because Turbopack/Webpack cache resolved JSON modules across HMR cycles — see the README "Dev tip" for the dev/prod-split pattern that gives full hot-reload.
5+
16
## 16.0.5
27

38
- **Pages Router: export missing types from `next-i18next/pages`**`TFunction`, `I18n`, `WithTranslation`, `WithTranslationHocType`, and `UseTranslation` types are now properly re-exported, matching the v15 API surface [#2339](https://github.com/i18next/next-i18next/issues/2339)

README.md

Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -87,6 +87,25 @@ The `resourceLoader` uses dynamic `import()` which the bundler can trace, ensuri
8787

8888
> **Tip**: Import `I18nConfig` from `next-i18next/proxy` (not from `next-i18next`) to keep the config file Edge-safe.
8989
90+
> **Dev tip — hot-reloading translations**: set `reloadOnPrerender: process.env.NODE_ENV === 'development'` in your config to refetch translations on every render in dev so edits to locale files appear without restarting `next dev`. The flag is automatically a no-op in production, so it is safe to keep in your committed config — custom backends (HTTP, locize, chained) won't be hit per-request in production builds.
91+
>
92+
> **Caveat with `import()`-based `resourceLoader`**: dynamic `import()` of JSON is cached at the bundler level and is not reliably re-invalidated by Turbopack/Webpack HMR after the first edit, so hot-reload can stall after one change. For full hot-reload during development, gate your loader so dev uses `fs.readFile` and production keeps bundler-traceable `import()`:
93+
> ```ts
94+
> const resourceLoader: I18nConfig['resourceLoader'] =
95+
> process.env.NODE_ENV === 'development'
96+
> ? async (lng, ns) => {
97+
> const fs = await import('fs/promises')
98+
> const path = await import('path')
99+
> const content = await fs.readFile(
100+
> path.resolve(process.cwd(), `app/i18n/locales/${lng}/${ns}.json`),
101+
> 'utf-8'
102+
> )
103+
> return JSON.parse(content)
104+
> }
105+
> : (lng, ns) => import(`./app/i18n/locales/${lng}/${ns}.json`)
106+
> ```
107+
> Pages Router and the App Router default backend already use `fs` and are unaffected.
108+
90109
### 4. Proxy
91110
92111
Create `proxy.ts` at your project root (Next.js 16+ replaces `middleware.ts` with `proxy.ts`):
Lines changed: 20 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,30 @@
1+
// In dev, read from disk so `reloadOnPrerender` can pick up edits without a
2+
// dev-server restart — bundlers cache dynamic `import()` JSON modules across
3+
// HMR cycles, which would otherwise stall hot-reload after the first edit.
4+
// In production, use bundler-traceable `import()` so locale files end up in
5+
// the serverless function bundle (Vercel/Lambda/etc.).
6+
const resourceLoader =
7+
process.env.NODE_ENV === 'development'
8+
? async (language, namespace) => {
9+
const fs = await import('fs/promises')
10+
const path = await import('path')
11+
const content = await fs.readFile(
12+
path.resolve(process.cwd(), `app/i18n/locales/${language}/${namespace}.json`),
13+
'utf-8'
14+
)
15+
return JSON.parse(content)
16+
}
17+
: (language, namespace) => import(`./app/i18n/locales/${language}/${namespace}.json`)
18+
119
/** @type {import('next-i18next/proxy').I18nConfig} */
220
const i18nConfig = {
321
supportedLngs: ['en', 'de', 'it'],
422
fallbackLng: 'en',
523
defaultNS: 'translation',
624
localeInPath: false,
725
ns: ['translation', 'footer', 'client-page', 'second-page', 'second-client-page'],
8-
resourceLoader: (language, namespace) => import(`./app/i18n/locales/${language}/${namespace}.json`),
26+
resourceLoader,
27+
reloadOnPrerender: process.env.NODE_ENV === 'development',
928
}
1029

1130
module.exports = i18nConfig

examples/app-router-no-locale-path/package.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -14,7 +14,7 @@
1414
"dependencies": {
1515
"i18next": "^26.0.1",
1616
"next": "16.2.1",
17-
"next-i18next": "^16.0.4",
17+
"next-i18next": "^16.0.6",
1818
"react": "19.2.4",
1919
"react-dom": "19.2.4",
2020
"react-i18next": "^17.0.1"
Lines changed: 20 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,30 @@
11
import type { I18nConfig } from 'next-i18next'
22

3+
// In dev, read from disk so `reloadOnPrerender` can pick up edits without a
4+
// dev-server restart — bundlers cache dynamic `import()` JSON modules across
5+
// HMR cycles, which would otherwise stall hot-reload after the first edit.
6+
// In production, use bundler-traceable `import()` so locale files end up in
7+
// the serverless function bundle (Vercel/Lambda/etc.).
8+
const resourceLoader: I18nConfig['resourceLoader'] =
9+
process.env.NODE_ENV === 'development'
10+
? async (language, namespace) => {
11+
const fs = await import('fs/promises')
12+
const path = await import('path')
13+
const content = await fs.readFile(
14+
path.resolve(process.cwd(), `app/i18n/locales/${language}/${namespace}.json`),
15+
'utf-8'
16+
)
17+
return JSON.parse(content)
18+
}
19+
: (language, namespace) => import(`./app/i18n/locales/${language}/${namespace}.json`)
20+
321
const i18nConfig: I18nConfig = {
422
supportedLngs: ['en', 'de', 'it'],
523
fallbackLng: 'en',
624
defaultNS: 'translation',
725
ns: ['translation', 'footer', 'client-page', 'second-page', 'second-client-page'],
8-
resourceLoader: (language: string, namespace: string) => import(`./app/i18n/locales/${language}/${namespace}.json`),
26+
resourceLoader,
27+
reloadOnPrerender: process.env.NODE_ENV === 'development',
928
}
1029

1130
export default i18nConfig

examples/app-router-simple/package.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -17,7 +17,7 @@
1717
"dependencies": {
1818
"i18next": "^26.0.1",
1919
"next": "16.2.1",
20-
"next-i18next": "^16.0.4",
20+
"next-i18next": "^16.0.6",
2121
"react": "19.2.4",
2222
"react-dom": "19.2.4",
2323
"react-i18next": "^17.0.1"

examples/mixed-routers/package.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -11,7 +11,7 @@
1111
"dependencies": {
1212
"i18next": "^26.0.1",
1313
"next": "^16.2.1",
14-
"next-i18next": "^16.0.4",
14+
"next-i18next": "^16.0.6",
1515
"react": "^19.2.4",
1616
"react-dom": "^19.2.4",
1717
"react-i18next": "^17.0.1"

examples/pages-router-auto-static-optimize/package.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -20,7 +20,7 @@
2020
"i18next-http-backend": "^3.0.2",
2121
"i18next-localstorage-backend": "^4.3.1",
2222
"next": "^16.2.1",
23-
"next-i18next": "^16.0.4",
23+
"next-i18next": "^16.0.6",
2424
"react": "^19.2.4",
2525
"react-dom": "^19.2.4",
2626
"react-i18next": "^17.0.1"

examples/pages-router-simple/package.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -17,7 +17,7 @@
1717
"dependencies": {
1818
"i18next": "^26.0.1",
1919
"next": "^16.2.1",
20-
"next-i18next": "^16.0.4",
20+
"next-i18next": "^16.0.6",
2121
"react": "^19.2.4",
2222
"react-dom": "^19.2.4",
2323
"react-i18next": "^17.0.1"

examples/pages-router-ssg/package.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -18,7 +18,7 @@
1818
"dependencies": {
1919
"i18next": "^26.0.1",
2020
"next": "16.2.1",
21-
"next-i18next": "^16.0.4",
21+
"next-i18next": "^16.0.6",
2222
"next-language-detector": "^1.1.1",
2323
"react": "^19.2.4",
2424
"react-dom": "^19.2.4",

0 commit comments

Comments
 (0)