next-export-i18n - Error: Text content does not match server-rendered HTML.
Answered
Indian Spitz posted this in #help-forum
Indian SpitzOP
Hey. I use "next": "14.0.4" and would like to do a static export of pages (output: "export"). I also would like to have an i18n solution.
After doing some research I stumbled upon https://www.npmjs.com/package/next-export-i18n and I use:
"next-export-i18n": "^3.0.0",
"next-language-detector": "^1.1.0"
But now I get the following error:
Unhandled Runtime Error
Error: Text content does not match server-rendered HTML.
Warning: Text content did not match. Server: "base.displaying" Client: "Darstellung"
See more info here: https://nextjs.org/docs/messages/react-hydration-error
Is there a solution for this?
After doing some research I stumbled upon https://www.npmjs.com/package/next-export-i18n and I use:
"next-export-i18n": "^3.0.0",
"next-language-detector": "^1.1.0"
But now I get the following error:
Unhandled Runtime Error
Error: Text content does not match server-rendered HTML.
Warning: Text content did not match. Server: "base.displaying" Client: "Darstellung"
See more info here: https://nextjs.org/docs/messages/react-hydration-error
Is there a solution for this?
Answered by Ray
have you tried the method on the doc?
https://nextjs.org/docs/app/building-your-application/routing/internationalization
https://nextjs.org/docs/app/building-your-application/routing/internationalization
130 Replies
Indian SpitzOP
Heyhey
Well the question is: What is the best way, to use i18n if next static export is used?
I just need a simple solution for i18n. Not a dedicated routing / context root... Just a simple t("languagekey") i18n possibility
I have had a look at the next recommended way but it seems that it is not supported with static export
It always said:
"Error: i18n support is not compatible with next export. See here for more info on deploying:"
"Error: i18n support is not compatible with next export. See here for more info on deploying:"
have you tried the method on the doc?
https://nextjs.org/docs/app/building-your-application/routing/internationalization
https://nextjs.org/docs/app/building-your-application/routing/internationalization
Answer
@Ray have you tried the method on the doc?
https://nextjs.org/docs/app/building-your-application/routing/internationalization
Indian SpitzOP
I have had a look. But I don't want to have a subroute / subcontext
what do you mean subroute/ subcontext?
Indian SpitzOP
this /en/ or /fr/
you can skip that part
Indian SpitzOP
Ok. I will give it another try
And there is the next question
Do I need to wrap ALL my folders & files into the [lang] folder?
Or only the ones for the pages?
how do you want your url look like?
Indian SpitzOP
The uri should not change. That's the thing
you mentioned you don't want to have subroute/subcontext
so base on the user browser?
Indian SpitzOP
xyz.shop/product/schliesszylinder
for instance
for instance
@Ray so base on the user browser?
Indian SpitzOP
Yes
it will need middleware for that
which is not supported in static export
Indian SpitzOP
And if I say: Ok, default language should be FR and if not available, then use EN
?
Because I know my shop people will speak French as an example
If I just set it hardcoded as of now
(or add a language selector later on)
then its better to do this
app/[lang]/product/[category]Indian SpitzOP
Ok. So I create [lang] and only move my pages there right?
The components can stay in app/components?
yes
Indian SpitzOP
But how can I use the i18n in components then?
Do I need to pull all translations through all components?
Or redux?
import { getDictionary } from './dictionaries'
export default async function Page({ params: { lang } }) {
const dict = await getDictionary(lang)
return <Client productData dict={dict} />
}you can pass the dictionary to it
Indian SpitzOP
That is going to be really ugly
Because I have sometimes 15 components wrapped
And they all need to use it
Pushing these into redux probably is also not the nicest idea or what do you think?
can you pass it the redux store inside the Client component?
Indian SpitzOP
Probably
or you could try using
next-intlIndian SpitzOP
One question I have still in mind
If I do app[lang] then how can I NOT provide a subcontext
As I said, I don't want this /de/ or so
@Indian Spitz If I do app[lang] then how can I NOT provide a subcontext
I think you can do
app/product/[...params]/page.tsxthen in the page component
export default async function Page({ params }: { params: { params: string[] } }}) {
const category = params[0]
if (!category) notFound()
const locale = params[1] || "fr"
}Indian SpitzOP
Like that?
oh should be params.params[0] and params.params[1]
this will look better
export default async function Page({ params }: { params: { params: string[] } }}) {
const [category, locale = "fr"] = params.params
if (!category) notFound()
}Indian SpitzOP
then the english url will be
/product/category/enupdate the generateStaticParams too
export async function generateStaticParams() {
const locales = [undefined, "en"]
const allCategorySlugs = ...
return locales.map(locale => allCategorySlugs.map(category => ({ params: [category, locale] })) )
}Indian SpitzOP
Ok done
does it work?
Indian SpitzOP
I need to do the redirect in the page.tsx in app/ right?
i dont know, you don't have the index page?
Indian SpitzOP
http://localhost:3000/ says not found
Probably I deleted it accidentally
what is the code in
app/page.tsxIndian SpitzOP
The same as in app/product/[...params]/page.tsx SO:
import { ProductPath } from "@/global/consts";
import readProductDataForCategory from "@/lib/api/readProductDataForCategory";
import { enumToArray } from "@/utils/enumToArray";
import { notFound } from "next/navigation";
import Client from "./product/[...params]/client";
import { getDictionary } from "./product/[...params]/dictionaries";
export default async function Page({
params,
}: {
params: { params: string[] };
}) {
const [category, locale = "de"] = params.params;
if (!category) notFound();
const dict = await getDictionary(locale);
const productData: any = await readProductDataForCategory(category);
return <Client productData={productData} dict={dict} />;
}
export async function generateStaticParams() {
const locales = ["de", "en"];
const allCategorySlugs: any = enumToArray(ProductPath).map((p) => ({
category: p,
}));
return locales.map((locale) =>
allCategorySlugs.map((category) => ({ params: [category, locale] }))
);
}
import readProductDataForCategory from "@/lib/api/readProductDataForCategory";
import { enumToArray } from "@/utils/enumToArray";
import { notFound } from "next/navigation";
import Client from "./product/[...params]/client";
import { getDictionary } from "./product/[...params]/dictionaries";
export default async function Page({
params,
}: {
params: { params: string[] };
}) {
const [category, locale = "de"] = params.params;
if (!category) notFound();
const dict = await getDictionary(locale);
const productData: any = await readProductDataForCategory(category);
return <Client productData={productData} dict={dict} />;
}
export async function generateStaticParams() {
const locales = ["de", "en"];
const allCategorySlugs: any = enumToArray(ProductPath).map((p) => ({
category: p,
}));
return locales.map((locale) =>
allCategorySlugs.map((category) => ({ params: [category, locale] }))
);
}
oh
set the default value for category in
app/page.tsxor redirect it
you don't need
generateStaticParams in app/page.tsxIndian SpitzOP
Ok, I was a bit confused
Fixed it now
But what about the layout.tsx in the app folder?
Should it make usage of params.lang or shall I set a default here?
export async function generateStaticParams() {
return [{ lang: "en-US" }, { lang: "de" }];
}
const RootLayout = ({ children, params }) => (
<html lang={params.lang}>
<body className={inter.className}>
<StyledComponentsRegistry>{children}</StyledComponentsRegistry>
</body>
</html>
);
export default RootLayout;
return [{ lang: "en-US" }, { lang: "de" }];
}
const RootLayout = ({ children, params }) => (
<html lang={params.lang}>
<body className={inter.className}>
<StyledComponentsRegistry>{children}</StyledComponentsRegistry>
</body>
</html>
);
export default RootLayout;
Because for the entry point we do not even have params correct?
@Indian Spitz export async function generateStaticParams() {
return [{ lang: "en-US" }, { lang: "de" }];
}
const RootLayout = ({ children, params }) => (
<html lang={params.lang}>
<body className={inter.className}>
<StyledComponentsRegistry>{children}</StyledComponentsRegistry>
</body>
</html>
);
export default RootLayout;
yes put this in
app/product/[...params]/layout.tsxexport async function generateStaticParams() {
return [{ lang: "en-US" }, { lang: "de" }];
}
const RootLayout = ({ children, params }) => (
<html lang={params.lang}>
<body>
{children}
</body>
</html>
);just this is fine
Indian SpitzOP
Yes, this is clear
But my question is as well: If I want to have i18n from the very beginning on (meaning from app/page.tsx) if I call localhost:3000, then I would also need to do something for it right?
Because app/page.tsx looks like this at the moment:
"use client";
import styles from "../styles/page.module.scss";
import { NavCategories } from "@/app/components/Navigation/NavCategories/NavCategories";
import PageLayout from "@/app/components/PageLayout/PageLayout";
import { Typography } from "antd";
export default function Page() {
return (
#Unknown Channel
<PageLayout>
<Typography.Title className={styles.pageWelcomeTitle}>
Hello World:
</Typography.Title>
<NavCategories />
</PageLayout>
</>
);
}
"use client";
import styles from "../styles/page.module.scss";
import { NavCategories } from "@/app/components/Navigation/NavCategories/NavCategories";
import PageLayout from "@/app/components/PageLayout/PageLayout";
import { Typography } from "antd";
export default function Page() {
return (
#Unknown Channel
<PageLayout>
<Typography.Title className={styles.pageWelcomeTitle}>
Hello World:
</Typography.Title>
<NavCategories />
</PageLayout>
</>
);
}
Here I also need:
export default async function Page({
params,
}: {
params: { params: string[] };
}) {
const [category, locale = "de"] = params.params;
if (!category) notFound();
const dict = await getDictionary(locale);
correct?
export default async function Page({
params,
}: {
params: { params: string[] };
}) {
const [category, locale = "de"] = params.params;
if (!category) notFound();
const dict = await getDictionary(locale);
correct?
for
app/page.tsxexport default async function Page() {
const dict = await getDictionary("de");Indian SpitzOP
app-index.js:34 ./app\product[...params]\dictionaries.ts
ReactServerComponentsError:
You're importing a component that needs server-only. That only works in a Server Component but one of its parents is marked with "use client", so it's a Client Component.
ReactServerComponentsError:
You're importing a component that needs server-only. That only works in a Server Component but one of its parents is marked with "use client", so it's a Client Component.
But if I remove the use client, I get this:
you should only import
getDictionary in server componentseem like you are importing it a client component
Indian SpitzOP
So I need to create a app/client.tsx file?
Same as you've shown once for the pages
you have 'use client' on top of
app/page.tsx?yes you need to do that too
@Ray you have 'use client' on top of `app/page.tsx`?
Indian SpitzOP
Yes
Indian SpitzOP
Ok somewhere in the product routes it still has a problem
app\product[...params]\page.tsx looks like this:
import { ProductPath } from "@/global/consts";
import readProductDataForCategory from "@/lib/api/readProductDataForCategory";
import { enumToArray } from "@/utils/enumToArray";
import Client from "./client";
import { getDictionary } from "./dictionaries";
import { notFound } from "next/navigation";
export default async function Page({
params,
}: {
params: { params: string[] };
}) {
const [category, locale = "de"] = params.params;
if (!category) notFound();
const dict = await getDictionary(locale);
const productData: any = await readProductDataForCategory(category);
return <Client productData={productData} dict={dict} />;
}
export async function generateStaticParams() {
const locales = ["de", "en"];
const allCategorySlugs: any = enumToArray(ProductPath).map((p) => ({
category: p,
}));
return locales.map((locale) =>
allCategorySlugs.map((category) => ({ params: [category, locale] }))
);
}
import { ProductPath } from "@/global/consts";
import readProductDataForCategory from "@/lib/api/readProductDataForCategory";
import { enumToArray } from "@/utils/enumToArray";
import Client from "./client";
import { getDictionary } from "./dictionaries";
import { notFound } from "next/navigation";
export default async function Page({
params,
}: {
params: { params: string[] };
}) {
const [category, locale = "de"] = params.params;
if (!category) notFound();
const dict = await getDictionary(locale);
const productData: any = await readProductDataForCategory(category);
return <Client productData={productData} dict={dict} />;
}
export async function generateStaticParams() {
const locales = ["de", "en"];
const allCategorySlugs: any = enumToArray(ProductPath).map((p) => ({
category: p,
}));
return locales.map((locale) =>
allCategorySlugs.map((category) => ({ params: [category, locale] }))
);
}
does it work before adding i18n?
Indian SpitzOP
Good question
I have done so much code changes
lol
Indian SpitzOP
Good thing is: I can start the application. And I can see the translations. So this part works
But once a product page is opened, I get the error
export default async function Page({
params,
}: {
params: { params: string[] };
}) {
const [category, locale = "de"] = params.params;
if (!category) notFound();
const dict = await getDictionary(locale);
const productData: any = await readProductDataForCategory(category);
return <div>test</div>
}try this and see if it still error
Indian SpitzOP
Error is still there
export default async function Page({
params,
}: {
params: { params: string[] };
}) {
const [category, locale = "de"] = params.params;
if (!category) notFound();
//const dict = await getDictionary(locale);
const productData: any = await readProductDataForCategory(category);
return <div>test</div>
}comment out getDictionary
Indian SpitzOP
This I have tried as well. It does not come from the dictionary it seems
// const productData: any = await readProductDataForCategory(category);Indian SpitzOP
Also not. I have even tried to return static <h1> in client.tsx
SyntaxError: Cannot use import statement outside a module
at internalCompileFunction (node:internal/vm:77:18)
at wrapSafe (node:internal/modules/cjs/loader:1288:20)
at Module._compile (node:internal/modules/cjs/loader:1340:27)
at Module._extensions..js (node:internal/modules/cjs/loader:1435:10)
at Module.load (node:internal/modules/cjs/loader:1207:32)
at Module._load (node:internal/modules/cjs/loader:1023:12)
at Module.require (node:internal/modules/cjs/loader:1235:19)
at mod.require (C:\Users\siri\Desktop\product-recommendovic\node_modules\next\dist\server\require-hook.js:65:28)
at require (node:internal/modules/helpers:176:18)
at @ant-design/icons/es/components/Context (C:\Users\siri\Desktop\product-recommendovic.next\server\pages_app.js:62:18)
at internalCompileFunction (node:internal/vm:77:18)
at wrapSafe (node:internal/modules/cjs/loader:1288:20)
at Module._compile (node:internal/modules/cjs/loader:1340:27)
at Module._extensions..js (node:internal/modules/cjs/loader:1435:10)
at Module.load (node:internal/modules/cjs/loader:1207:32)
at Module._load (node:internal/modules/cjs/loader:1023:12)
at Module.require (node:internal/modules/cjs/loader:1235:19)
at mod.require (C:\Users\siri\Desktop\product-recommendovic\node_modules\next\dist\server\require-hook.js:65:28)
at require (node:internal/modules/helpers:176:18)
at @ant-design/icons/es/components/Context (C:\Users\siri\Desktop\product-recommendovic.next\server\pages_app.js:62:18)
Somehow it talks about C:\Users\siri\Desktop\product-recommendovic.next\server\pages_app.js:196:3
what is
enumToArray?Indian SpitzOP
type NonFunctional<T> = T extends Function ? never : T;
/**
* Helper to produce an array of enum values.
* @param enumeration Enumeration object.
*/
export function enumToArray<T>(enumeration: T): NonFunctional<T[keyof T]>[] {
return Object.keys(enumeration)
.filter((key) => isNaN(Number(key)))
.map((key) => enumeration[key])
.filter((val) => typeof val === "number" || typeof val === "string");
}
/**
* Helper to produce an array of enum values.
* @param enumeration Enumeration object.
*/
export function enumToArray<T>(enumeration: T): NonFunctional<T[keyof T]>[] {
return Object.keys(enumeration)
.filter((key) => isNaN(Number(key)))
.map((key) => enumeration[key])
.filter((val) => typeof val === "number" || typeof val === "string");
}
change the
generateStaticParams to thisexport async function generateStaticParams() {
// const locales = ["de", "en"];
// const allCategorySlugs: any = enumToArray(ProductPath).map((p) => ({
// category: p,
// }));
// return locales.map((locale) =>
// allCategorySlugs.map((category) => ({ //params: [category, locale] }))
// );
return [{ params: ["some-category-name", "en"] }]
}Indian SpitzOP
I deleted the next folder once and in the console I see this now:
Warning: [antd: Breadcrumb]
â—‹ Compiling /product/[...params] ...
✓ Compiled /product/[...params] in 1930ms (5127 modules)
⨯ Error: Page "/product/[...params]/page" is missing param "/product/schliesszylinder" in "generateStaticParams()", which is required with "output: export" config.
at DevServer.renderToResponseWithComponentsImpl (C:\Users\siri\Desktop\product-recommendovic\node_modules\next\dist\server\base-server.js:1046:27)
at process.processTicksAndRejections (node:internal/process/task_queues:95:5)
at async DevServer.renderPageComponent (C:\Users\siri\Desktop\product-recommendovic\node_modules\next\dist\server\base-server.js:1843:24)
at async DevServer.renderToResponseImpl (C:\Users\siri\Desktop\product-recommendovic\node_modules\next\dist\server\base-server.js:1881:32)
at async DevServer.pipeImpl (C:\Users\siri\Desktop\product-recommendovic\node_modules\next\dist\server\base-server.js:909:25)
at async NextNodeServer.handleCatchallRenderRequest (C:\Users\siri\Desktop\product-recommendovic\node_modules\next\dist\server\next-server.js:266:17)
at async DevServer.handleRequestImpl (C:\Users\siri\Desktop\product-recommendovic\node_modules\next\dist\server\base-server.js:805:17) {
page: '/product/schliesszylinder'
}
Warning: [antd: Breadcrumb]
Breadcrumb.Item and Breadcrumb.Separator is deprecated. Please use items instead.â—‹ Compiling /product/[...params] ...
✓ Compiled /product/[...params] in 1930ms (5127 modules)
⨯ Error: Page "/product/[...params]/page" is missing param "/product/schliesszylinder" in "generateStaticParams()", which is required with "output: export" config.
at DevServer.renderToResponseWithComponentsImpl (C:\Users\siri\Desktop\product-recommendovic\node_modules\next\dist\server\base-server.js:1046:27)
at process.processTicksAndRejections (node:internal/process/task_queues:95:5)
at async DevServer.renderPageComponent (C:\Users\siri\Desktop\product-recommendovic\node_modules\next\dist\server\base-server.js:1843:24)
at async DevServer.renderToResponseImpl (C:\Users\siri\Desktop\product-recommendovic\node_modules\next\dist\server\base-server.js:1881:32)
at async DevServer.pipeImpl (C:\Users\siri\Desktop\product-recommendovic\node_modules\next\dist\server\base-server.js:909:25)
at async NextNodeServer.handleCatchallRenderRequest (C:\Users\siri\Desktop\product-recommendovic\node_modules\next\dist\server\next-server.js:266:17)
at async DevServer.handleRequestImpl (C:\Users\siri\Desktop\product-recommendovic\node_modules\next\dist\server\base-server.js:805:17) {
page: '/product/schliesszylinder'
}
Indian SpitzOP
It clearly says: " Error: Page "/product/[...params]/page" is missing param "/product/schliesszylinder" in "generateStaticParams()", which is required with "output: export" config." though
Is it really correct like that?
This is it as JSON:
@Ray update the generateStaticParams too
ts
export async function generateStaticParams() {
const locales = [undefined, "en"]
const allCategorySlugs = ...
return locales.map(locale => allCategorySlugs.map(category => ({ params: [category, locale] })) )
}
notice that the first element in array is undefined
Indian SpitzOP
Ok so what should I try?
undefined outputs the same error
This is what it looks like in total:
client.tsx:
"use client";
import ProductPageLayout from "@/app/components/PageLayout/ProductPageLayout";
import { ProductOverview } from "@/app/components/Product/ProductOverview/ProductOverview";
import ReduxProvider from "@/app/store/ReduxProvider";
export default function Client({ productData, t }) {
return (
<ReduxProvider>
<ProductPageLayout>
<ProductOverview productData={productData} t={t} />
</ProductPageLayout>
</ReduxProvider>
);
}
"use client";
import ProductPageLayout from "@/app/components/PageLayout/ProductPageLayout";
import { ProductOverview } from "@/app/components/Product/ProductOverview/ProductOverview";
import ReduxProvider from "@/app/store/ReduxProvider";
export default function Client({ productData, t }) {
return (
<ReduxProvider>
<ProductPageLayout>
<ProductOverview productData={productData} t={t} />
</ProductPageLayout>
</ReduxProvider>
);
}
can you liveshare
Indian SpitzOP
Of course
Indian SpitzOP
Solved! thanks a lot