[dynamicIO] No longer possible to set dynamic <html> attributes. I18n is broken. Framework solution needed. #71927
-
Next.js v15+ with dynamicIO enabledAfter the change to pass all dynamic data ( Since accessing dynamic data requires a
Caution Filling a cache during prerender timed out like because request specific arguments such as params, searchParams, cookies() or dynamic data was used inside the "use cache". These are all expected behavior, however one very important use case feels impossible to implement; Internationalization. Basically this entire documentation page is obsolete. The following code is no longer valid: export async function generateStaticParams() {
return [{ lang: 'en-US' }, { lang: 'de' }]
}
export default function Root({ children, params }) {
return (
<html lang={params.lang}>
<body>{children}</body>
</html>
)
} This is very unfortunate. And similarly you cannot set the Failed attempts to fix the problem
Hacky workaroundTo fix the I first set a client cookie in the middleware. Then I read the cookie value and modify the element synchronously, suppressing hydration warnings on the export default function Layout({ children }) {
return (
<html suppressHydrationWarning={true}>
<head>
<script>{langCode}</script>
</head>
<body>{children}</body>
</html>
);
} Where const code = `(${() => {
const cookies = String(document.cookie);
const pos = cookies.indexOf('__Host-Lang=');
if (pos > -1) {
const endPos = cookies.indexOf(';', pos);
const cookieValue = cookies.slice(pos + 12, endPos > -1 ? endPos : undefined);
document.documentElement.lang = cookieValue;
document.documentElement.dir = ['ar', 'fa', 'he', 'ps', 'ur'].includes(cookieValue.slice(0, 2)) ? 'rtl' : 'ltr';
}
}})();`;
// The code should be injected in the page before any other script so it runs before React hydration
export default code; Unfortunately, this doesn't solve situations when the language depends on outside data (e.g. an article from a CMS). Or if you want to change the language without resorting to full-page navigation. Solution neededI don't know what a React-native or Next.js-native solution for this problem could look like. To me it feels more of a framework oversight than something that React should address. So, a solution from Next.js seems more appropriate. Some have suggested adding attributes support in Maybe some kind of middleware api could handle all this. I don't know... The |
Beta Was this translation helpful? Give feedback.
Replies: 3 comments 7 replies
-
The main thing that's currently missing is the flag (It will also be possible to use a Suspense boundary above html but that's not the best solution since it makes the whole page dynamic.) |
Beta Was this translation helpful? Give feedback.
-
I had to comment out a bunch of stuff to test this, but I'm correctly able to set dynamic parameters on the html tag. Just put "use cache" at the top of the [locale]/layout.tsx "use cache"
import { hasLocale, Locale } from "next-intl"
import { routing } from "@/i18n/routing"
import { Inter } from "next/font/google"
import { cn } from "@/lib/utils"
import { Providers } from "../providers"
import { notFound } from "next/navigation"
import { getMessages, setRequestLocale } from "next-intl/server"
import NextIntlProvider from "./next-intl-provider"
const inter = Inter({ subsets: ["latin"] })
export default async function LocaleLayout({
children,
params
}: {
children: React.ReactNode
params: Promise<{ locale: Locale }>
}) {
const { locale } = await params
// Ensure that the incoming `locale` is valid
if (!hasLocale(routing.locales, locale)) {
notFound()
}
// Enable static rendering
setRequestLocale(locale)
// Providing all messages to the client
// side is the easiest way to get started
const messages = await getMessages({ locale })
return (
<html key="html" lang={locale} suppressHydrationWarning>
<body>
<NextIntlProvider locale={locale} messages={messages}>
<Providers>
<div
className={cn(inter.className,
"flex flex-col w-svw min-h-svh bg-gradient-to-r from-blue-100 to-purple-100 dark:from-blue-950/50 dark:to-purple-950/50"
)}
>
<div className="px-safe pb-safe flex flex-col grow">
{children}
</div>
</div>
</Providers>
</NextIntlProvider>
</body>
</html>
)
}
export async function generateStaticParams() {
return routing.locales.map((locale) => ({ locale }))
} Only issue here is using any of the next-intl stuff, it complains about using "headers" I'll keep playing around with dynamicIO |
Beta Was this translation helpful? Give feedback.
-
import { unstable_rootParams as rootParams } from 'next/server';
// i18n stuff
import { locales } from '#lang/config.ts';
import { langDir } from '#lang/utils.ts';
export async function generateStaticParams() {
return locales.map((locale) => ({ locale }));
}
export default async function RootLayout({ children }: React.PropsWithChildren) {
const { locale } = await rootParams();
if (typeof locale !== 'string') {
locale = 'en-GB';
}
return (
<html lang={locale} dir={langDir(locale)}>
<body>
{children}
</body>
</html>
);
} |
Beta Was this translation helpful? Give feedback.
The main thing that's currently missing is the flag
dynamicParams = false
. You'll notice that this is disabled indynamicIO
because we have plans for a different alternative. Once that's in then you'll be able to await the params without a Suspense boundary since they will all be known statically (fromgenerateStaticParams
).(It will also be possible to use a Suspense boundary above html but that's not the best solution since it makes the whole page dynamic.)