본문 바로가기
공부한것/React

[NEXT15] Hydration failed because the server rendered HTML didn't match the client. As a result this tree will be regenerated on the client. This can happen if a SSR-ed Client Component used 오류 해결

by flyda 2025. 1. 15.
Hydration failed because the server rendered HTML didn't match the client. As a result this tree will be regenerated on the client. This can happen if a SSR-ed Client Component used

- A server/client branch `if (typeof window !== 'undefined')`.
- Variable input such as `Date.now()` or `Math.random()` which changes each time it's called.
- Date formatting in a user's locale which doesn't match the server.
- External changing data without sending a snapshot of it along with the HTML.
- Invalid HTML tag nesting.

It can also happen if the client has a browser extension installed which messes with the HTML before React loaded.

See more info here: https://nextjs.org/docs/messages/react-hydration-error

콘솔에는 이렇게 찍히고 있어서 글꼴을 잘못 설정한건가 했다. 

 

하지만 문제는 이 두 파일이었다...

app/layout.tsx 

import localFont from "next/font/local";
import { ReactNode } from "react";
import "./globals.css";

const GothicA1 = localFont({
  src: [
    { path: "/fonts/GothicA1-Regular.ttf", weight: "400", style: "normal" },
    { path: "/fonts/GothicA1-Medium.ttf", weight: "500", style: "normal" },
    { path: "/fonts/GothicA1-SemiBold.ttf", weight: "600", style: "normal" },
    { path: "/fonts/GothicA1-Bold.ttf", weight: "700", style: "normal" },
  ],
  variable: "--gothic-a1",
});

const IBMPlexSans = localFont({
  src: [
    { path: "/fonts/IBMPlexSans-Regular.ttf", weight: "400", style: "normal" },
    { path: "/fonts/IBMPlexSans-Medium.ttf", weight: "500", style: "normal" },
    {
      path: "/fonts/IBMPlexSans-SemiBold.ttf",
      weight: "600",
      style: "normal",
    },
    { path: "/fonts/IBMPlexSans-Bold.ttf", weight: "700", style: "normal" },
  ],
  variable: "--ibm-plex-sans",
});

const BebasNeue = localFont({
  src: [
    { path: "/fonts/BebasNeue-Regular.ttf", weight: "400", style: "normal" },
  ],
  variable: "--bebas-neue",
});

const RootLayout = ({ children }: { children: ReactNode }) => {
  return (
    <html
      lang="ko"
      className={`${GothicA1.variable} ${IBMPlexSans.variable} ${BebasNeue.variable} font-gothic`}
    >
      <body>{children}</body>
    </html>
  );
};
export default RootLayout;

app/[locale]/layout.tsx

import { NextIntlClientProvider } from "next-intl";
import { getMessages, setRequestLocale } from "next-intl/server";
import { notFound } from "next/navigation";
import { routing } from "@/i18n/routing";

export default async function LocaleLayout({
  children,
  params,
}: {
  children: React.ReactNode;
  params: { locale: string };
}) {
  const { locale } = await params;
  if (!routing.locales.includes(locale as "en" | "ko")) {
    notFound();
  }
  setRequestLocale(locale);
  const messages = await getMessages();

  return (
    <html lang={locale}>
      <body>
        <NextIntlClientProvider messages={messages}>
          {children}
        </NextIntlClientProvider>
      </body>
    </html>
  );
}

이 두파일의 결과는 

html의 body 태그 속에 또 html과 body 넣은....ㅋ

결론 : 공식문서를 잘 읽자. & ai를 맹신하지 말자 ^^ claude가 하라는데로 했는데.... 헛소리한거였다. 교차 검증은 필수. 

댓글