WebP가 Safari에서 까맣게 변한 날
Safari에서 WebP 배경이 검정으로 변한 원인과 MSW 서비스 워커로 인한 Accept 헤더 문제 해결
2025년 9월 18일
🤔 Next.js <Image> 최적화를 쓰면, 굳이 원본을 WebP로 바꿀 필요가 있을까?
프로젝트를 하다 보면 로컬에 이미지를 모아두는 경우가 많습니다. 이미지가 많아지면 사용자 경험이 좋아질 수 있지만, 용량이 너무 크면 로딩 속도가 느려져서 오히려 불편해집니다.
그래서 png 파일들을 webp로 변환하려 했습니다. 그런데 Next.js <Image> 태그는 png 파일을 넣어도 자동으로 WebP나 AVIF 같은 최신 포맷으로 변환해줍니다.
굳이 원본을 WebP로 바꿀 필요가 있을까?

사용자에게 제공되는 이미지는 Next.js 최적화를 거쳐 WebP나 AVIF로 변환되기 때문에, 사용자 경험 측면에서는 큰 차이가 없을 수 있습니다. 하지만 원본 파일 포맷에 따라 몇 가지 중요한 차이가 발생합니다.
1. 빌드 및 배포 시간
WebP는 PNG보다 파일 크기가 훨씬 작습니다. 프로젝트에 포함되는 원본 이미지 총 용량이 줄어들어 빌드 및 배포 시간이 단축됩니다. 이미지가 많은 프로젝트라면 차이가 더 두드러집니다.
2. 초기 최적화 효율
Next.js는 PNG처럼 용량이 큰 이미지를 WebP로 변환하는 데 더 많은 처리가 필요합니다. 원본이 이미 WebP라면 추가 포맷 변환 없이 크기 최적화(Resizing)에 집중할 수 있어 효율이 높아집니다.
결론적으로, 원본 이미지를 PNG 대신 WebP로 변환해 로컬에 저장하기로 했습니다.
🕶️ 이미지에 검정 배경이 뜬다?
WebP로 적용해보니 또 다른 문제가 발생했습니다. Safari와 모바일에서 이미지 배경이 검정색으로 보이는 문제였습니다. 🥹
- 크롬, 파이어폭스 → 정상
- Safari, 모바일 → 배경이 검정

처음엔 두 가지 원인을 생각했습니다.
- Safari 자체의 문제인가?
- WebP 변환을 잘못했나?
하지만 진짜 원인은 따로 있었습니다.
🔍 개발자 도구로 확인
이미지를 불러오는 방식을 확인하기 위해 개발자 도구를 열었습니다.

다른 사이트에서는 아래처럼 나왔습니다.
서비스 워커와 Accept 헤더가 문제의 실마리였습니다.
📬 Accept 헤더란?
브라우저는 Accept 헤더를 통해 어떤 포맷을 지원하는지 서버에 알려줍니다. Next.js <Image> 컴포넌트는 이 값을 보고 브라우저에 맞는 포맷으로 변환합니다.
🛠️ 서비스 워커란? (feat. MSW)
서비스 워커는 브라우저와 서버 사이에서 동작하는 프록시 같은 존재입니다. 브라우저가 서버로 요청을 보내면, 서비스 워커가 먼저 그 요청을 가로챕니다.
MSW는 서비스 워커 위에서 동작하는 API 요청 모킹(mocking) 라이브러리입니다. 실제 서버 없이도 API 통신을 흉내내서 백엔드 API가 완성되지 않은 상황에서도 프론트엔드 개발을 진행할 수 있습니다.
🐛 첫 번째 문제: 운영 서버에서도 MSW가 켜져 있었다
첫 번째 문제는 운영 서버에서도 MSW가 활성화되어 있었다는 점입니다.
NEXT_PUBLIC_API_MOCKING이 없거나 false일 때 모킹이 켜지지 않게 하려는 의도였습니다. 하지만 실제로는 문제가 발생했습니다.
번들링/컴파일 시점
NEXT_PUBLIC_API_MOCKING=false로 설정- Next.js 컴파일러가
process.env.NEXT_PUBLIC_API_MOCKING을 문자열"false"로 치환 - 번들된 JS 파일:
experimental__runtimeEnv: { NEXT_PUBLIC_API_MOCKING: "false" }
2번 과정은 Next.js GitHub 코드에서 확인할 수 있습니다.
런타임 시점
- env.ts 실행,
z.coerce.boolean()이"false"문자열을 받음 Boolean("false")→true- MSWProvider에서
env.NEXT_PUBLIC_API_MOCKING이true로 평가되어 MSW 활성화
실제 배포 사이트에서도 NEXT_PUBLIC_API_MOCKING=false로 설정되어 있었고, 위 과정을 거쳐 true로 변환됐을 것입니다.
위와 같이 수정하여 "true"일 때만 true, 그 외 모든 값은 false가 되도록 했습니다.
🧩 두 번째 문제: Next.js Image API까지 가로챘다
Next.js <Image> 컴포넌트의 최적화는 서버 사이드에서 실시간으로 이루어집니다. 정적 파일을 직접 서빙하는 것이 아니라 API 엔드포인트(/_next/image)를 통해 처리됩니다.
서비스 워커는 네트워크 요청을 인터셉트하는데, /_next/image API 요청도 가로채서 헤더를 변조합니다. 브라우저가 어떤 포맷을 선호하는지 서버가 알 수 없게 되고, Next.js가 WebP 지원을 감지하지 못해 기본 포맷인 JPEG로 변환합니다.
JPEG는 투명도를 지원하지 않으므로 WebP 이미지를 JPEG로 변환하면 배경이 검정색으로 보입니다.
요청 흐름:
-
브라우저:
GET /_next/image?url=/image.webp&w=300&q=75
Headers:Accept: image/webp,image/avif,image/*,*/*;q=0.8 -
MSW 인터셉트: 서비스 워커가 요청을 가로챔
Headers:Accept: */*(헤더 변조 발생) -
Next.js Image API: 변조된 헤더를 받음
→ WebP 지원을 감지하지 못함
→ JPEG로 변환 -
결과: WebP → JPEG 변환으로 투명도 손실
❓ 왜 하필 JPEG로 바뀔까?
Next.js Image 최적화 코드를 확인했습니다.
제 경우:
- 원본이 WebP
- Accept가 변조되어 WebP 지원을 감지 못함
- → JPEG로 강제 변환
🚫 서비스 워커에서 Next.js API 제외하기
/_next/image 엔드포인트를 MSW가 모킹하지 않도록 수정해야 했습니다.
기존 코드:
서비스 워커가 이미 요청을 가로챈 후에 호출되므로 헤더는 이미 변조된 상태입니다.
서비스 워커가 애초에 특정 경로만 가로채도록 수정했습니다:
수정 후 /_next/image 엔드포인트를 모킹하지 않아 헤더가 정상적으로 전달되고, WebP가 올바르게 내려와 검정 배경 문제가 해결됐습니다.

📝 마치며
API를 하나라도 연결한 상태였다면 원인을 더 빠르게 찾을 수 있었을 것 같습니다. 하지만 이미지 문제를 해결하면서 요청 헤더와 Next.js GitHub 코드를 살펴볼 수 있었던 좋은 기회였습니다.
같은 문제를 겪을 경우는 드물겠지만, 다음 내용을 알아두시면 좋을 것 같습니다.
- 각 이미지 포맷의 특성
- Next.js
<Image>최적화 동작 방식 - Next.js의 env 처리 방식 (env 변수를 문자열로 치환하므로 boolean 처리에 주의)
- 서비스 워커 스코프 설정
원인을 찾는 과정 자체가 큰 공부가 되었고, 같은 문제를 겪는 분께 도움이 되었으면 좋겠습니다 🫠