iBetter Books
수정

Ch 08. 자주 만나는 에러와 해결법

처음 Next.js App Router를 다루면 생소한 에러들을 만납니다. 에러 메시지만 봐서는 원인을 알기 어렵습니다. 개발하다가 마주치는 대표적인 에러들과 해결 방법을 정리합니다.

Hydration 에러

가장 당혹스러운 에러입니다. 개발 모드에서 콘솔에 붉은 글씨로 나타납니다.

Error: Hydration failed because the initial UI does not match what was rendered on the server.

원인: 서버에서 렌더링한 HTML과 클라이언트에서 React가 렌더링하는 결과가 다를 때 발생합니다.

대표적인 상황입니다.

1. 현재 시간이나 랜덤 값 사용

// 잘못된 예시function BadComponent() {  return <div>{new Date().toLocaleString()}</div>; // 서버/클라이언트 시간이 다름}// 올바른 예시"use client";import { useState, useEffect } from "react";function GoodComponent() {  const [time, setTime] = useState("");  useEffect(() => {    setTime(new Date().toLocaleString());  }, []);  return <div>{time}</div>;}```text**2. `typeof window` 분기**```typescript// 잘못된 예시 (서버에서 window가 없음)function BadComponent() {  const isClient = typeof window !== "undefined";  return <div>{isClient ? "클라이언트" : "서버"}</div>;}// 올바른 예시"use client";import { useState, useEffect } from "react";function GoodComponent() {  const [mounted, setMounted] = useState(false);  useEffect(() => {    setMounted(true);  }, []);  if (!mounted) return null;  return <div>클라이언트에서만 보임</div>;}```text**3. 브라우저 전용 API (`localStorage`, `navigator` 등)**항상 `useEffect` 안에서 사용해야 합니다.```typescript"use client";import { useEffect, useState } from "react";function ThemeToggle() {  const [theme, setTheme] = useState("light"); // 초기값은 서버 안전한 값  useEffect(() => {    // 마운트 후에만 localStorage 접근    const saved = localStorage.getItem("theme") ?? "light";    setTheme(saved);  }, []);  return <button onClick={() => setTheme(t => t === "light" ? "dark" : "light")}>{theme}</button>;}```text### 직렬화 에러

Error: Only plain objects, and a few built-ins, can be passed to Client Components from Server Components. Classes or null prototypes are not supported.

**원인**: Server Component에서 Client Component로 직렬화할 수 없는 값을 props로 전달할 때 발생합니다.```typescript// 잘못된 예시// Server Componentasync function ServerComp() {  const posts = await prisma.post.findMany(); // Prisma 결과는 Date 객체 포함  return <ClientComp posts={posts} />; // Date 직렬화 불가!}// 올바른 예시 — 직렬화 가능한 형태로 변환async function ServerComp() {  const posts = await prisma.post.findMany();  const serializable = posts.map((p) => ({    ...p,    createdAt: p.createdAt.toISOString(), // Date → string    updatedAt: p.updatedAt.toISOString(),  }));  return <ClientComp posts={serializable} />;}```text함수도 직렬화할 수 없습니다. Server Component에서 일반 함수를 props로 전달하면 에러가 납니다. Server Actions는 특별히 허용됩니다.### useRouter 빌드 에러

Error: You're importing a component that needs "next/navigation". That only works in a Client Component...

**원인**: `useRouter`, `usePathname`, `useSearchParams` 등 훅은 Client Component에서만 사용할 수 있습니다. `"use client"` 선언이 없으면 발생합니다.```typescript// 잘못된 예시import { useRouter } from "next/navigation"; // "use client" 없음export function BackButton() {  const router = useRouter(); // 에러!  return <button onClick={() => router.back()}>뒤로</button>;}// 올바른 예시"use client"; // 첫 줄에 추가import { useRouter } from "next/navigation";export function BackButton() {  const router = useRouter();  return <button onClick={() => router.back()}>뒤로</button>;}```text### Prisma 연결 에러 — 싱글턴 패턴

Error: PrismaClientInitializationError: Too many connections

**원인**: 개발 환경에서 핫 리로드마다 새 Prisma 클라이언트가 생성되어 연결이 과도하게 늘어납니다.Ch 01에서 소개한 싱글턴 패턴을 반드시 사용해야 합니다.```typescript// 파일: lib/prisma.ts (올바른 싱글턴 패턴)import { PrismaClient } from "@prisma/client";const globalForPrisma = globalThis as unknown as {  prisma: PrismaClient | undefined;};export const prisma =  globalForPrisma.prisma ??  new PrismaClient({    log: process.env.NODE_ENV === "development" ? ["query"] : [],  });if (process.env.NODE_ENV !== "production") globalForPrisma.prisma = prisma;```text### next/image 도메인 에러

Error: Invalid src prop on next/image, hostname "images.example.com" is not configured under images in your next.config.js

**해결**: `next.config.ts`에 도메인을 추가합니다.```typescript// 파일: next.config.tsconst nextConfig = {  images: {    remotePatterns: [      {        protocol: "https",        hostname: "images.example.com",      },    ],  },};export default nextConfig;

설정 변경 후 개발 서버를 재시작해야 합니다.

에러별 빠른 참조

에러 메시지 원인 해결책
Hydration failed 서버/클라이언트 렌더링 불일치 useEffect로 클라이언트 전용 코드 분리
Only plain objects can be passed Date, 함수 등 직렬화 불가 props .toISOString() 등으로 변환
needs "use client" 훅을 Server Component에서 사용 파일 첫 줄에 "use client" 추가
Too many connections Prisma 다중 인스턴스 싱글턴 패턴 적용
hostname not configured 외부 이미지 도메인 미등록 next.config.tsremotePatterns 추가

에러를 만나는 것은 자연스러운 과정입니다. 에러 메시지를 차분히 읽고 원인을 파악하는 습관이 가장 중요합니다.

다음 PART에서는 만든 블로그를 실제 인터넷에 배포합니다.