Joonmook 프로필 사진

@Joonmook

Blog

sharp 0.35가 Node 18에서 안 돌았다: 네이티브 모듈과 런타임 버전

sharp 0.35가 Node 18에서 안 돌았다: 네이티브 모듈과 런타임 버전 썸네일

업로드 이미지를 WebP로 줄이려고 sharp를 붙였다. 로컬에서는 잘 됐다. 배포하고 나서 서버 로그를 보니 이렇게 찍혀 있었다.

Could not load the "sharp" module using the linux-x64 runtime
Possible solutions:
- Please upgrade Node.js:
Found 18.19.1
Requires >=20.9.0

서버는 Ubuntu 24.04에 apt로 깐 Node 18이고, 로컬은 Node 25다. sharp 0.35부터 최소 버전이 Node 20.9로 올라갔는데, 로컬에서 npm install 할 때는 당연히 아무 문제가 없었으니 알 길이 없었다.

경고는 있었다

정확히 말하면 알 길은 있었다. 서버에서 npm install 을 돌릴 때 이런 경고가 나왔다.

npm WARN EBADENGINE Unsupported engine {
npm WARN EBADENGINE package: 'sharp@0.35.4',
npm WARN EBADENGINE required: { node: '>=20.9.0' },
npm WARN EBADENGINE current: { node: 'v18.19.1', npm: '9.2.0' }
npm WARN EBADENGINE }

EBADENGINE은 경고지 에러가 아니다. 설치는 그냥 된다. 그리고 배포 스크립트 출력에서 경고 열 줄은 아무도 안 읽는다. 나도 안 읽었다. 순수 JS 패키지라면 engines 필드가 틀려도 대부분 돌아가니까 경고로 두는 게 맞는데, 네이티브 모듈은 다르다. 미리 빌드된 바이너리가 특정 Node ABI에 묶여 있어서 런타임에 require 하는 순간 터진다.

서비스가 안 죽은 이유

다행히 블로그는 계속 떠 있었다. sharp를 불러오는 코드를 이렇게 감싸뒀기 때문이다.

let sharp = null;
try {
sharp = require('sharp');
} catch (err) {
console.warn('sharp 를 불러오지 못해 이미지 최적화를 건너뜁니다:', err.message);
}
// ...
if (!sharp) return file; // 최적화 없이 원본 저장

이미지 최적화는 있으면 좋은 기능이지 없으면 서비스가 멈춰야 하는 기능이 아니다. 그래서 처음부터 "없으면 건너뛴다"로 짰다. 이 한 줄 덕분에 배포 직후 사이트는 멀쩡했고, 나는 로그를 보고 나서 고칠 시간을 벌었다. 부가 기능의 의존성은 항상 이렇게 격리하는 게 맞다고 다시 느꼈다.

고친 방법

sharp 0.34 계열은 Node 18.17 이상을 지원한다. package.json에서 버전을 내렸다.

npm install "sharp@^0.34.5"

npm view 로 버전별 engines를 미리 확인하는 습관이 있었으면 처음부터 맞았을 거다.

npm view sharp@0.34.5 engines.node # ^18.17.0 || ^20.3.0 || >=21.0.0
npm view sharp@0.35.0 engines.node # >=20.9.0

진짜 문제는 Node 18이다

사실 sharp가 잘못한 건 없다. Node 18은 2025년 4월에 지원이 끝났다. 보안 패치도 더 안 나온다. 서버를 처음 세팅할 때 apt에 있는 걸 그냥 깔았고, 그 뒤로 한 번도 안 올렸다. 혼자 운영하는 서비스에서 흔한 일이다. 돌아가면 안 건드리게 되니까.

그래서 이번 일은 "sharp 버전 내리기"로 끝낼 게 아니라 "Node 올리기"로 끝내야 맞다. 다만 같은 서버에 다른 앱도 떠 있어서, 그건 시간을 잡고 따로 할 생각이다. 그전까지 0.34에 고정해두는 건 임시방편이라는 걸 package.json 주석으로 남겨뒀다.

그래서 나는

  1. 네이티브 모듈을 추가할 때는 서버의 Node 버전으로 engines를 확인한다. 로컬 설치 성공은 아무것도 보장하지 않는다.
  2. 부가 기능의 의존성은 없어도 서비스가 도는 구조로 격리한다. 이번에 이게 나를 살렸다.
  3. 배포 스크립트에서 EBADENGINE 경고를 에러로 승격시키는 걸 고려 중이다. npm 설정으로는 engine-strict 가 있다.
  4. Node 18은 올해 안에 올린다. 이 글이 그 약속의 기록이다.

Comments 0

No comments yet. Be the first!

← Back to Blog