
로컬에선 빌드가 되는데 배포만 터졌습니다
글 하나 올리려다 이틀을 썼습니다.
앞선 두 글에서 배포하다 막힌 곳들과 한글에서 깨진 것들을 정리하고 나서, 이제 손 볼 데는 다 봤다고 생각했습니다. 그래서 평소처럼 글을 쓰고 git push를 했습니다. 그런데 30분이 지나도 블로그에 안 올라옵니다.
빌드 로그를 열었더니 빨간 글씨가 있었습니다. 그날 빌드는 두 번 터졌고, 두 번 다 원인이 달랐고, 두 번 다 제 PC에서는 아무 문제 없이 빌드가 됐습니다.
1. “로컬에선 되는데요”
첫 번째 로그입니다.
[ERROR] Cannot find module '@astrojs/markdown-remark'
Location: /opt/buildhome/repo/astro.config.mjs
설정 파일에서 그 패키지를 가져다 쓰고 있었습니다. 한글 글에서 엔터 한 번을 줄바꿈으로 처리하려고 remark-breaks를 붙였는데, 그러려면 마크다운 처리기를 직접 지정해야 했거든요.
import { unified } from '@astrojs/markdown-remark';
제 PC에서는 잘 돌아갔습니다. npm run build를 치면 아무 말 없이 끝났어요. 그래서 그냥 올렸습니다.
문제는 이 패키지를 package.json에 적어두지 않았다는 것이었습니다.
npm은 설치한 패키지들의 의존성을 node_modules 아래에 최대한 평평하게 펼쳐놓습니다. 다른 패키지가 자기 몫으로 데려온 부품도 결국 node_modules/@astrojs/markdown-remark라는, 제가 직접 설치했을 때와 똑같은 자리에 놓입니다. 제 경우엔 @astrojs/mdx가 데려온 것이었습니다. 그래서 선언한 적 없는 패키지인데도 import가 성공했습니다. 제 코드가 남의 부품을 몰래 꺼내 쓰고 있었던 겁니다.
배포 서버는 사정이 다릅니다. 빌드 로그를 보면 Cloudflare는 npm install이 아니라 npm clean-install을 씁니다. node_modules를 통째로 지우고 lockfile에 적힌 그대로 다시 까는 명령입니다. 매번 같은 결과를 보장하는 대신, 우연히 얻어걸리던 것도 같이 사라집니다.
import한 것은 전부 package.json에 있어야 합니다. 없어도 돌아간다면 그건 통과가 아니라 유예입니다.
2. 되돌렸는데 또 터졌습니다
일단 설정을 원래대로 되돌리고 다시 push했습니다. 이번엔 문구가 달랐습니다.
[ERROR] Cannot find package '@astrojs/markdown-remark',
which is no longer installed by default now that Sätteri
is the default Markdown processor.
Install it with: npm install @astrojs/markdown-remark
되돌렸는데 왜 같은 패키지 이름이 또 나올까요.
며칠 전에 npm update astro를 한 번 쳤던 게 원인이었습니다.

에러에 Sätteri라는 이름이 나와서 처음엔 7.2.10에서 마크다운 엔진이 바뀐 줄 알았습니다. 아니었습니다. Sätteri는 Astro 7.0부터 이미 기본 처리기였고, 이 에러 문구도 그때부터 있던 안내입니다. Sätteri는 remark 플러그인을 실행하지 않아서, 플러그인을 쓰려면 예전 처리기인 @astrojs/markdown-remark를 따로 깔아야 합니다.
7.2.10에서 실제로 바뀐 건 한 줄이었습니다. astro는 @astrojs/markdown-remark를 “있으면 쓰는” 부품으로 두고 맞아야 하는 버전을 적어두는데, 그 조건이 “정확히 7.2.4”에서 “7.3.0 이상”으로 바뀌었습니다. 그런데 제 node_modules에 있던 건 제가 깐 게 아니라 mdx가 데려온 7.2.4였습니다. 조건이 딱 맞을 때는 그게 최상위 자리에 놓여 빌려 쓸 수 있었는데, 조건이 바뀌면서 그 자리를 더는 보장받지 못하게 된 겁니다. 결국 1번과 뿌리가 같았습니다.
제 package.json에는 이렇게 적혀 있었습니다.
"astro": "^7.2.9"
앞에 붙은 캐럿(^)은 “7점대 안에서는 알아서 최신으로”라는 뜻입니다. 7.2.9도 되고 7.2.10도 됩니다. npm update는 그 범위 안에서 가장 최신 버전을 찾아 lockfile을 고쳐 씁니다. 그래서 제가 코드를 한 줄도 건드리지 않은 사이에 빌려 쓰던 부품의 조건이 바뀌어 있었습니다.
숫자로는 0.0.1 차이입니다. semver에서 맨 뒤 자리는 “고쳐도 티 안 나는 수정”을 뜻합니다.
Astro를 탓할 일은 아닙니다. 바뀐 내용은 릴리스 노트에 한 줄로 적혀 있었고, 에러 메시지에는 해결 명령까지 그대로 나와 있었습니다. 저는 릴리스 노트를 안 읽고 npm update부터 쳤고, 그다음엔 에러를 “또 그 패키지네”까지만 읽고 설정을 되돌리느라 반나절을 더 썼습니다.
3. 고친 방법
두 문제의 답이 결국 같았습니다. 쓰는 패키지를 제대로 선언하는 것.
npm install @astrojs/markdown-remark
그리고 설정에서 처리기를 명시했습니다.
import { unified } from '@astrojs/markdown-remark';
import remarkBreaks from 'remark-breaks';
export default defineConfig({
markdown: {
processor: unified({ remarkPlugins: [remarkBreaks] }),
},
});
빠른 Sätteri를 쓰고 싶으면 이 설정을 지우고 remark-breaks를 포기하면 됩니다. 저는 한글 글에서 엔터 한 번이 줄바꿈으로 보이는 쪽이 더 중요해서 예전 처리기를 남겼습니다. 블로그 빌드는 어차피 몇 초짜리라 속도 차이가 체감되지 않습니다.
4. 그래서 바꾼 습관 세 가지

첫째, push 전에 배포 서버와 같은 방식으로 한 번 빌드합니다.
rm -rf node_modules
npm ci
npm run build
npm ci는 배포 서버가 하는 것과 같은 일을 합니다. lockfile대로만 설치하고, package.json과 lockfile이 어긋나 있으면 조용히 맞춰주는 대신 에러를 내고 멈춥니다. 30초쯤 더 걸리는 대신 “로컬에선 되는데”라고 말할 일이 없어집니다.
둘째, npm update는 글 올리기 직전에 하지 않습니다.
업데이트를 했으면 lockfile이 어디까지 움직였는지 눈으로 확인합니다.
git diff package-lock.json | grep '"version"'
메이저 버전이 그대로라고 안심할 일이 아니라는 걸 이번에 알았습니다.
셋째, 에러 메시지를 끝까지 읽습니다.
두 번째 에러에는 Install it with: npm install @astrojs/markdown-remark라고 답이 적혀 있었습니다. 저는 그 줄을 안 읽고 반나절을 썼습니다. 요즘 도구들은 생각보다 친절합니다. 문제는 제가 로그를 스크롤해서 빨간 글씨 첫 줄만 본다는 것이었습니다.
남는 이야기
블로그 하나 굴리는 데 lockfile이니 호이스팅이니 하는 걸 알아야 하나 싶었습니다. 그런데 이건 회사 프로젝트에서도 똑같이 나는 사고입니다. 규모가 작아서 이틀 만에 원인까지 짚었을 뿐이지, 사람 여럿이 붙은 저장소였다면 “내 로컬에선 되는데요”를 서로 주고받으며 며칠을 보냈을 겁니다.
남 탓할 데가 없는 프로젝트를 하나 갖고 있으면, 이런 걸 조용히 배우게 됩니다. 그게 직접 만들어 쓰는 이유이기도 하고요.
고친 곳
2026년 9월 29일, 글에 붙일 참고 문서를 하나씩 다시 열어보다가 본문과 어긋나는 곳을 찾아 고쳤습니다.
- 2번의 원인 설명을 고쳤습니다. 처음엔 “7.2.10부터 기본 처리기가 Sätteri로 바뀌었다”고 적었는데, Sätteri는 7.0부터 기본이었습니다. 7.2.10에서 바뀐 건
@astrojs/markdown-remark에 요구하는 버전 조건이었고, 그게 제가 선언 없이 빌려 쓰던 패키지를 건드렸습니다. 그림도 같이 고쳤습니다. - “Cloudflare는
npm clean-install을 쓴다”는 공식 문서가 아니라 빌드 로그에서 본 것이라 그렇게 밝혔습니다.
참고한 문서
- npm 문서 — 폴더 구조와 호이스팅 — 1번. 선언 안 한 패키지가 우연히 찾아지는 이유
- npm 문서 — npm ci — 1·4번. node_modules를 지우고 lockfile 그대로 설치, 어긋나면 멈춤
- npm 문서 — npm update — 2번. 범위 안에서 최신으로 올리는 동작
- node-semver — 캐럿(^) 범위 — 2번. ^7.2.9가 허용하는 구간
- Astro 문서 — 마크다운 처리기 (Sätteri / unified) — 2·3번. Sätteri는 7.0부터 기본, remark 플러그인은 unified 처리기로
- Astro 7.2.10 변경 기록 — 2번. 7.2.10에서 실제로 바뀐 한 줄
댓글
로그인도 이메일도 필요 없습니다. 이름만 적고 편하게 남겨주세요.
지금은 댓글을 불러올 수 없습니다. 잠시 뒤에 다시 시도해 주세요. 급한 내용이면 ryuma@kakao.com로 보내주셔도 됩니다.