일반적인 오류 및 해결 방법
설치·포트·타입·모듈·Hook·데이터 페칭·Hydration·Vercel 오류를 증상과 실행 위치에 따라 진단합니다.
Next.js 애플리케이션을 개발하고 운영하다 보면 다양한 유형의 오류를 만나게 됩니다.
오류 자체는 자연스러운 개발 과정이지만, 핵심은 문제를 빠르게 진단하고 생산성을 유지하는 데 있습니다.
이 절에서는 자주 발생하는 오류를 유형별로 정리하고, 원인과 실용적인 해결 방법을 함께 제시합니다.
먼저 오류가 발생한 단계를 구분하기
같은 화면이 나오지 않아도 원인은 다를 수 있습니다. 빌드 중 Cannot find module이 나면 파일·의존성·임포트 경로부터 확인하고, 서버 요청 중 DB 연결이 실패하면 서버 로그와 연결 설정을 확인합니다. 브라우저에서만 map 오류가 나면 그 컴포넌트가 실제 받은 값부터 확인합니다. 재현한 URL과 동작, 최초 오류의 파일·줄, 빌드/서버/브라우저 중 발생 위치를 함께 기록하면 뒤따르는 오류를 원인으로 착각하기 어렵습니다.
개발 환경 설정 및 의존성 문제
프로젝트를 시작하거나 의존성을 업데이트할 때 흔히 발생하는 오류들입니다.
npm install 또는 yarn install 오류
- 오류 메시지 예시: Error: Cannot find module '...' , Error: EACCES: permission denied, Node-gyp related errors
-
원인
- 누락된 의존성:
package.json에 명시된 패키지가 제대로 설치되지 않았거나, 손상된node_modules폴더. - 권한 문제: 특정 디렉터리에 파일을 쓸 권한이 부족한 경우 (주로 Linux/macOS).
- Node.js/npm 버전 불일치: 프로젝트가 요구하는 Node.js 또는 npm/yarn 버전과 현재 설치된 버전이 다를 때.
- 네트워크 문제: 패키지 레지스트리에 접근할 수 없는 경우.
- 누락된 의존성:
-
해결 방법
설치 로그와 고정된 의존성 확인: 먼저 실패한 패키지와 프로젝트의 패키지 매니저·버전을 확인합니다. npm 프로젝트에 일치하는
package-lock.json이 있다면 다음처럼 재설치할 수 있습니다.npm cache verify npm cinpm ci는 기존node_modules를 교체하지만 락 파일은 수정하지 않습니다. 락 파일과package.json이 다르면 먼저 의도한 의존성 변경을 확인합니다. Yarn 프로젝트는 해당 버전의 고정 설치 옵션을 사용하며, npm 캐시 전체 삭제나 락 파일 삭제를 일반 해결책으로 삼지 않습니다.권한 문제 해결: 오류에 나온 경로와 다음 설정값을 비교해 캐시 문제인지 전역 설치 경로 문제인지 구분합니다.
npm config get cache npm config get prefix전역 설치 권한 문제는 Node 버전 관리자를 사용하거나 사용자 소유의 전역 설치 경로를 설정하는 방법을 검토합니다. 공유 시스템 디렉터리의 소유권을 일괄 변경하지 않습니다.
Node.js 버전 확인 및 변경: 프로젝트가 지원하는 버전을 확인합니다. macOS/Linux의
nvm을 쓰고 저장소에 검토된.nvmrc가 있다면 그 버전을 설치·선택합니다.nvm install nvm use.nvmrc가 없다면 먼저 배포 환경과 일치하는 지원 버전을 정합니다. Windows의 별도 버전 관리자는 사용법이 다릅니다.네트워크 확인: 프록시 설정, VPN 연결 등을 확인하고, 공용 네트워크에서 다시 시도해 봅니다.
포트 충돌 (EADDRINUSE)
- 오류 메시지 예시: Error: listen EADDRINUSE: address already in use :::3000
- 원인: Next.js 개발 서버(기본적으로 3000번 포트)가 사용하려는 포트가 이미 다른 프로세스에 의해 사용 중일 때 발생합니다.
-
해결 방법
현재 실행 중인 Next.js 서버 종료: 터미널에서
Ctrl + C를 눌러 종료합니다.포트 사용 프로세스 확인 및 종료: PID와 프로세스 소유·용도를 확인하고, 종료해도 되는 본인 개발 서버에만 적용합니다.
-
macOS/Linux
sudo lsof -i :3000 # 3000번 포트를 사용하는 프로세스 ID (PID) 찾기 kill <PID> # 확인한 본인 프로세스에 정상 종료 요청 -
Windows (PowerShell)
netstat -ano | findstr :3000 # PID 찾기 Stop-Process -Id <PID> # 해당 PID 종료
다른 포트 사용:
npm run dev -- -p 3001또는yarn dev -p 3001명령어를 사용하여 다른 포트에서 개발 서버를 실행합니다. -
macOS/Linux
컴파일 및 런타임 오류
개발 중 코드를 작성하면서 발생하는 일반적인 오류들입니다.
타입스크립트(TypeScript) 오류
- 오류 메시지 예시: Property '...' does not exist on type '...', Type 'A' is not assignable to type 'B', Cannot find name '...'
- 원인: TypeScript의 타입 정의 불일치, 오타, 잘못된 임포트, 누락된 타입 정의 파일(
.d.ts). -
해결 방법
타입 정의 확인: 변수, 함수 매개변수, 컴포넌트 Props 등의 타입을 정확하게 정의했는지 확인합니다.
외부 라이브러리 타입 설치: 사용하는 서드파티 라이브러리의 타입 정의가 누락된 경우
@types/패키지를 설치합니다. (예:npm install --save-dev @types/node @types/react @types/react-dom)tsconfig.json검토:compilerOptions의strict모드,lib설정 등을 확인하여 프로젝트에 맞게 구성되어 있는지 확인합니다.IDE 활용: VS Code와 같은 IDE는 TypeScript 오류를 실시간으로 표시해 주므로, 이를 적극 활용하여 오류 발생 즉시 수정합니다.
빌드 산출물 확인: 먼저 오류가 가리키는 원본 타입과 설정을 수정합니다. 이미 고친 원본과 오래된
.next산출물이 어긋나는 경우에만 산출물을 정리하고 다시 빌드하여 확인합니다.string을number에 대입하는 실제 타입 오류는 캐시를 지워도 해결되지 않습니다.
모듈 임포트 오류
-
오류 메시지 예시: Module not found: Can't resolve '...' in '...', Cannot find module '...'
-
원인: 잘못된 파일 경로, 대소문자 불일치(특히 Linux 환경 배포 시), 모듈이 실제로 설치되지 않았거나 삭제된 경우.
-
해결 방법
경로 확인: 임포트하는 파일의 경로가 정확한지, 오타는 없는지 확인합니다.
상대 경로(
../)와 절대 경로(@/) 모두 확인합니다.대소문자 일치: 파일 시스템에서 파일 이름과 임포트 구문의 대소문자가 정확히 일치하는지 확인합니다. (일반적인 Windows 디렉터리는 대소문자를 구분하지 않지만 디렉터리별 설정으로 달라질 수 있습니다. Linux 배포 환경에서도 같은 임포트가 동작하도록 실제 파일명과 철자를 일치시킵니다.)
패키지 설치 확인: 해당 모듈이
node_modules에 실제로 설치되어 있는지package.json과 비교하여 확인합니다.누락되었다면
npm install또는yarn add로 설치합니다.tsconfig.jsonpaths설정: 절대 경로를 사용한다면tsconfig.json의paths설정이 올바른지 확인합니다.tsconfig.json { "compilerOptions": { "paths": { "@/*": ["./src/*"] } } }
paths는 TypeScript가 임포트 대상을 찾는 설정입니다. Next.js의 번들러가 지원하는 별칭을 사용하더라도, 같은 코드를 별도의 Node.js 스크립트로 실행할 때 그 별칭까지 자동으로 해석되는 것은 아닙니다. TypeScript가 출력한 JavaScript의 임포트 문자열은 paths만으로 바뀌지 않으므로 실행 도구의 별칭 지원도 확인합니다.
React Hooks 규칙 위반
- 오류 메시지 예시: React Hook 'useState' is called in function '...' which is neither a React function component or a custom React Hook function.
- 원인:
useState,useEffect,useMemo같은 Hook은 React 함수 컴포넌트 또는 커스텀 Hook의 최상위 레벨에서 호출해야 합니다. 조건문, 루프, 중첩된 함수 내에서 호출될 때 발생합니다. -
해결 방법
규칙 준수: 해당 Hook을 컴포넌트나 커스텀 Hook의 최상위에서 호출했는지 확인합니다. React의
useAPI는 조건문·반복문 안에서도 호출할 수 있는 별도 규칙을 갖습니다.컴포넌트 명명 규칙: 함수 컴포넌트는 항상 대문자로 시작해야 합니다.
use client지시문 누락: App Router에서 클라이언트 측 훅을 사용하는 컴포넌트 상단에"use client";지시문을 추가했는지 확인합니다.
서버 사이드 렌더링 (SSR) 및 데이터 페칭 오류
Next.js의 핵심 기능인 SSR 및 데이터 페칭 과정에서 발생하는 오류들입니다.
fetch 또는 데이터베이스 연결 오류
Server Components나 Route Handler에서 데이터를 가져올 때 발생하는 오류입니다.
- 오류 메시지 예시: Failed to connect to database, Network request failed, TypeError: Cannot read properties of undefined (reading 'map')
-
원인
- 환경 변수 누락/오류: 데이터베이스 연결 문자열, API 키 등 중요한 환경 변수가 올바르게 설정되지 않았거나 누락된 경우.
- 네트워크 문제: 서버리스 함수가 외부 데이터베이스나 API에 연결할 수 없는 경우 (방화벽, IP 허용 목록 등).
- 서버-클라이언트 전달 계약 문제: Client Component Props에 일반 함수, 클래스 인스턴스나 데이터베이스 문서 객체처럼 React Server Components가 전달할 수 없는 값을 포함할 때.
- 잘못된 데이터 구조: 예상했던 데이터 구조와 실제 페칭된 데이터 구조가 다를 때.
데이터를 받는 세 단계 구분하기
fetch가 응답을 받는 단계,response.json()이 본문을 해석하는 단계, 앱이 필요한 필드를 사용하는 단계를 따로 확인합니다. HTTP 404나 500 응답을 받았다는 이유만으로fetch의 Promise가 자동으로 거절되지는 않습니다. 상태와response.ok를 먼저 확인합니다. 그 뒤 본문 형식을 확인하고, JSON을 해석한 뒤에도 필요한 필드의 타입을 검사합니다.
예를 들어 404 HTML 오류 페이지에 response.json()을 호출하면 JSON 해석이 실패합니다. 반대로 HTTP 200과 JSON 객체를 받았어도 items 대신 records만 있다면 data.items.map(...)은 실패합니다. 이때 네트워크 연결을 바꾸기보다 응답 계약과 Array.isArray(data.items) 같은 조건을 먼저 확인해야 합니다. 오류 화면을 처리할 때도 HTTP 실패·해석 실패·업무 실패를 구분합니다.
-
해결 방법
환경 변수 확인:
.env.local파일과 배포 환경(Vercel 대시보드 등)의 환경 변수가 올바르게 설정되었는지 다시 확인합니다.process.env.MY_VAR형태로 접근 시 오타가 없는지도 확인합니다.데이터베이스/API 접근성 테스트: 별도의 스크립트나 Postman 등으로 데이터베이스나 외부 API에 직접 연결하여 정상 작동하는지 확인합니다.
Vercel Functions의 기본 외부 송신 IP는 고정 범위가 아니므로 임의의 Vercel IP 목록을 허용하지 않습니다.
데이터베이스의 네트워크 정책에 맞춰 비공개 연결을 구성하거나, 고정 송신 IP가 꼭 필요하면 Vercel의 Static IPs 또는 Secure Compute 같은 별도 기능을 사용합니다.
전달 가능한 값 확인: 서버 컴포넌트에서 Client Component로 전달하는 Props는 React Server Components의 직렬화 계약을 따라야 합니다.
문자열·숫자·배열·일반 객체뿐 아니라
Date,Map,Set등 React가 지원하는 값은 전달할 수 있습니다.일반 함수는 전달하지 말고 이벤트 로직을 Client Component 안에 두며, 서버 호출이 필요하면 명시적인 Server Function을 사용합니다.
클래스 인스턴스나 Mongoose 문서는 그대로 넘기지 않고
lean()과 명시적 DTO 변환으로 공개할 필드만 일반 데이터로 만듭니다.오류 처리 및 로깅:
try...catch로 예상하지 못한 페칭 오류를 기록하고 응답을 처리합니다. 로그에는 토큰·연결 문자열을 제외한 오류 종류와 발생 위치를 남깁니다.
Hydration 오류
Hydration 오류는 서버가 만든 HTML과 브라우저의 첫 렌더가 달라지는 문제이므로, 원인을 환경 차이와 HTML 구조 차이로 나누어 확인합니다.
Hydration failed because the initial UI does not match what was rendered on the server.
- 오류 메시지 예시: There was an error while hydrating. Because the error happened outside of a Suspense boundary, the entire root will be unmounted.
- 원인: 서버에서 렌더링된 HTML과 클라이언트에서 React가 렌더링하려는 HTML 구조가 일치하지 않을 때 발생합니다.
Date객체나Math.random()등 서버/클라이언트 환경에 따라 결과가 달라지는 로직을 포함하는 경우.- 잘못된 중첩 태그로 인한 브라우저의 DOM 보정.
- 브라우저 확장 프로그램에 의한 DOM 변경.
- 브라우저 전용 API나 환경 의존 값을 첫 렌더 중 바로 읽어 서버 HTML과 다른 결과를 만드는 경우.
-
해결 방법
같은 초기값 사용: 시간·난수는 서버에서 만든 하나의 값을 전달해 양쪽 첫 렌더에 사용합니다. 양쪽에서 각각 현재 시간을 문자열로 바꾸는 것만으로는 값이 같아지지 않습니다. 브라우저에서만 정할 값은 첫 렌더를 일치시킨 뒤 Effect에서 갱신합니다.
서버에서 실행할 수 없는 라이브러리 격리: 브라우저 전용 라이브러리처럼 서버 사전 렌더 자체가 불가능한 경우에만 Client Component 안에서
next/dynamic의ssr: false를 사용합니다. 일반 Client Component는 서버에서도 정상적으로 사전 렌더되므로 이 옵션이 기본 해결책은 아닙니다.src/components/ClientOnlySection.tsx (클라이언트 컴포넌트) "use client"; import dynamic from 'next/dynamic'; const NoSSRDynamicComponent = dynamic(() => import('../components/ClientOnlyComponent'), { ssr: false }); function MyPage() { return <NoSSRDynamicComponent />; }조건부 렌더링 (
useEffect): 클라이언트에서만 존재하는 전역 객체(예:window)를 사용하는 경우,useEffect훅 내부에서 해당 로직을 실행하여 클라이언트 렌더링 시점에만 작동하도록 합니다.src/components/ClientSideText.tsx (클라이언트 컴포넌트) "use client"; import { useState, useEffect } from 'react'; function ClientSideText() { const [text, setText] = useState(''); useEffect(() => { setText(window.innerWidth > 768 ? 'Large Screen' : 'Small Screen'); }, []); return <div>{text}</div>; }HTML 구조 확인:
<p>안에<div>를 넣는 식의 잘못된 중첩은 브라우저가 DOM을 보정하면서 서버 HTML과 달라질 수 있습니다. 유효한 태그 구조로 고쳐 원인을 제거합니다.Suspense는 비동기 로딩 경계이지 hydration 불일치를 해결하는 오류 경계가 아닙니다.
"use client"는 클라이언트 컴포넌트 경계를 선언하며, 그 컴포넌트의 초기 HTML이 서버에서 만들어지지 않는다는 뜻은 아닙니다. typeof window !== 'undefined'로 접근 오류를 막아도 서버에서는 빈 결과, 브라우저 첫 렌더에서는 문단을 반환하면 초기 출력이 달라집니다. 위 ClientSideText는 양쪽 첫 렌더를 빈 <div>로 맞춘 뒤 Effect에서 화면 너비를 읽으므로 그 차이를 뒤로 옮깁니다. 처음부터 중요한 정보가 필요하다면 서버가 만든 동일한 초기값을 Props로 전달하는 방법도 고려합니다.
같은 첫 렌더 뒤 브라우저 값 반영
서버와 브라우저 초기 렌더가 같은 빈 텍스트를 내고, effect가 window 폭을 읽은 뒤 지역 상태와 출력을 바꿉니다. 초기 일치와 effect 이후 변화가 같은 화면에서 명확히 구분됩니다.
Next · Initial output and effect
같은 첫 렌더 뒤 브라우저 값 반영
ClientSideText 예제는 서버와 브라우저의 첫 렌더를 모두 빈 text로 맞춥니다. 브라우저에서 Effect가 window.innerWidth를 읽고 상태를 바꾼 뒤 글자를 표시합니다.
이 예제는 첫 Effect에서 읽은 너비만 반영하며 resize 리스너는 없습니다. 유효한 HTML 구조와 같은 초기 출력이 전제이고, 브라우저 전용 라이브러리 격리는 별도 판단입니다.
이 예의 조건
- 두 박스는 같은 실행의 진행 정보와 자료·결과를 관찰하는 고정 역할입니다. 박스의 좌우·위아래 위치를 별도 호출 순서로 해석하지 않습니다.
- 조건별 종료는 대안입니다. 실패 뒤 정상 후속 단계를 실행하거나 독립 비교 사례를 같은 요청으로 연결하지 않습니다.
- 서버와 브라우저 첫 렌더의 빈 text는 일치한다. 이후 브라우저 Effect가 너비로 상태를 변경하고 다시 렌더한다.
| 장면 | 관찰 값 | 전환 이유 |
|---|---|---|
| 서버 초기 출력 서버가 빈 text로 초기 출력합니다. | 서버 text: 빈 값 · window 사용: 초기 출력 밖 | 브라우저와 맞출 출발 출력이 생깁니다. |
| 브라우저 첫 렌더 브라우저 첫 렌더도 같은 빈 text입니다. | 브라우저 text: 빈 값 · 초기 일치: 예 | 첫 출력이 서버와 일치합니다. |
| Effect 후 상태 변경 브라우저 Effect가 window 폭을 읽어 상태를 바꾸고 다시 렌더합니다. | 자료: 브라우저 width · 상태: Effect 이후 갱신 · 임의 폭 수치: 없음 | 초기 일치 이후에만 화면 text가 바뀝니다. |
같은 첫 렌더 뒤 브라우저 값 반영
서버와 브라우저 초기 렌더가 같은 빈 텍스트를 내고, effect가 window 폭을 읽은 뒤 지역 상태와 출력을 바꿉니다. 초기 일치와 effect 이후 변화가 같은 화면에서 명확히 구분됩니다.
Next · Initial output and effect
같은 첫 렌더 뒤 브라우저 값 반영
ClientSideText 예제는 서버와 브라우저의 첫 렌더를 모두 빈 text로 맞춥니다. 브라우저에서 Effect가 window.innerWidth를 읽고 상태를 바꾼 뒤 글자를 표시합니다.
이 예제는 첫 Effect에서 읽은 너비만 반영하며 resize 리스너는 없습니다. 유효한 HTML 구조와 같은 초기 출력이 전제이고, 브라우저 전용 라이브러리 격리는 별도 판단입니다.
이 예의 조건
- 두 박스는 같은 실행의 진행 정보와 자료·결과를 관찰하는 고정 역할입니다. 박스의 좌우·위아래 위치를 별도 호출 순서로 해석하지 않습니다.
- 조건별 종료는 대안입니다. 실패 뒤 정상 후속 단계를 실행하거나 독립 비교 사례를 같은 요청으로 연결하지 않습니다.
- 서버와 브라우저 첫 렌더의 빈 text는 일치한다. 이후 브라우저 Effect가 너비로 상태를 변경하고 다시 렌더한다.
| 장면 | 관찰 값 | 전환 이유 |
|---|---|---|
| 서버 초기 출력 서버가 빈 text로 초기 출력합니다. | 서버 text: 빈 값 · window 사용: 초기 출력 밖 | 브라우저와 맞출 출발 출력이 생깁니다. |
| 브라우저 첫 렌더 브라우저 첫 렌더도 같은 빈 text입니다. | 브라우저 text: 빈 값 · 초기 일치: 예 | 첫 출력이 서버와 일치합니다. |
| Effect 후 상태 변경 브라우저 Effect가 window 폭을 읽어 상태를 바꾸고 다시 렌더합니다. | 자료: 브라우저 width · 상태: Effect 이후 갱신 · 임의 폭 수치: 없음 | 초기 일치 이후에만 화면 text가 바뀝니다. |
초기 출력과 너비 변경을 구별하기 ClientSideText의 서버 출력과 브라우저 첫 렌더는 모두 빈 div입니다. 브라우저에서 Effect가 실행된 뒤에만 너비에 따라 글자가 정해집니다. React의 Effect 설명은 초기 출력의 일치와 서버에서 Effect가 실행되지 않는 점을 구별합니다.
직접 추적하기 브라우저에서 첫 Effect가 실행될 때 너비가 각각 768 px과 769 px이라고 합시다. 어떤 글자가 보일까요? 769 px에서 실행된 화면을 그 뒤 400 px로 줄였지만 컴포넌트를 다시 마운트하지 않았다면 원 코드가 글자를 바꿀까요?
비교 연산과 실행 계기
원 조건은 768보다 큰지 검사하므로 768 px은 Small Screen, 769 px은 Large Screen입니다. 그 뒤 창만 줄여도 resize 리스너가 없고 Effect의 의존성 배열이 비어 있어 원 코드가 너비 변경을 다시 읽지 않습니다. 같은 마운트에서 남아 있는 글자는 Large Screen입니다. 화면 크기에 계속 반응하는 기능이 필요하면 CSS로 표현할 수 있는지 먼저 보고, JavaScript가 필요한 경우에는 리스너의 등록·정리까지 별도로 설계합니다. 개발 Strict Mode의 추가 Effect 점검은 resize 처리를 구현한 것이 아닙니다.
Vercel 배포 관련 오류
Vercel에 배포 시 발생하는 특유의 문제들입니다.
빌드 실패 (Build Failed)
-
오류 메시지 예시: Module not found, TypeScript errors, Command failed with exit code 1
-
원인
- 로컬에서는 성공적으로 빌드되지만, Vercel 환경에서는 실패하는 경우가 있습니다. 이는 주로 대소문자 불일치(Linux 기반 서버), 환경 변수 누락,
package.json의script오류, Node.js 버전 불일치 등이 원인입니다.
- 로컬에서는 성공적으로 빌드되지만, Vercel 환경에서는 실패하는 경우가 있습니다. 이는 주로 대소문자 불일치(Linux 기반 서버), 환경 변수 누락,
-
해결 방법
Vercel 빌드 로그 확인: Vercel 대시보드에서 해당 배포의 빌드 로그를 상세히 확인하여 정확한 오류 메시지를 파악합니다.
환경 변수 확인: Vercel 프로젝트 설정의 환경 변수(Environment Variables) 섹션에 필요한 모든 변수들이 올바르게 추가되었는지 확인합니다.
MONGODB_URI는 DB에 접근하는 실행 단계에 필요합니다. 빌드 중 사전 렌더가 DB를 읽는다면 빌드 환경에도 설정하고, 요청 때만 조회한다면 런타임 환경을 확인합니다.대소문자 일치 확인: 파일 이름 및 임포트 경로의 대소문자가 로컬과 Git 리포지토리, 그리고 Vercel 서버(Linux 기반)에서 모두 일치하는지 확인합니다.
Node.js 버전 일치: Next.js 16은 Node.js 20.9 이상이 필요합니다. 로컬과 배포 환경을 맞추고 필요하면
package.json에"engines": { "node": ">=20.9.0" }을 명시합니다.next.config.js검토: Next.js 설정 파일에 오류가 없는지 확인합니다.vercel.json(고급): 필요하다면vercel.json파일을 사용하여 빌드 명령어나 배포 설정을 커스터마이징합니다.
런타임 오류 (배포 후)
-
오류 메시지 예시: 웹 페이지에 500 에러, Application error: a client-side exception has occurred
-
원인: 배포된 서버리스 함수나 페이지에서 런타임 오류가 발생하는 경우입니다. 데이터베이스 연결 문제, 외부 API 호출 실패, 잘못된 로직 등이 원인일 수 있습니다.
-
해결 방법
Vercel 런타임 로그 확인: Vercel 대시보드의 "Logs" 탭에서 서버 함수와 Edge Runtime의 오류를 확인합니다.
상세한 스택 트레이스를 통해 문제의 원인을 파악할 수 있습니다.
재현 가능한 시나리오: 로컬 환경에서 프로덕션 빌드(
npm run build && npm run start)를 통해 배포 환경과 유사하게 테스트하여 오류를 재현하고 디버깅합니다.데이터 직렬화 문제 재확인: 서버에서 클라이언트로 전달되는 데이터에 직렬화 불가능한 객체가 포함되어 있는지 다시 확인합니다.
일반적인 디버깅 팁
어떤 유형의 오류든 문제를 해결하는 데 도움이 되는 일반적인 디버깅 기술입니다.
- 콘솔 로깅 (
console.log): 서버 컴포넌트와 Route Handler의 로그는 터미널에서 확인합니다. Client Component도 초기 HTML을 만들 때 서버에서 사전 렌더될 수 있어 render 중 로그는 터미널과 브라우저 양쪽에 나타날 수 있고, 이벤트 핸들러와useEffect로그는 브라우저에서 확인합니다. -
브라우저 개발자 도구
- Console 탭: 클라이언트 측 JavaScript 오류, 경고,
console.log출력을 확인합니다. - Elements 탭: 렌더링된 HTML 구조를 확인하여 예상과 다른 DOM 구조를 파악합니다 (Hydration 오류 시 유용).
- Network 탭: 기록 중인 페이지의 브라우저 요청(API 호출, 이미지 등)을 확인합니다. 서버 내부의 DB 쿼리나 외부 API 호출은 브라우저에 별도 요청 행으로 나타나지 않으므로 서버 로그와 함께 확인합니다.
- Sources 탭: JavaScript 코드에 브레이크포인트(Breakpoint)를 설정하여 코드 실행을 일시 중지하고 변수 값을 검사하는 등 상세 디버깅을 수행합니다. (배포된 JavaScript에도 중단점을 둘 수 있으며, Source Map이 있으면 원본 TypeScript의 위치와 연결하여 읽기 쉽습니다.)
- Console 탭: 클라이언트 측 JavaScript 오류, 경고,
-
VS Code 디버거
- VS Code의 내장 디버거로 Node.js 환경(Route Handler, 서버 컴포넌트)과 브라우저 환경(클라이언트 컴포넌트)을 각각 디버깅할 수 있습니다.
launch.json파일을 설정하여 디버깅 구성을 저장할 수 있습니다.
- 커밋 이력 확인 (
git blame): 문제가 발생한 지점을 찾기 위해 Git 히스토리를 확인합니다. 특히git blame <file>명령어를 사용하면 파일의 각 줄이 마지막으로 변경된 커밋과 작성자를 확인할 수 있어 유용합니다. - 오류 메시지 검색: 오류 메시지는 문제 해결의 가장 중요한 단서입니다. 오류 메시지를 그대로 복사하여 Google이나 Stack Overflow에서 검색하면 유사한 문제와 해결책을 찾을 가능성이 높습니다.
문제 해결은 개발 과정의 필수적인 부분이며, 꾸준한 연습을 통해 실력을 향상시킬 수 있습니다.
오류 메시지를 주의 깊게 읽고, 체계적인 접근 방식을 취하며, 다양한 디버깅 도구를 활용하면 어떤 문제든 효과적으로 해결할 수 있을 것입니다.
추가 연습: 실패 단계를 좁혀 보기 다음은 각각 독립적인 상황입니다. 원인으로 확인할 단계와 그 이유를 설명하세요.
- API가 HTTP 404,
Content-Type: text/html, 본문<p>Not Found</p>를 반환했습니다.fetch에서는 응답을 받았는데 이어서response.json()이 실패합니다. 두 결과는 모순일까요? - API가 HTTP 200과 유효한 JSON
{"records": []}를 반환했습니다. 화면은data.items.map(...)을 호출합니다. DB 연결을 다시 설정하기 전에 무엇을 확인해야 할까요? - 컴포넌트가 서버에서
null, 브라우저 첫 렌더에서<p>Client</p>를 반환합니다. 분기 조건은typeof window !== 'undefined'입니다. 이 조건과"use client"만으로 hydration 불일치가 해결될까요? 중요한 서버 초기 정보가 없는 예제로 한정하여 수정 방향을 제시하세요. - 원본 코드에서
number변수에 문자열을 대입해 빌드가 실패했습니다..next를 정리했는데도 같은 줄에서 오류가 납니다. 다음 조치는 무엇일까요?
단계별 진단과 수정 방향
- 모순이 아닙니다. HTTP 오류 상태도 응답 객체로 받을 수 있습니다. 여기서는 응답 상태 확인과 JSON 해석이 다른 단계이며 HTML은 JSON이 아니므로 해석이 실패합니다.
response.ok·상태와 본문 형식을 확인하고 HTTP 오류 처리를 먼저 선택합니다. - 응답 계약을 확인합니다. JSON 해석은 성공했지만
items가 없으므로 그 값은undefined입니다. API와 화면 중 어느 쪽의 필드 이름이 계약과 다른지 수정하고 배열인지 검사한 뒤map을 호출합니다. 빈 배열 자체는 유효한 결과이므로 임의로 연결 오류로 바꾸지 않습니다. - 해결되지 않습니다. 조건은
window접근 오류를 막을 뿐 양쪽 첫 결과를 같게 만들지 않습니다. 서버와 첫 브라우저 렌더가 모두 빈 결과를 반환하도록 상태를 시작하고, Effect 이후에 클라이언트용 문단을 표시할 수 있습니다. 브라우저 전용 라이브러리를 격리할 때는 별도의 Client Component에서ssr: false를 검토하지만 일반적인 불일치의 원인부터 확인합니다. - 오류가 가리키는 대입과 타입 계약을 고칩니다. 숫자가 필요한 값이면 입력 변환과 유효성 검사를, 문자열이 맞는 값이면 사용하는 곳을 포함한 타입 정의를 검토합니다. 캐시는 원본 코드의 타입 관계를 바꾸지 않습니다.