바이브덕

Cloudflare Pages 빌드 실패, 로그 3자리

로컬에서 251ms 만에 끝난 빌드가 배포에서는 죽어요.

리눅스 컨테이너로 세 가지를 깨뜨려 보니 마지막 줄이 원인인 건 셋 중 하나였습니다.

설치에서 죽었는지 빌드에서 죽었는지 가르는 법부터 적었어요.

제 노트북에서 빌드는 251ms 만에 끝났어요. 같은 파일을 리눅스에 올렸더니 이 줄에서 멈췄습니다.

[UNRESOLVED_IMPORT] Could not resolve './components/button.jsx' in src/App.jsx

파일은 있어요. src/components/Button.jsx. 대문자 B 하나가 다릅니다.

배포 로그를 붙잡고 세 가지로 깨뜨려 봤는데, 로그 마지막 줄이 원인을 알려준 건 셋 중 하나뿐이었어요. 하나는 원인이 로그 한참 위쪽에 경고로 지나가 있었고, 하나는 로그 어디에도 안 적혀 있었습니다.

설치에서 죽었나, 빌드에서 죽었나

로그를 열면 맨 아래 빨간 줄부터 보게 돼요. 그 전에 한 가지를 먼저 가르면 찾을 범위가 절반으로 줄어듭니다. 배포는 크게 두 단계로 돌아갑니다. 먼저 라이브러리를 내려받고(설치), 그다음 내 코드를 묶습니다(빌드). 그래서 로그에서 이걸 찾으면 됩니다.

로그에 빌드 도구 이름(vite, next, astro 같은 것)이 한 번이라도 나왔나. 안 나왔으면 빌드는 시작도 못 한 거예요.

npm error로 끝났는데 그 위에 빌드 도구 이름이 없다면 설치에서 죽은 겁니다. 이때 코드를 고치는 건 헛수고예요. 코드는 아직 읽히지도 않았거든요.

설치에서 죽는 자리: lock 파일

AI에게 “달력 기능 넣어 줘”라고 하면 라이브러리를 하나 추가해요. 그때 package.json에는 이름이 적히는데, 짝인 package-lock.json은 안 따라오는 경우가 있습니다. 설치를 안 돌렸거나, 돌렸는데 그 파일만 커밋에서 빠졌을 때요.

그 상태를 만들어서 컨테이너에 넣어 봤어요.

npm error code EUSAGE
npm error
npm error `npm ci` can only install packages when your package.json and package-lock.json or npm-shrinkwrap.json are in sync. Please update your lock file with `npm install` before continuing.
npm error
npm error Missing: date-fns@4.4.0 from lock file

Missing: date-fns@4.4.0 from lock file. 어떤 라이브러리가 짝이 안 맞는지까지 이름으로 적혀 있어요. 이 경우는 로그가 정직합니다.

내 쪽에서 이 상태인지 확인하는 건 명령 한 줄이에요.

npm ci

평소 쓰는 npm install이 아니라 ci입니다.

install은 어긋난 lock을 알아서 맞춰 버려서, 이 문제가 로컬에서는 절대 안 보여요. ci는 lock에 적힌 그대로만 깔고, 안 맞으면 위 문구를 그대로 뱉습니다.

한 가지는 짚어 둘게요. Cloudflare가 설치 단계에 정확히 어떤 명령을 쓰는지는 공식 문서에 안 적혀 있어요. 그래서 “lock이 어긋나면 항상 여기서 죽는다”가 아니라, 빌드 로그에 저 EUSAGE 문구가 있으면 원인은 여기로 읽는 게 맞습니다.

고치는 것도 두 줄입니다.

npm install
git add package.json package-lock.json && git commit -m "fix: lock 파일 동기화"

package-lock.json은 지저분해 보여도 반드시 같이 커밋합니다. 이 파일이 어긋나면 내 코드는 읽히지도 못하고 멈춰요.

빌드에서 죽는데 원인은 로그 위쪽: Node 버전

두 번째는 좀 고약해요. 같은 소스를 Node 18 컨테이너에 넣었더니 설치는 멀쩡히 끝나고, 빌드가 이렇게 죽었습니다.

file:///app/node_modules/rolldown/dist/shared/create-bundler-option-wRiQzEJ3.mjs:8
import { formatWithOptions, styleText } from "node:util";
                            ^^^^^^^^^
SyntaxError: The requested module 'node:util' does not provide an export named 'styleText'

이 문구를 그대로 검색하면 제 코드와 아무 상관 없는 결과가 나와요. 내가 만들지도 않은 파일 안에서, 들어본 적 없는 이름이 없다고 합니다.

진짜 원인은 로그를 한참 위로 올려야 나와요. 설치 단계에 이게 지나가 있었습니다.

npm warn EBADENGINE Unsupported engine {
npm warn EBADENGINE   package: 'vite@8.2.1',
npm warn EBADENGINE   required: { node: '^20.19.0 || >=22.12.0' },
npm warn EBADENGINE   current: { node: 'v18.20.8', npm: '10.8.2' }
npm warn EBADENGINE }

required는 20.19 이상인데 current가 18.20.8이에요. 빌드 도구가 자기는 이 Node에서 못 돈다고 미리 말한 겁니다.

문제는 이게 warn이라는 거예요. 경고라서 설치는 성공으로 지나가고, added 23 packages가 찍힙니다. 그러고 나서 한참 뒤에 엉뚱한 문구로 죽어요. 이 어긋남은 클로드 코드 설치에서도 똑같이 봤는데, 거기서는 경고가 뜬 쪽이 오히려 멀쩡했어요. npm warn은 무시해도 되는 신호가 아니라, 나중에 청구되는 신호에 가깝습니다.

같은 소스를 Node 22 컨테이너에 넣으면 그냥 통과해요.

v22.23.2
vite v8.2.1 building client environment for production...
✓ 17 modules transformed.
✓ built in 59ms

내 노트북이 Node 22면 로컬에서는 평생 안 보이는 문제예요. Cloudflare Pages는 빌드 이미지 버전마다 기본 Node가 고정돼 있는데, 공식 문서 기준으로 v3가 22.16.0, v2가 18.17.1, v1이 12.18.0입니다. 오래전에 만든 프로젝트가 v2에 남아 있으면 Node 18로 도는 거예요. 문서에는 v2가 v3로 자동 이전되는 시점이 2027년 2월 23일로 적혀 있으니, 지금은 아직 그런 프로젝트가 남아 있는 시기입니다.

버전을 내가 정하는 방법은 두 가지예요. 프로젝트 설정에 NODE_VERSION 환경변수를 넣거나, 저장소 맨 위에 .nvmrc 파일을 만들어 버전만 한 줄 적습니다.

node -v

화면에 v22.14.0처럼 나오면 앞의 v를 뗀 22.14.0.nvmrc에 한 줄로 적습니다. 공식 문서가 쓰는 표기도 22.16.0처럼 접두사가 없어요.

git add .nvmrc && git commit -m "chore: 빌드 Node 버전 고정"

내 컴퓨터에서 되는 그 버전을 그대로 박아 두는 거예요.

참고로 package.jsonengines 필드에 기대면 안 됩니다. 빌드 이미지 v3의 미지원 항목에 engines로 Node를 감지하는 기능이 빠져 있다고 적혀 있어요. v2에서 먹히는지는 문서에 안 나오니, 어느 쪽이든 NODE_VERSION이나 .nvmrc로 못 박는 게 확실합니다.

로그에 답이 없는 자리: 대소문자

맨 앞에서 본 그 에러입니다. Could not resolve './components/button.jsx', 그 아래에 Module not found.가 붙어요.

맥의 기본 파일시스템은 대소문자를 구분하지 않아요. button.jsx라고 적어도 Button.jsx를 열어 줍니다. 리눅스는 다른 파일로 봐요. 빌드 환경은 리눅스라서, 로컬에서 성공한 그 코드가 거기서만 죽습니다.

여기가 앞의 둘과 결정적으로 다른 지점이에요. 로그에 있는 건 내가 잘못 적은 경로뿐이고, 진짜 파일 이름은 안 적혀 있어요. 로그를 아무리 노려봐도 B가 대문자라는 사실은 안 나옵니다. 그건 내 컴퓨터에만 있는 정보예요.

그래서 확인은 로그가 아니라 파일 목록에서 합니다.

git ls-files src/components

여기 찍힌 이름과 에러에 적힌 경로를 한 글자씩 맞춰 봐요. 고칠 때는 파일명이 아니라 import 쪽을 파일명에 맞추는 게 안전합니다. 반대로 하면 함정이 하나 더 있어요.

맥에서 Button.jsxbutton.jsx로 바꾸고 git status를 치면 아무것도 안 나옵니다. 빈 화면이에요. 맥의 git은 core.ignorecase가 켜져 있어서 이름이 바뀐 걸 못 봅니다. 커밋할 게 없으니 푸시해도 저장소는 그대로고, 빌드는 똑같은 자리에서 또 실패해요.

그래서 임시 이름(Button2tmp.jsx)을 거쳐 두 번에 나눠 바꾸는 방법을 써 봤어요. git mv가 이렇게 거절합니다.

fatal: not under version control, source=src/components/button.jsx, destination=src/components/Button2tmp.jsx

git status는 아무 일 없다고 하는데 git mv는 버전 관리 밖이라고 해요. 같은 파일 하나를 두고 두 명령이 다른 말을 합니다. 통하는 건 git이 알고 있는 이름을 출발점으로 주는 것이에요.

git mv src/components/Button.jsx src/components/button.jsx

이러면 R src/components/Button.jsx -> src/components/button.jsx로 잡힙니다. 그냥 import 한 줄을 고치는 쪽이 훨씬 짧죠.

로그를 통째로 AI에 붙이면 되지 않나요

셋 중 둘은 그걸로 풀려요. lock 불일치는 어떤 라이브러리인지 이름까지 적혀 있고, Node 버전도 requiredcurrent가 나란히 있으니까요. 이 글을 안 읽어도 붙여넣기만 하면 답이 나옵니다.

다만 붙여넣을 때 걸리는 게 두 개 있어요.

하나는 범위예요.

원인이 로그 위쪽 경고에 있는 경우, 화면에 보이는 마지막 부분만 복사하면 그 경고가 안 딸려옵니다. 실패한 줄만 보고 styleText를 고치려 들면 영영 못 고쳐요. 로그는 처음부터 끝까지 받거나, 최소한 EBADENGINE으로 한 번 찾아보고 복사해야 합니다.

다른 하나는 대소문자예요.

이건 정보 자체가 로그에 없어요. 없는 정보는 붙여넣어도 안 나옵니다. 내 파일 이름을 같이 알려주기 전까지는요.

빌드 로그에서 볼 자리는 셋이에요. 빌드 도구 이름이 나왔는지(설치냐 빌드냐), EBADENGINE이 있는지(Node 버전), 그리고 로그 밖의 내 파일 이름입니다.

만들다 막히는 자리에서 에러 문구를 읽는 순서는 대체로 같아요. 어디서 죽었는지부터 가르고, 그다음 문구를 봅니다. 순서를 뒤집으면 멀쩡한 코드를 며칠씩 고치게 돼요.

여기 말고 다른 데서 막혔다면

증상만 고르면 지금 뭘 하면 되는지 짚어줘요.

참고 자료

읽으면서 떠오른 사람에게 공유해 주세요

레스덕이 엄지를 들고 있는 포즈

레스덕

· 운영자

현직 개발자가 AI·바이브코딩·개발자 커리어를 직접 겪고 판단한 개인 기록입니다. 공식 자료를 간략히 요약하고, 그 위에 저의 경험·판단을 덧붙입니다. 전문 자문이 아니므로, 중요한 결정 전에는 최신 원문과 전문가 상담을 함께 확인해 주세요.

최종 수정 2026.08.14 · 문의 lessduck2@gmail.com

관련 글 · 테크

검색어를 입력하면 글 본문에서 찾아드려요.

본문 + 제목 검색전체 검색 페이지 →