Search code examples
reactjsnext.jsnext-intl

In HTML, <html> cannot be a child of <body>, This will cause a hydration error


I got an error when using next-intl.

this is my first time using this library. i don't know if i have setup correctly.

this is my code

import React from 'react';
import localFont from 'next/font/local';
import { NextIntlClientProvider } from 'next-intl';
import { getMessages } from 'next-intl/server';
import { ReactNode } from 'react';
import { ToastProvider } from '@/providers/toaster-provider';
import BackToTopProvider from '@/providers/back-to-top-provider';
import { AuthProvider } from '@/contexts/AuthContext';
import type { Metadata } from 'next';

const lora = localFont({
  src: '../fonts/Lora.ttf',
  variable: '--font-lora',
  weight: '400 500 600 700 800',
});

const notoSans = localFont({
  src: '../fonts/NotoSansSC.ttf',
  variable: '--font-noto-sans',
  weight: '400 500 600 700 800',
});

export const metadata: Metadata = {
  title: 'Create Next App',
  description: 'Generated by create next app',
};

type Props = {
  children: ReactNode;
  locale: string;
};

// Fix: Correct async function declaration
export default async function RootLayout({ children, locale }: Props) {
  const messages = await getMessages({ locale });

  return (
    <html lang={locale}>
      <body className={`${lora.variable} ${notoSans.variable}`}>
        <NextIntlClientProvider messages={messages}>
          <AuthProvider>
            <BackToTopProvider>
              <ToastProvider />
              {children}
            </BackToTopProvider>
          </AuthProvider>
        </NextIntlClientProvider>
      </body>
    </html>
  );
}

folder struct

folder structure

image error

This error I don't know where it starts. I only know if when and page not found page will appear. or return to the old page will be.

below is my notfound page

not-found.tsx

import RootLayout from '@/layout/RootLayout';
import React from 'react';
import { routing } from '@/i18n/routing';
import { useTranslations } from 'next-intl';

const NotFoundPage = () => {
  const language = useTranslations('notFound');
  return (
    <RootLayout locale={routing.defaultLocale}>
      <div className='not-found-page'>
        <div className='not-found-container'>
          <h1> {language('title')}</h1>
          <h2>{language('content')}</h2>
          <p>{language('description')}</p>
        </div>
      </div>
    </RootLayout>
  );
};

export default NotFoundPage;

[locale/not-found-.tsx]

export { default } from '@/app/not-found';


Solution

  • You're importing RootLayout and using it to wrap NotFoundPage content, this is unnecessary as you can simply write the content, and the layout.tsx will do the job of wrapping it. I'm supposing you don't know this, so i'll explain, when you have a layout.tsx at the same level as an ordinary page or in your case a not-found.tsx page, the layout will wrap the pages of that same level and pages of lower levels, so you don't really have to import the layout whenever there's a page at the same level as the layout. Here's the solution:

    // not-found.tsx
    
    // No need to import RootLayout
    import React from 'react';
    import { routing } from '@/i18n/routing';
    import { useTranslations } from 'next-intl';
    
    const NotFoundPage = () => {
      const language = useTranslations('notFound');
      return (
          <div className='not-found-page'>
            <div className='not-found-container'>
              <h1> {language('title')}</h1>
              <h2>{language('content')}</h2>
              <p>{language('description')}</p>
            </div>
          </div>
      );
    };
    
    export default NotFoundPage;
    
    // layout.tsx
    
    /* imports and other declarations */
    
    export default async function RootLayout({ children, locale }: Props) {
      const messages = await getMessages({ locale });
    
      return (
        <html lang={locale}> {/*if you want to use routing.defaultLocale you should use it here*/}
          <body className={`${lora.variable} ${notoSans.variable}`}>
            <NextIntlClientProvider messages={messages}>
              <AuthProvider>
                <BackToTopProvider>
                  <ToastProvider />
                  {children}
                </BackToTopProvider>
              </AuthProvider>
            </NextIntlClientProvider>
          </body>
        </html>
      );
    }