일요일

Python ModuleNotFoundError 해결 — pip install 했는데 왜 못 찾냐

🔍 검색 키워드: ModuleNotFoundError No module named, pip install 했는데 에러, Python 모듈 못 찾는 문제, ImportError No module named 해결, 파이썬 가상환경 모듈 에러, pip install 후 import 에러

pip install pandas 했다. 분명히 잘 됐다. 근데 코드 실행하면 이 에러가 난다.

ModuleNotFoundError: No module named 'pandas'

이걸 처음 보는 사람은 당황하고, 두 번 이상 본 사람은 원인을 알면서도 순간 헷갈린다. 이 에러는 "모듈이 없다"는 게 아니라 "네가 실행하는 Python이 설치된 곳을 못 찾는다"는 뜻이다. 패턴별로 원인과 해결을 정리한다.


핵심 원인: pip와 python이 같은 환경을 보고 있지 않다

이 에러의 90%는 여기서 나온다. 시스템에 Python이 여러 개 있거나, 가상환경이 활성화되지 않은 상태에서 설치하면 발생한다.

먼저 어떤 Python, 어떤 pip를 쓰는지 확인한다:

# 현재 사용 중인 Python 경로 확인
which python
which python3
python --version

# 현재 사용 중인 pip 경로 확인
which pip
which pip3

# pip가 어디에 설치하는지 확인
pip show pandas

pip show pandas를 했는데 나오면 설치는 된 것이다. 근데 python이 다른 경로면 서로 다른 환경이다.


해결 1: python -m pip 를 쓴다 (가장 확실한 방법)

# ❌ 이렇게 쓰면 pip가 어떤 Python에 연결됐는지 불확실
pip install pandas

# ✅ 이렇게 쓰면 실행하는 Python과 100% 같은 환경에 설치
python -m pip install pandas
python3 -m pip install pandas

python -m pip는 현재 실행 중인 Python 인터프리터에 pip를 직접 연결한다. 이 방법이 가장 확실하다.


해결 2: 가상환경 활성화 확인

가상환경(venv)을 쓰면 activate가 되어 있어야 한다. 안 된 상태에서 설치하면 시스템 Python에 설치되고, 가상환경 Python은 못 찾는다.

# 가상환경 생성
python -m venv venv

# 활성화 — macOS/Linux
source venv/bin/activate

# 활성화 — Windows
venv\Scripts\activate

# 활성화 확인 (앞에 (venv) 표시 나옴)
# (venv) user@machine:~$

# 활성화된 상태에서 설치
pip install pandas

# 비활성화
deactivate

IDE(VS Code, PyCharm)를 쓰면 인터프리터 설정도 확인한다.

  • VS Code: Ctrl+Shift+PPython: Select Interpreter → 가상환경 경로 선택
  • PyCharm: SettingsProjectPython Interpreter → 가상환경 선택

해결 3: PyPI 패키지명과 import명이 다른 경우

설치는 됐는데 이름이 달라서 못 찾는 케이스다. 대표적인 예시들:

# 설치명 → import명 (다른 경우)
pip install Pillow          # import PIL
pip install opencv-python   # import cv2
pip install scikit-learn    # import sklearn
pip install python-dotenv   # import dotenv
pip install beautifulsoup4  # import bs4
pip install pyyaml          # import yaml
# ❌ 잘못 쓰는 경우
import Pillow  # ModuleNotFoundError

# ✅ 맞는 import
import PIL
from PIL import Image

해결 4: Python 버전이 여러 개인 환경 (pyenv, conda 등)

# 현재 활성 Python 버전 확인
pyenv version

# 프로젝트 디렉토리에서 로컬 버전 설정
pyenv local 3.11.8

# 해당 버전으로 설치
python -m pip install pandas

conda를 쓰는 경우:

# conda 환경 목록
conda env list

# 환경 활성화
conda activate myenv

# 해당 환경에 설치 (conda 우선, pip는 없을 때만)
conda install pandas
# 또는
pip install pandas

해결 5: 같은 이름의 .py 파일이 있는 경우

프로젝트 폴더에 numpy.py, random.py 같이 표준 라이브러리나 패키지와 같은 이름의 파일을 만들면 충돌한다.

my_project/
├── main.py
├── numpy.py        ← ❌ 이게 문제 (패키지보다 먼저 import됨)
└── utils.py

# main.py에서
import numpy  # 실제 numpy가 아닌 my_project/numpy.py를 로드 → 에러

파일명을 바꿔야 한다. 표준 라이브러리나 외부 패키지와 이름이 겹치지 않도록 한다.


해결 6: __init__.py 누락 (직접 만든 패키지)

my_project/
├── main.py
└── utils/
    ├── helper.py   ← 있음
    └── __init__.py ← ❌ 없으면 에러

# __init__.py 생성 (내용은 비워도 됨)
touch utils/__init__.py

진단 체크리스트

상황 확인 명령어 해결
설치가 됐는지 모르겠다 pip show 패키지명 없으면 python -m pip install
설치됐는데 못 찾는다 which python vs which pip python -m pip install 사용
가상환경 쓰는데 에러 which python이 venv 경로인지 source venv/bin/activate
IDE에서만 에러 IDE 인터프리터 설정 가상환경 Python 경로로 변경
설치명 헷갈린다 PyPI에서 패키지 검색 import명 확인 후 설치
직접 만든 모듈 에러 디렉토리에 __init__.py 있는지 touch 패키지디렉토리/__init__.py

빠른 진단 스크립트

어디에 설치됐는지 한 번에 보고 싶을 때:

import sys

print("Python 실행 경로:", sys.executable)
print("Python 버전:", sys.version)
print("\nPATH 목록:")
for path in sys.path:
    print(" -", path)

이걸 실행해서 나오는 경로들을 확인한다. 내가 설치한 경로가 sys.path에 없으면 Python이 찾을 수가 없다.


정리

ModuleNotFoundError는 모듈 자체가 없는 게 아니라 현재 Python이 보는 경로에 없는 것이다. 순서대로 확인하면 빠르게 해결된다:

  1. python -m pip install 패키지명으로 설치 — pip와 python 경로 일치 보장
  2. 가상환경 활성화 여부 확인 (which python이 venv 경로인지)
  3. 패키지명과 import명이 다른 경우 확인 (Pillow→PIL, opencv-python→cv2 등)
  4. 같은 이름의 .py 파일 충돌 여부 확인
  5. 직접 만든 패키지라면 __init__.py 누락 확인

python -m pip install을 습관화하면 1~2번 문제는 거의 안 만난다.

Next.js Hydration Error 해결 — 서버/클라이언트 불일치 원인과 디버깅 완전 가이드

🔍 검색 키워드: Next.js hydration error, hydration failed, Text content does not match server-rendered HTML, Hydration Mismatch, next.js 하이드레이션 에러 해결, useEffect hydration, next.js 서버 클라이언트 불일치

Next.js 프로젝트를 띄웠는데 콘솔에 이런 에러가 뜨는 순간 멘탈이 흔들린다.

Error: Hydration failed because the initial UI does not match what was rendered on the server.
Warning: Text content did not match. Server: "2026-06-21" Client: "2026-06-20"

에러 메시지는 있는데 어디서 터지는지 안 보인다. 서버에서는 잘 렌더링되는 것 같은데 클라이언트에서 뭔가 다르다는 것이다. 이 글에서 원인별로 정리해서 어디서 문제인지 빠르게 찾을 수 있도록 쓴다.


하이드레이션(Hydration)이 뭔지 먼저

Next.js는 서버에서 HTML을 미리 렌더링하고, 클라이언트에서 그 HTML 위에 React가 붙는다(hydrate). 이때 서버에서 만든 HTML과 클라이언트가 첫 렌더링하는 결과물이 완전히 일치해야 한다. 다르면 React가 신뢰를 잃고 에러를 던진다.


원인 1: 브라우저 전용 API를 렌더 중에 접근

가장 흔한 케이스다. window, document, localStorage 같은 객체는 서버(Node.js)에 없다.

// ❌ 잘못된 코드 — 서버에서 window가 undefined라 에러
function MyComponent() {
  const width = window.innerWidth; // 서버에서 undefined
  return <div>너비: {width}px</div>;
}
// ✅ 해결 — useEffect 안에서만 접근
import { useState, useEffect } from 'react';

function MyComponent() {
  const [width, setWidth] = useState(0);

  useEffect(() => {
    setWidth(window.innerWidth);
  }, []);

  return <div>너비: {width}px</div>;
}

또는 컴포넌트 전체를 클라이언트 전용으로 만들어야 한다면:

// ✅ dynamic import + ssr: false
import dynamic from 'next/dynamic';

const ClientOnlyComponent = dynamic(() => import('./ClientOnlyComponent'), {
  ssr: false,
});

원인 2: 날짜/시간 값 불일치

두 번째로 흔하고, 찾기 까다롭다. 서버는 UTC로 렌더링하고 클라이언트는 로컬 타임존으로 렌더링한다.

// ❌ 문제 코드
function PostDate() {
  const today = new Date().toLocaleDateString('ko-KR');
  return <span>{today}</span>;
  // 서버(UTC): "2026. 6. 21."
  // 클라이언트(KST +9): "2026. 6. 22."
}
// ✅ 해결 1 — 서버에서 데이터로 날짜를 받아 props로 전달
function PostDate({ date }: { date: string }) {
  return <span>{date}</span>;
}

// 서버 컴포넌트에서:
export default function Page() {
  const date = new Date().toISOString().split('T')[0]; // "2026-06-21" (UTC 기준 고정)
  return <PostDate date={date} />;
}
// ✅ 해결 2 — 클라이언트 전용 렌더링
function PostDate() {
  const [date, setDate] = useState('');

  useEffect(() => {
    setDate(new Date().toLocaleDateString('ko-KR'));
  }, []);

  if (!date) return null;
  return <span>{date}</span>;
}

원인 3: Math.random() / Date.now() 같은 불확정 값

서버와 클라이언트에서 각각 한 번씩 실행되므로 결과가 달라진다.

// ❌ 문제 코드
function Avatar() {
  const id = Math.floor(Math.random() * 1000); // 서버 42, 클라이언트 731
  return <img src={`/avatar/${id}.jpg`} />;
}

// ✅ 해결 — userId 기반의 결정론적 값 사용
function Avatar({ userId }: { userId: number }) {
  return <img src={`/avatar/${userId % 100}.jpg`} />;
}

원인 4: 인증 상태 불일치 (Clerk, NextAuth 등)

인증 라이브러리 쓸 때 자주 발생한다. 서버는 쿠키 없이 렌더링하고, 클라이언트는 로그인 상태를 감지해서 다른 UI를 그린다.

// ✅ 해결 - 인증 상태에 따라 분기되는 UI는 클라이언트 전용으로
'use client';

import { useUser } from '@clerk/nextjs';

export default function Header() {
  const { isLoaded, isSignedIn } = useUser();

  if (!isLoaded) return null; // 로드 전엔 아무것도 렌더링 안 함

  return isSignedIn ? <UserButton /> : <SignInButton />;
}

원인 5: Partial Prerendering(PPR) + Suspense 경계 없음 (Next.js 15+)

Next.js 15 이상에서 PPR(Partial Prerendering)을 쓸 때, 동적 데이터 영역을 <Suspense>로 감싸지 않으면 미스매치가 난다.

// ✅ 해결 — 동적 컴포넌트를 Suspense로 래핑
import { Suspense } from 'react';

export default function Page() {
  return (
    <Suspense fallback={<div>로딩 중...</div>}>
      <DynamicContent />
    </Suspense>
  );
}

async function DynamicContent() {
  const data = await fetchDynamicData();
  return <div>{data.value}</div>;
}

원인 6: 빌드 캐시 오염

코드는 맞는데 .next 캐시가 오래된 경우다. 주로 use client 경계 변경 후 발생한다.

# 캐시 완전 삭제 후 재시작
rm -rf .next
npm run dev

# 프로덕션 빌드라면
rm -rf .next
npm run build
npm start

빠른 진단 체크리스트

증상 의심 원인 해결
window is not defined 브라우저 API 직접 접근 useEffect 안에서 접근
날짜/숫자 값 불일치 new Date(), Math.random() 렌더 중 사용 useEffect 또는 서버 props 전달
인증 UI 불일치 인증 상태 SSR isLoaded 체크 후 렌더
코드 변경 후 갑자기 발생 빌드 캐시 오염 rm -rf .next 후 재시작
PPR 활성화 후 발생 Suspense 경계 없음 동적 컴포넌트 Suspense 래핑

정리

Hydration Error는 "서버에서 만든 HTML과 클라이언트 첫 렌더가 다르다"는 한 가지 원인에서 파생된다. 원인 찾는 순서:

  1. 브라우저 전용 API(window, document, localStorage) 렌더 중 직접 접근 여부 확인
  2. new Date(), Math.random() 같은 비결정론적 값 렌더 중 사용 여부 확인
  3. .next 캐시 삭제 후 재시작
  4. 인증 라이브러리 상태 로드 완료 전 렌더 여부 확인
  5. PPR 설정과 Suspense 경계 검토

대부분은 1~3번에서 해결된다.