사람함께에이전트▾
사람 — 직접 정하고 책임지는 비즈니스 규칙과 흐름. 직접 읽어보세요.
함께 — 개념은 알아두고, 세부 규칙은 에이전트가 따릅니다.
에이전트 — 에이전트가 따르는 규칙과 레퍼런스. 필요할 때 찾아보세요.
일반▾
인터페이스▾
관측성▾
성능▾
네이티브▾
개발▾

이미지 최적화

폰으로 찍은 4MB 사진이 96px 아바타로 그려진다고 해 봅시다. 화면은 멀쩡해 보이지만, 방문자는 썸네일 하나를 보려고 4MB를 내려받습니다.
akanjs/ui의 Image는 이 문제를 세 단계로 풉니다:
  1. 컴포넌트가 후보마다 /_akan/image?url=…&w=640&q=75 같은 optimizer URL을 만듭니다.
  2. 서버가 원본을 한 번만 줄이고 다시 인코딩한 뒤, 결과를 디스크에 캐시합니다.
  3. 브라우저가 srcSet에서 화면에 맞는 후보를 골라 그것 하나만 내려받습니다.
이 페이지의 나머지는 그 캐시를 헛되게 만들지 않는 방법입니다.
해야 할 일
  • Image로 그립니다. UI에 보이는 이미지는 모두 이 컴포넌트를 씁니다.
  • 외부 호스트를 허용합니다. akan.config.ts의 images.remotePatterns에 적기 전까지 외부 이미지는 차단됩니다.
  • 크기 종류를 줄입니다. 몇 가지 크기를 반복해 써야 여러 요소가 캐시 파일 하나를 함께 씁니다.
이 페이지에서 쓰는 말
optimizer
이미지를 줄이고 다시 인코딩해 캐시하는 /_akan/image 엔드포인트입니다.
srcSet
<img>에 붙는 후보 URL 목록입니다. 브라우저는 맞는 것 하나만 내려받습니다.
sizes
뷰포트별로 이미지가 그려지는 폭입니다. 예: (min-width: 768px) 50vw, 100vw.
1x2x
고정 폭 하나에 대한 후보 두 개입니다. 일반 화면용과 고밀도(Retina) 화면용입니다.
640w
픽셀 폭이 붙은 후보입니다. 브라우저가 sizes를 보고 하나를 고릅니다.
TTL
내려받은 외부 이미지를 다시 받기 전까지 재사용하는 시간입니다.

Image 사용하기

file(File 관계 필드나 { url, imageSize } 형태의 객체) 또는 src를 넘기고, 그려질 폭을 함께 줍니다:
apps/myapp/lib/article/Article.Unit.tsx
  • 그려질 폭을 꼭 넘깁니다. width가 없으면 원본 폭인 file.imageSize를 쓰므로, 4000px 사진은 3840px로 요청됩니다.
  • priority는 첫 화면용입니다. 높은 우선순위로 바로 로드하고 SSR 중에 preload합니다. 나머지 이미지는 lazy로 로드됩니다.
  • 따로 준 quality는 허용 목록에 있어야 합니다. 설정의 qualities에 그 값을 넣지 않으면 optimizer가 400으로 답합니다.
어떤 srcSet이 나오나
넘긴 것srcSet어울리는 곳
width만1x/2x 두 개, 각각 허용된 폭으로 올림고정 크기: 아바타, 썸네일
sizes (width 유무 무관)허용된 15개 폭 전부를 w 후보로, width는 무시뷰포트에 따라 폭이 변하는 요소
width 없는 srcdeviceSizes 8개를 w 후보로화면 전체 폭 이미지
data:·blob: URL, .svg 경로없음, 원본 src를 그대로 씀인라인 미리보기, 아이콘
unoptimized없음, 원본 src를 그대로 씀다른 서비스가 이미 최적화한 이미지
  • sizes가 width보다 우선합니다. sizes가 있으면 width는 URL에 쓰이지 않으니, 폭이 변하는 요소에만 줍니다.
  • unoptimized는 SVG용이 아닙니다. data:, blob:, .svg는 알아서 optimizer를 건너뜁니다. 이 플래그는 다른 곳에서 이미 최적화한 이미지용입니다.
Props
fileFile | { url, imageSize } | null
보여줄 이미지입니다. width·height를 생략하면 imageSize로 채웁니다.
srcstring
직접 넘기는 URL이며 file.url보다 우선합니다. 둘 다 없으면 요청 없이 bg-muted 빈 상자를 그립니다.
widthnumber기본값 file.imageSize[0]
화면에 그려지는 폭(CSS 픽셀)입니다. 이 값으로 1x/2x 후보를 고릅니다.
heightnumber기본값 file.imageSize[1]
화면에 그려지는 높이(CSS 픽셀)입니다. 자리를 미리 잡아 레이아웃이 튀지 않게 합니다.
sizesstring
폭이 변하는 요소용으로 w 후보 전체를 내보내게 합니다.
qualitynumber기본값 75
인코딩 품질입니다. 75가 아닌 값은 qualities에 있어야 합니다.
priorityboolean기본값 false
우선순위를 높여 바로 로드하고 SSR 중에 preload합니다. preload도 같은 일을 합니다.
unoptimizedboolean기본값 false
optimizer를 건너뛰고 원본 src를 그립니다.
altstring기본값 "image"
대체 텍스트입니다. 기본값은 스크린 리더에 아무 정보도 주지 않으니 항상 직접 넘깁니다.

설정

optimizer 설정은 모두 akan.config.ts의 images 키 아래에 있습니다. 배열 값은 기본값과 합쳐지지 않고 통째로 바뀌어서, deviceSizes를 적으면 기본값 여덟 개가 모두 사라집니다.
크기, 포맷, 품질
deviceSizesnumber[]기본값 [640, 750, 828, 1080, 1200, 1920, 2048, 3840]
화면 폭 이미지용 width 목록입니다. imageSizes와 합친 값만 w로 받습니다.
imageSizesnumber[]기본값 [32, 48, 64, 96, 128, 256, 384]
고정 크기 요소용 width 목록이며 deviceSizes와 합쳐집니다.
formats("image/avif" | "image/webp")[]기본값 ["image/webp"]
선호 순서대로 적은 출력 포맷입니다. 요청의 Accept 헤더가 허용하는 것 중 앞에 적힌 포맷이 쓰입니다.
qualitiesnumber[]기본값 [75]
허용하는 q 값입니다. 1~100 사이 정수라도 목록에 없으면 400입니다.
허용할 원본
remotePatterns{ protocol?, hostname?, port?, pathname?, search? }[]기본값 []
절대 URL 허용 목록입니다. 비어 있으면 외부 이미지를 하나도 받지 않습니다.
localPatterns{ pathname?, search? }[]기본값 [{ pathname: "/**" }]
루트 상대 URL 허용 목록입니다. 기본값은 public/ 전체를 허용합니다.
dangerouslyAllowSVGboolean기본값 false
SVG를 optimizer에 그대로 통과시킵니다. 꺼져 있으면 SVG 입력은 400입니다.
외부 이미지 가져오기
minimumCacheTTLnumber기본값 14400
외부 이미지를 최소 몇 초 재사용할지입니다. production 응답 max-age의 하한으로도 쓰입니다.
maximumRedirectsnumber기본값 3
외부 이미지를 받을 때 따라가는 리다이렉트 수입니다. 매 홉도 remotePatterns에 맞아야 합니다.
fetchTimeoutMsnumber기본값 7000
외부 fetch의 홉마다 적용하는 타임아웃(ms)입니다.
maxRemoteBytesnumber기본값 26214400 (25MB)
받아들이는 외부 응답 본문의 최대 크기입니다. 넘으면 413입니다.
서버 부하
maxConcurrencynumber기본값 0
동시에 도는 인코딩 수입니다. 0이면 서버가 보는 CPU의 절반(최소 1)입니다.
인코딩은 파일 읽기, 해싱과 같은 worker pool을 씁니다. maxConcurrency를 올리면 이미지 요청이 몰릴 때 서버의 다른 작업까지 느려지므로, 기본값은 일부러 절반으로 묶어 둡니다.

포맷과 플랫폼

인코딩은 Bun.Image가 하고, 쓸 수 있는 코덱은 서버 OS에 따라 다릅니다. AVIF, HEIC, TIFF는 macOS와 Windows에만 있는 OS 코덱이 필요하므로, Linux 컨테이너는 AVIF를 내보내지 않습니다.
코덱
macOS · Windows
Linux
출력
image/webp
✓
✓
기본 출력 포맷입니다. 어디서나 됩니다.
image/avif
✓
Linux에서는 formats에서 빠지고, 그 요청은 webp를 받습니다.
입력 읽기
JPEG · PNG · GIF · WebP
✓
✓
모든 플랫폼에서 읽고 줄입니다.
AVIF · TIFF
✓
Linux에서는 손대지 않고 그대로 보냅니다.
✓지원지원 안 함
입력별로 일어나는 일
입력
↳ 결과
JPEG · PNG · 정적 GIF
크기를 줄인 뒤, 브라우저가 받는 formats 중 첫 포맷으로 바꿉니다.
WebP · AVIF
크기만 줄이고 포맷은 그대로 둡니다.
애니메이션 GIF · PNG · WebP
애니메이션이 살아 있도록 손대지 않고 그대로 보냅니다.
.ico · .icns · .bmp · .jxl · .heic
손대지 않고 그대로 보냅니다.
Linux의 AVIF · TIFF
읽을 코덱이 없어 그대로 보냅니다.
SVG
dangerouslyAllowSVG를 켜지 않으면 400이고, 켜면 그대로 보냅니다.
  • ["image/avif", "image/webp"]로 적어도 안전합니다. Linux 배포는 모두에게 webp를 주고, 개발자의 Mac에서는 둘 다 실제로 동작합니다.
  • 순서가 중요합니다. 브라우저 Accept 헤더가 허용하는 것 중 formats에 먼저 적힌 포맷이 쓰이므로 image/avif를 앞에 둡니다. 둘 다 받지 않는 브라우저는 원본 포맷 그대로 크기만 줄여 받습니다.
  • 인코딩이 실패해도 응답은 나갑니다. 원본 바이트를 보내고 캐시하지 않으므로, 다음 요청에서 다시 시도합니다.

remotePatterns

외부 이미지는 URL이 remotePatterns의 항목과 맞아야 받으며, 기본값은 빈 목록입니다. 외부 이미지 최적화가 400으로 실패하면 이 설정부터 확인하세요.
이미지를 가져올 호스트를 하나씩 추가합니다:
apps/myapp/akan.config.ts
필드비교 방식예시
protocol정확히 일치: http 또는 https"https"
hostnameglob, *는 점(.)도 넘어갑니다"*.example.com"
port정확히 일치하는 문자열, ""은 기본 포트"8443"
pathnameglob, *는 경로 한 단계, **는 여러 단계"/articles/**"
search앞의 ?까지 포함해 정확히 일치"?v=2"
  • 적지 않은 필드는 무엇이든 통과합니다. { hostname: "cdn.example.com" }만 적으면 그 호스트의 모든 경로를 http와 https 모두로 허용합니다.
  • *는 /에서 멈추고 **는 넘어갑니다. hostname에는 /가 없으므로 *.example.com은 a.b.example.com에도 맞습니다.
  • port와 search는 정확히 일치해야 합니다. 비표준 포트나 고정된 쿼리 하나는 요구할 수 있지만, 이미지마다 바뀌는 서명은 표현할 수 없습니다.
  • 리다이렉트도 검사합니다. 매 홉이 패턴에 맞아야 하므로, 목록에 없는 호스트로 리다이렉트하는 CDN은 거절됩니다.

외부 이미지 캐시

외부 이미지는 한 번 내려받은 뒤 TTL이 끝날 때까지 디스크에서 제공하므로, 캐시된 이미지는 원본 서버(origin)까지 가지 않습니다. TTL은 업스트림 max-age이고, minimumCacheTTL보다 짧아지지 않습니다.
원본원본을 다시 읽는 때서버가 수정본을 주는 때
외부, production 빌드TTL이 끝난 뒤TTL 이후
외부, akan start요청마다즉시
public/의 로컬 파일요청마다, mtime과 크기로 비교즉시
  • 원본이 그대로면 다시 인코딩하지 않습니다. 인코딩된 파일은 업스트림 ETag로 찾으므로, 같은 이미지를 다시 읽는 비용은 다운로드 한 번이고 인코딩은 없습니다.
  • production은 NODE_ENV=production으로 실행한 빌드 결과물입니다. akan start에서는 TTL을 건너뛰므로 업스트림 수정이 바로 보입니다.
  • 브라우저도 자기 사본을 따로 둡니다. production 응답에는 최소 minimumCacheTTL만큼의 max-age가 붙으므로, 방문자는 그 시간이 지날 때까지 예전 이미지를 볼 수 있습니다.
  • 캐시는 replica마다 따로 있습니다. 빌드 결과물 디렉터리에 쌓이므로 replica마다 따로 채우고, 배포할 때마다 빈 상태로 시작합니다.

캐시 적중

인코딩된 파일은 다섯 가지 값으로 만든 key로 저장됩니다. 페이지가 요청하는 width와 quality 종류가 많을수록, 캐시는 잘 재사용되지 않는 파일들로 잘게 쪼개집니다.
url
url 파라미터로 들어온 원본 경로나 URL입니다.
width
허용된 폭으로 올림된 w 파라미터입니다.
quality
q 파라미터입니다. 컴포넌트가 quality를 넘기지 않으면 75입니다.
출력 포맷
브라우저가 받는 formats 중 첫 포맷, 아니면 원본 포맷입니다.
원본 태그
외부 이미지는 업스트림 ETag(없으면 내용 해시), 로컬 이미지는 mtime과 크기입니다.
  • 카드 크기 몇 가지를 반복해 씁니다. 제각각인 width는 여러 허용 폭으로 흩어지고, 반복되는 몇 가지 크기는 같은 파일로 모입니다.
  • qualities는 꼭 필요할 때가 아니면 [75]로 둡니다. 값이 하나 늘 때마다 파일도 배로 늘고, 컴포넌트가 quality를 넘기지 않는 한 클라이언트는 75만 요청합니다.

MIT 라이선스 하에 배포되었습니다.

내 AI에 이 문서 연결하기

MCPhttps://akanjs.com/mcp
Copyright © 2026 Akan.js 모든 권리 보유.시스템 관리자bassman