본문으로 건너뛰기
안동민 개발노트 아이콘

안동민 개발노트

본문 시작
18장 : 문제 해결 및 디버깅

일반적인 오류 및 해결 방법

설치·포트·타입·모듈·Hook·데이터 페칭·Hydration·Vercel 오류를 증상과 실행 위치에 따라 진단합니다.

Next.js 애플리케이션을 개발하고 운영하다 보면 다양한 유형의 오류를 만나게 됩니다.

오류 자체는 자연스러운 개발 과정이지만, 핵심은 문제를 빠르게 진단하고 생산성을 유지하는 데 있습니다.

이 절에서는 자주 발생하는 오류를 유형별로 정리하고, 원인과 실용적인 해결 방법을 함께 제시합니다.

먼저 오류를 설치, 개발 서버, 컴파일, 런타임, 배포 위치로 나누면 어디서부터 확인할지 빠르게 정할 수 있습니다.


개발 환경 설정 및 의존성 문제

프로젝트를 시작하거나 의존성을 업데이트할 때 흔히 발생하는 오류들입니다.

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 버전과 현재 설치된 버전이 다를 때.
    • 네트워크 문제: 패키지 레지스트리에 접근할 수 없는 경우.
  • 해결 방법
    node_modules 및 락 파일 삭제
    rm -rf node_modules
    rm package-lock.json # npm 사용 시
    rm yarn.lock         # yarn 사용 시
    npm cache clean --force # npm 캐시 클린

    이후 다시 npm install 또는 yarn install을 시도합니다.

    권한 문제 해결: Linux/macOS에서 권한 문제 발생 시 sudo 사용은 피하고, 다음과 같이 소유권을 변경하거나 npm의 전역 설치 경로를 변경하는 것을 권장합니다.

    sudo chown -R $(whoami) ~/.npm
    sudo chown -R $(whoami) /usr/local/lib/node_modules

    Node.js 버전 확인 및 변경: nvm (Node Version Manager)을 사용하여 프로젝트에 맞는 Node.js 버전으로 변경합니다.

    nvm install 20.9.0
    nvm use 20.9.0

    네트워크 확인: 프록시 설정, VPN 연결 등을 확인하고, 공용 네트워크에서 다시 시도해 봅니다.

포트 충돌 (EADDRINUSE)

  • 오류 메시지 예시: Error: listen EADDRINUSE: address already in use :::3000
  • 원인: Next.js 개발 서버(기본적으로 3000번 포트)가 사용하려는 포트가 이미 다른 프로세스에 의해 사용 중일 때 발생합니다.
  • 해결 방법

    현재 실행 중인 Next.js 서버 종료: 터미널에서 Ctrl + C를 눌러 종료합니다.

    포트 사용 프로세스 확인 및 종료
    • macOS/Linux
      sudo lsof -i :3000 # 3000번 포트를 사용하는 프로세스 ID (PID) 찾기
      kill -9 <PID>      # 해당 PID 종료
    • Windows (PowerShell)
      netstat -ano | findstr :3000 # PID 찾기
      Stop-Process -Id <PID>       # 해당 PID 종료

    다른 포트 사용: npm run dev -- -p 3001 또는 yarn dev -p 3001 명령어를 사용하여 다른 포트에서 개발 서버를 실행합니다.

다음 다이어그램은 로컬 개발 환경 오류를 설치, 버전, 포트, 환경 변수 순서로 좁히는 점검 흐름입니다.


컴파일 및 런타임 오류

개발 중 코드를 작성하면서 발생하는 일반적인 오류들입니다.

타입스크립트(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 검토: compilerOptionsstrict 모드, lib 설정 등을 확인하여 프로젝트에 맞게 구성되어 있는지 확인합니다.

    IDE 활용: VS Code와 같은 IDE는 TypeScript 오류를 실시간으로 표시해주므로, 이를 적극 활용하여 오류 발생 즉시 수정합니다.

    캐시 삭제: 때때로 .next 폴더를 삭제하고 다시 빌드/실행하면 해결되는 경우가 있습니다.

모듈 임포트 오류

  • 오류 메시지 예시: Module not found: Can't resolve '...' in '...', Cannot find module '...'

  • 원인: 잘못된 파일 경로, 대소문자 불일치(특히 Linux 환경 배포 시), 모듈이 실제로 설치되지 않았거나 삭제된 경우.

  • 해결 방법

    경로 확인: 임포트하는 파일의 경로가 정확한지, 오타는 없는지 확인합니다.

    상대 경로(../)와 절대 경로(@/) 모두 확인합니다.

    대소문자 일치: 파일 시스템에서 파일 이름과 임포트 구문의 대소문자가 정확히 일치하는지 확인합니다. (Windows는 대소문자 구분을 하지 않지만, Linux는 구분합니다.)

    패키지 설치 확인: 해당 모듈이 node_modules에 실제로 설치되어 있는지 package.json과 비교하여 확인합니다.

    누락되었다면 npm install 또는 yarn add로 설치합니다.

    tsconfig.json paths 설정: 절대 경로를 사용한다면 tsconfig.jsonpaths 설정이 올바른지 확인합니다.

    tsconfig.json
    {
      "compilerOptions": {
        "paths": {
          "@/*": ["./src/*"]
        }
      }
    }

React Hooks 규칙 위반

  • 오류 메시지 예시: React Hook 'useState' is called in function '...' which is neither a React function component or a custom React Hook function.
  • 원인: React Hooks(useState, useEffect, useMemo 등)는 오직 React 함수 컴포넌트의 최상위 레벨 또는 커스텀 훅 내에서만 호출되어야 합니다. 조건문, 루프, 중첩된 함수 내에서 호출될 때 발생합니다.
  • 해결 방법

    규칙 준수: 모든 훅 호출이 컴포넌트의 최상위 스코프에 있는지 확인합니다.

    컴포넌트 명명 규칙: 함수 컴포넌트는 항상 대문자로 시작해야 합니다.

    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가 전달할 수 없는 값을 포함할 때.
    • 잘못된 데이터 구조: 예상했던 데이터 구조와 실제 페칭된 데이터 구조가 다를 때.
  • 해결 방법

    환경 변수 확인: .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() 등 서버/클라이언트 환경에 따라 결과가 달라지는 로직을 포함하는 경우.
    • HTML 주석이나 잘못된 중첩 태그.
    • 브라우저 확장 프로그램에 의한 DOM 변경.
    • 브라우저 전용 API나 환경 의존 값을 첫 렌더 중 바로 읽어 서버 HTML과 다른 결과를 만드는 경우.
  • 해결 방법

    환경 독립적인 코드: 서버와 클라이언트에서 동일한 결과를 보장하는 코드를 작성합니다. (예: Date 객체는 문자열로 변환하여 사용).

    서버에서 실행할 수 없는 라이브러리 격리: 브라우저 전용 라이브러리처럼 서버 사전 렌더 자체가 불가능한 경우에만 Client Component 안에서 next/dynamicssr: 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 불일치를 해결하는 오류 경계가 아닙니다.


Vercel 배포 관련 오류

Vercel에 배포 시 발생하는 특유의 문제들입니다.

빌드 실패 (Build Failed)

  • 오류 메시지 예시: Module not found, TypeScript errors, Command failed with exit code 1

  • 원인
    • 로컬에서는 성공적으로 빌드되지만, Vercel 환경에서는 실패하는 경우가 있습니다. 이는 주로 대소문자 불일치(Linux 기반 서버), 환경 변수 누락, package.jsonscript 오류, Node.js 버전 불일치 등이 원인입니다.
  • 해결 방법

    Vercel 빌드 로그 확인: Vercel 대시보드에서 해당 배포의 빌드 로그를 상세히 확인하여 정확한 오류 메시지를 파악합니다.

    환경 변수 확인: Vercel 프로젝트 설정의 환경 변수(Environment Variables) 섹션에 필요한 모든 변수들이 올바르게 추가되었는지 확인합니다.

    특히 MONGODB_URI 같은 민감한 정보는 빌드 시 접근 가능해야 합니다.

    대소문자 일치 확인: 파일 이름 및 임포트 경로의 대소문자가 로컬과 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 호출, 이미지 등)을 모니터링하여 상태 코드, 응답 시간, 데이터 등을 확인합니다.
    • Sources 탭: JavaScript 코드에 브레이크포인트(Breakpoint)를 설정하여 코드 실행을 일시 중지하고 변수 값을 검사하는 등 상세 디버깅을 수행합니다. (Source Map이 활성화되어 있어야 함)
  • VS Code 디버거
    • VS Code의 내장 디버거로 Node.js 환경(Route Handler, 서버 컴포넌트)과 브라우저 환경(클라이언트 컴포넌트)을 각각 디버깅할 수 있습니다.
    • launch.json 파일을 설정하여 디버깅 구성을 저장할 수 있습니다.
  • 단계별 커밋 (git blame): 문제가 발생한 지점을 찾기 위해 Git 히스토리를 확인합니다. 특히 git blame <file> 명령어를 사용하면 파일의 각 줄이 마지막으로 변경된 커밋과 작성자를 확인할 수 있어 유용합니다.
  • 오류 메시지 검색: 오류 메시지는 문제 해결의 가장 중요한 단서입니다. 오류 메시지를 그대로 복사하여 Google이나 Stack Overflow에서 검색하면 유사한 문제와 해결책을 찾을 가능성이 높습니다.

문제 해결은 개발 과정의 필수적인 부분이며, 꾸준한 연습을 통해 실력을 향상시킬 수 있습니다.

오류 메시지를 주의 깊게 읽고, 체계적인 접근 방식을 취하며, 다양한 디버깅 도구를 활용하면 어떤 문제든 효과적으로 해결할 수 있을 것입니다.


오류를 만났을 때는 메시지 자체보다 발생 위치를 먼저 나누면 확인 순서가 훨씬 단순해집니다.

아래 다이어그램은 TypeScript 오류, 모듈 import 오류, Hooks 규칙, Hydration 오류를 원인별로 나눠 보여줍니다.

이처럼 오류 유형을 먼저 나누면 검색보다 빠르게 수정 위치와 검증 방법을 고를 수 있습니다.