icon
18장 : 문제 해결 및 디버깅

일반적인 오류 및 해결 방법

Next.js 애플리케이션을 개발하고 운영하다 보면 다양한 유형의 오류에 직면하게 됩니다. 이러한 오류들은 개발 과정의 자연스러운 부분이지만, 효과적인 문제 해결 전략과 디버깅 기술을 통해 신속하게 해결하고 개발 생산성을 유지하는 것이 중요합니다. 이 절에서는 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 버전과 현재 설치된 버전이 다를 때.
    • 네트워크 문제: 패키지 레지스트리에 접근할 수 없는 경우.
  • 해결 방법
    1. node_modules 및 락 파일 삭제
      rm -rf node_modules
      rm package-lock.json # npm 사용 시
      rm yarn.lock         # yarn 사용 시
      npm cache clean --force # npm 캐시 클린
      이후 다시 npm install 또는 yarn install을 시도합니다.
    2. 권한 문제 해결: Linux/macOS에서 권한 문제 발생 시 sudo 사용은 피하고, 다음과 같이 소유권을 변경하거나 npm의 전역 설치 경로를 변경하는 것을 권장합니다.
      sudo chown -R $(whoami) ~/.npm
      sudo chown -R $(whoami) /usr/local/lib/node_modules
    3. Node.js 버전 확인 및 변경: nvm (Node Version Manager)을 사용하여 프로젝트에 맞는 Node.js 버전으로 변경합니다.
      nvm use <version_number> # 예: nvm use 18
    4. 네트워크 확인: 프록시 설정, VPN 연결 등을 확인하고, 공용 네트워크에서 다시 시도해 봅니다.

포트 충돌 (EADDRINUSE)

  • 오류 메시지 예시: "Error: listen EADDRINUSE: address already in use :::3000"
  • 원인: Next.js 개발 서버(기본적으로 3000번 포트)가 사용하려는 포트가 이미 다른 프로세스에 의해 사용 중일 때 발생합니다.
  • 해결 방법
    1. 현재 실행 중인 Next.js 서버 종료: 터미널에서 Ctrl + C를 눌러 종료합니다.
    2. 포트 사용 프로세스 확인 및 종료
      • 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 종료
    3. 다른 포트 사용: 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).
  • 해결 방법
    1. 타입 정의 확인: 변수, 함수 매개변수, 컴포넌트 Props 등의 타입을 정확하게 정의했는지 확인합니다.
    2. 외부 라이브러리 타입 설치: 사용하는 서드파티 라이브러리의 타입 정의가 누락된 경우 @types/ 패키지를 설치합니다. (예: npm install --save-dev @types/node @types/react @types/react-dom)
    3. tsconfig.json 검토: compilerOptionsstrict 모드, lib 설정 등을 확인하여 프로젝트에 맞게 구성되어 있는지 확인합니다.
    4. IDE 활용: VS Code와 같은 IDE는 TypeScript 오류를 실시간으로 표시해주므로, 이를 적극 활용하여 오류 발생 즉시 수정합니다.
    5. 캐시 삭제: 때때로 .next 폴더를 삭제하고 다시 빌드/실행하면 해결되는 경우가 있습니다.

모듈 임포트 오류

  • 오류 메시지 예시: "Module not found: Can't resolve '...' in '...'", "Cannot find module '...'"
  • 원인: 잘못된 파일 경로, 대소문자 불일치(특히 Linux 환경 배포 시), 모듈이 실제로 설치되지 않았거나 삭제된 경우.
  • 해결 방법
    1. 경로 확인: 임포트하는 파일의 경로가 정확한지, 오타는 없는지 확인합니다. 상대 경로(../)와 절대 경로(@/) 모두 확인합니다.
    2. 대소문자 일치: 파일 시스템에서 파일 이름과 임포트 구문의 대소문자가 정확히 일치하는지 확인합니다. (Windows는 대소문자 구분을 하지 않지만, Linux는 구분합니다.)
    3. 패키지 설치 확인: 해당 모듈이 node_modules에 실제로 설치되어 있는지 package.json과 비교하여 확인합니다. 누락되었다면 npm install 또는 yarn add로 설치합니다.
    4. tsconfig.json paths 설정: 절대 경로를 사용한다면 tsconfig.jsonpaths 설정이 올바른지 확인합니다.
      // tsconfig.json
      {
        "compilerOptions": {
          "baseUrl": ".",
          "paths": {
            "@/*": ["./*"] // src/ 또는 app/ 디렉토리를 기준으로 설정
          }
        }
      }

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 함수 컴포넌트의 최상위 레벨 또는 커스텀 훅 내에서만 호출되어야 합니다. 조건문, 루프, 중첩된 함수 내에서 호출될 때 발생합니다.
  • 해결 방법
    1. 규칙 준수: 모든 훅 호출이 컴포넌트의 최상위 스코프에 있는지 확인합니다.
    2. 컴포넌트 명명 규칙: 함수 컴포넌트는 항상 대문자로 시작해야 합니다.
    3. use client 지시문 누락: App Router에서 클라이언트 측 훅을 사용하는 컴포넌트 상단에 "use client"; 지시문을 추가했는지 확인합니다.

서버 사이드 렌더링 (SSR) 및 데이터 페칭 오류

Next.js의 핵심 기능인 SSR 및 데이터 페칭 과정에서 발생하는 오류들입니다.

fetch 또는 데이터베이스 연결 오류

Server Components / API Routes에서 데이터를 페칭할 때 발생하는 오류입니다.

  • 오류 메시지 예시: "Failed to connect to database", "Network request failed", "TypeError: Cannot read properties of undefined (reading 'map')"
  • 원인
    • 환경 변수 누락/오류: 데이터베이스 연결 문자열, API 키 등 중요한 환경 변수가 올바르게 설정되지 않았거나 누락된 경우.
    • 네트워크 문제: 서버리스 함수가 외부 데이터베이스나 API에 연결할 수 없는 경우 (방화벽, IP 허용 목록 등).
    • 데이터 직렬화 문제: 서버에서 가져온 데이터가 클라이언트 컴포넌트로 전달될 때 JSON 직렬화가 불가능한 객체(Date 객체, 함수 등)를 포함할 때.
    • 잘못된 데이터 구조: 예상했던 데이터 구조와 실제 페칭된 데이터 구조가 다를 때.
  • 해결 방법
    1. 환경 변수 확인: .env.local 파일과 배포 환경(Vercel 대시보드 등)의 환경 변수가 올바르게 설정되었는지 다시 확인합니다. process.env.MY_VAR 형태로 접근 시 오타가 없는지도 확인합니다.
    2. 데이터베이스/API 접근성 테스트: 별도의 스크립트나 Postman 등으로 데이터베이스나 외부 API에 직접 연결하여 정상 작동하는지 확인합니다. 클라우드 DB의 경우 IP 허용 목록에 Vercel의 IP 범위가 추가되었는지 확인합니다.
    3. 데이터 직렬화: 서버 컴포넌트에서 클라이언트 컴포넌트로 전달되는 Props는 JSON으로 직렬화될 수 있는 형태여야 합니다. Date 객체는 date.toISOString() 등으로 문자열로 변환하고, 함수는 전달하지 않습니다. Mongoose의 lean() 메서드를 사용하여 순수 JavaScript 객체를 반환하면 직렬화 문제를 줄일 수 있습니다.
    4. 오류 처리 및 로깅: try...catch 블록을 사용하여 데이터 페칭 로직을 감싸고, 콘솔에 자세한 오류 메시지를 로깅하여 문제의 원인을 파악합니다.

Hydration 오류

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 변경.
    • 클라이언트 전용 컴포넌트가 서버에서 렌더링되는 경우.
  • 해결 방법
    1. 환경 독립적인 코드: 서버와 클라이언트에서 동일한 결과를 보장하는 코드를 작성합니다. (예: Date 객체는 문자열로 변환하여 사용).
    2. 클라이언트 전용 컴포넌트 처리: next/dynamicssr: false 옵션을 사용하여 클라이언트에서만 렌더링되도록 합니다.
      import dynamic from 'next/dynamic';
      
      const NoSSRDynamicComponent = dynamic(() => import('../components/ClientOnlyComponent'), { ssr: false });
      
      function MyPage() {
        return <NoSSRDynamicComponent />;
      }
    3. 조건부 렌더링 (useEffect): 클라이언트에서만 존재하는 전역 객체(예: window)를 사용하는 경우, useEffect 훅 내부에서 해당 로직을 실행하여 클라이언트 렌더링 시점에만 작동하도록 합니다.
      import { useState, useEffect } from 'react';
      
      function ClientSideText() {
        const [text, setText] = useState('');
        useEffect(() => {
          setText(window.innerWidth > 768 ? 'Large Screen' : 'Small Screen');
        }, []);
        return <div>{text}</div>;
      }
    4. Suspense 경계 사용: Hydration 오류가 발생하는 컴포넌트 주변을 <Suspense>로 감싸면, 오류가 전체 애플리케이션으로 전파되는 것을 막고 폴백 UI를 표시할 수 있습니다.

Vercel 배포 관련 오류

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

빌드 실패 (Build Failed)

  • 오류 메시지 예시: "Module not found", "TypeScript errors", "Command failed with exit code 1"
  • 원인
    • 로컬에서는 성공적으로 빌드되지만, Vercel 환경에서는 실패하는 경우가 있습니다. 이는 주로 대소문자 불일치(Linux 기반 서버), 환경 변수 누락, package.jsonscript 오류, Node.js 버전 불일치 등이 원인입니다.
  • 해결 방법
    1. Vercel 빌드 로그 확인: Vercel 대시보드에서 해당 배포의 빌드 로그를 상세히 확인하여 정확한 오류 메시지를 파악합니다.
    2. 환경 변수 확인: Vercel 프로젝트 설정의 환경 변수(Environment Variables) 섹션에 필요한 모든 변수들이 올바르게 추가되었는지 확인합니다. 특히 MONGODB_URI 같은 민감한 정보는 빌드 시 접근 가능해야 합니다.
    3. 대소문자 일치 확인: 파일 이름 및 임포트 경로의 대소문자가 로컬과 Git 리포지토리, 그리고 Vercel 서버(Linux 기반)에서 모두 일치하는지 확인합니다.
    4. Node.js 버전 일치: package.jsonengines 필드를 추가하여 Vercel에서 사용할 Node.js 버전을 명시할 수 있습니다. (예: "engines": { "node": ">=18.x" })
    5. next.config.js 검토: Next.js 설정 파일에 오류가 없는지 확인합니다.
    6. vercel.json (고급): 필요하다면 vercel.json 파일을 사용하여 빌드 명령어나 배포 설정을 커스터마이징합니다.

런타임 오류 (배포 후)

  • 오류 메시지 예시: 웹 페이지에 500 에러, "Application error: a client-side exception has occurred"
  • 원인: 배포된 서버리스 함수나 페이지에서 런타임 오류가 발생하는 경우입니다. 데이터베이스 연결 문제, 외부 API 호출 실패, 잘못된 로직 등이 원인일 수 있습니다.
  • 해결 방법:
    1. Vercel 런타임 로그 확인: Vercel 대시보드에서 "Logs" 탭을 확인하여 서버리스 함수나 Edge Function에서 발생한 런타임 오류 메시지를 확인합니다. 상세한 스택 트레이스를 통해 문제의 원인을 파악할 수 있습니다.
    2. 재현 가능한 시나리오: 로컬 환경에서 프로덕션 빌드(npm run build && npm run start)를 통해 배포 환경과 유사하게 테스트하여 오류를 재현하고 디버깅합니다.
    3. 데이터 직렬화 문제 재확인: 서버에서 클라이언트로 전달되는 데이터에 직렬화 불가능한 객체가 포함되어 있는지 다시 확인합니다.

일반적인 디버깅 팁

어떤 유형의 오류든 문제를 해결하는 데 도움이 되는 일반적인 디버깅 기술입니다.

  • 콘솔 로깅 (console.log): 가장 기본적인 디버깅 방법입니다. 변수의 값, 함수의 실행 흐름 등을 추적하는 데 유용합니다. Next.js에서는 서버 컴포넌트와 API Routes의 console.log는 터미널에, 클라이언트 컴포넌트의 console.log는 브라우저 개발자 도구 콘솔에 표시됩니다.
  • 브라우저 개발자 도구
    • Console 탭: 클라이언트 측 JavaScript 오류, 경고, console.log 출력을 확인합니다.
    • Elements 탭: 렌더링된 HTML 구조를 확인하여 예상과 다른 DOM 구조를 파악합니다 (Hydration 오류 시 유용).
    • Network 탭: 모든 네트워크 요청(API 호출, 이미지 등)을 모니터링하여 상태 코드, 응답 시간, 데이터 등을 확인합니다.
    • Sources 탭: JavaScript 코드에 브레이크포인트(Breakpoint)를 설정하여 코드 실행을 일시 중지하고 변수 값을 검사하는 등 상세 디버깅을 수행합니다. (Source Map이 활성화되어 있어야 함)
  • VS Code 디버거
    • VS Code의 내장 디버거를 사용하여 Node.js 환경(API Routes, Server Components)과 브라우저 환경(클라이언트 컴포넌트) 모두에서 디버깅할 수 있습니다.
    • launch.json 파일을 설정하여 디버깅 구성을 저장할 수 있습니다.
  • 단계별 커밋 (git blame): 문제가 발생한 지점을 찾기 위해 Git 히스토리를 확인합니다. 특히 git blame <file> 명령어를 사용하면 파일의 각 줄이 마지막으로 변경된 커밋과 작성자를 확인할 수 있어 유용합니다.
  • 오류 메시지 검색: 오류 메시지는 문제 해결의 가장 중요한 단서입니다. 오류 메시지를 그대로 복사하여 Google이나 Stack Overflow에서 검색하면 유사한 문제와 해결책을 찾을 가능성이 높습니다.

문제 해결은 개발 과정의 필수적인 부분이며, 꾸준한 연습을 통해 실력을 향상시킬 수 있습니다. 오류 메시지를 주의 깊게 읽고, 체계적인 접근 방식을 취하며, 다양한 디버깅 도구를 활용하면 어떤 문제든 효과적으로 해결할 수 있을 것입니다.