[트러블슈팅 - Docker / Next.js] Module not found: Can't resolve '@/app/components/feed/FeedList’ Module not found: Can't resolve '@/components/Menubar’
에러메세지/문제상황
Docker 환경에서 Next.js 애플리케이션을 빌드할 때 다음과 같은 에러가 발생함:
Module not found: Can't resolve '@/app/components/feed/FeedList'
Module not found: Can't resolve '@/components/Menubar'
- 해당 파일은 실제로 존재함에도 불구하고 Next.js 빌드 중 모듈을 찾을 수 없다는 오류가 발생함
- 로컬에서는 정상 동작하나, Docker에서만 발생하는 점이 주요 특징이었음
원인
1차 원인
초기 프로젝트 구조가 다음과 같았음:
FRONTEND/
├── src/
│ └── app/
│ └── components/
│ └── feed/
│ └── FeedList.tsx
components 폴더가 app 폴더 내부에 위치해 있었고, import 경로는 다음과 같았음:
import FeedList from "@/app/components/feed/FeedList";
tsconfig.json은 아래와 같이 설정되어 있었음:
"paths": {
"@/*": ["./src/*"]
}
따라서 @/app/components/... 경로는 이론상으로 ./src/app/components/...에 대응되므로 올바른 듯 보였으나, 실제로는 Next.js의 App Router 특성과 Webpack path alias 처리 방식 상 라우트 디렉토리인 app/ 내부를 일반 컴포넌트 저장소로 쓰는 것이 적절하지 않음.
⇒ app/components를 쓰면 components가 또 다른 페이지로 간주될 수 있음
2차 원인
구조를 수정한 이후에도 동일한 에러 메시지가 발생했음:
Module not found: Can't resolve '@/components/Menubar'
이후 프로젝트 구조는 다음과 같았음:
FRONTEND/
├── src/
│ ├── app/
│ └── components/
│ └── Menubar.tsx
import 경로도 다음과 같이 변경함:
import Menubar from "@/components/Menubar";
하지만 Docker 환경에서 여전히 동일한 에러가 반복됨.
시도
시도 1
기존 구조(app/components)에서 components를 src/ 하위로 분리함.
FRONTEND/
├── src/
│ ├── app/
│ └── components/
│ └── Menubar.tsx
import 경로도 @/components/... 형태로 수정함.
import FeedList from "@/components/feed/FeedList";
그럼에도 Docker에서 여전히 동일한 Module not found 에러 발생함.
시도 2
- 경로를 맞췄는데도 같은 에러가 발생하니, import 구문이 잘못쓰였는지, 대소문자 실수가 있었는지, 실제 해당 파일이 존재하는지를 모두 체크함.
RUN ls -R /app/src/components/
빌드 로그에서 실제로 FeedList.tsx, Menubar.tsx 등이 /app/src/components 하위에 존재하는 것이 확인됨.
시도 3
"paths": {
"@/*": ["./src/*"],
"@/components/*": ["./src/components/*"]
}
그러나 여전히 Docker 빌드에서 동일한 경로 에러 발생함.
시도 4
import path from 'path';
const nextConfig = {
output: "standalone",
webpack: (config) => {
config.resolve.alias = {
...config.resolve.alias,
'@': path.resolve(__dirname, 'src'),
'@/components': path.resolve(__dirname, 'src/components')
};
return config;
},
};
export default nextConfig;
이후 다시 빌드 시도했으며, 해당 설정 추가 이후부터는 더 이상 경로 관련 에러가 발생하지 않음.
- nextjs에서 여러 파일들을 하나로 패키징하는 도구
- 여러 군데에서 작성한 TS/JS 파일들을 하나의 JavaScript 파일로 묶어줌
- import/export 문법들도 브라우저가 이해할 수 있도록 변환해줌.
⇒’@’ 표시를 src/ 폴더 전체를 의미한다고 지정하고, ‘@/components’ 는 src/components/폴더를 직접 alias하도록 명확히 지정하였음.next.config.ts에 명시적으로 Webpack alias를 추가한 것이 최종 해결책이었음.Webpack 레벨에서 명확하게 경로 alias를 지정해야 Docker/Linux 환경에서도 import 경로 해석이 정확히 이루어졌음.
webpack이란?
- nextjs에서 여러 파일들을 하나로 패키징하는 도구
- 여러 군데에서 작성한 TS/JS 파일들을 하나의 JavaScript 파일로 묶어줌
- import/export 문법들도 브라우저가 이해할 수 있도록 변환해줌.
⇒’@’ 표시를 src/ 폴더 전체를 의미한다고 지정하고, ‘@/components’ 는 src/components/폴더를 직접 alias하도록 명확히 지정하였음.
해결책
next.config.ts에 명시적으로 Webpack alias를 추가한 것이 최종 해결책이었음.
Next.js에서 App Router와 함께 경로 alias를 사용할 때, 환경에 따라 tsconfig.json만으로는 빌드 단계에서 정확한 경로 해석이 보장되지 않음.
Webpack 레벨에서 명확하게 경로 alias를 지정해야 Docker/Linux 환경에서도 import 경로 해석이 정확히 이루어졌음.
회고 및 정리
- 동일한 코드가 로컬에서는 정상 작동하고 Docker에서만 실패하는 경우, 환경 의존적인 차이(OS, 파일 시스템, 빌드 파서 등)를 먼저 의심해야 함.
- Next.js는 App Router 아래 구조에 대해 엄격한 처리 방식을 가지고 있으며, 해당 영역에 일반 컴포넌트를 포함하는 것은 바람직하지 않음.
- 경로 alias는 tsconfig.json과 Webpack 둘 다 일치하게 설정해야 안정적인 빌드가 가능함.
- 대소문자 구분 문제나 COPY 실패와 같은 낮은 단계 이슈가 아닌, Webpack 빌드 해석기와 alias 충돌이라는 높은 추상화 레벨의 문제였음.
이번 경험을 통해 Docker 환경에서 Next.js를 사용하는 경우, import 경로 alias는 반드시 Webpack과 tsconfig에 이중 정의하고, App Router 디렉토리와 컴포넌트 디렉토리는 분리하는 것이 안정적임을 배웠다!