Astro 이미지 최적화: Image와 Picture 컴포넌트 활용법
Lighthouse로 사이트 성능을 측정해 보면 이미지 때문에 점수가 깎이는 경우가 정말 많은데요. HTTP Archive에 따르면 평균 웹 페이지에서 이미지가 전체 바이트의 절반 가까이를 차지한다고 합니다. 포맷 변환, 리사이징, lazy loading, 반응형 srcset까지 직접 챙기려면 꽤 번거롭죠.
Astro는 이런 작업을 프레임워크 차원에서 해결해 줍니다. 별도의 플러그인 없이 <Image />와 <Picture /> 컴포넌트만 쓰면 빌드 시 알아서 처리하거든요. 사실 이 블로그도 Astro로 만들었는데, 이미지 최적화 설정을 한 번 해두니까 글 쓸 때 이미지는 신경 쓸 일이 거의 없어졌습니다.
이 글에서는 Astro의 이미지 최적화 기능을 하나씩 살펴보겠습니다.
이미지 저장 위치부터
Astro에서 이미지를 다룰 때 가장 먼저 알아야 할 것은 이미지를 어디에 두느냐에 따라 최적화 여부가 갈린다는 점입니다.
src/ 디렉토리에 넣은 이미지는 Astro의 이미지 파이프라인을 거칩니다. 빌드 시 Sharp 라이브러리가 WebP 변환, 리사이징, width/height 속성 삽입 등을 자동으로 처리해 줍니다. 반면 public/ 디렉토리에 넣은 이미지는 아무런 처리 없이 그대로 복사됩니다.
src/
└── assets/
└── images/ # ✅ 최적화 대상
├── hero.png
└── diagram.png
public/
└── images/
└── og-default.png # ❌ 최적화 안 됨 (절대 URL 필요한 경우만)
public/은 파비콘이나 사이트 기본 OG 이미지처럼 절대 URL이 필요한 정적 자산에만 쓰는 게 좋습니다. 블로그 포스트에 들어가는 이미지라면 src/assets/images/에 두면 됩니다.
Image 컴포넌트
<Image />는 Astro에서 이미지를 최적화하는 가장 기본이 되는 방법입니다. astro:assets에서 import해서 사용합니다.
---
import { Image } from "astro:assets";
import heroImage from "../assets/images/hero.png";
---
<Image src={heroImage} alt="히어로 이미지" />
이렇게만 해도 빌드 시 원본 PNG가 WebP로 변환되고 width와 height 속성이 자동으로 들어갑니다. loading="lazy"와 decoding="async"도 알아서 붙어요. width/height가 있으면 브라우저가 이미지 영역을 미리 확보하니까 레이아웃이 밀리는 CLS(Cumulative Layout Shift) 문제가 생기지 않죠. lazy loading 덕에 초기 로딩도 빨라집니다.
로컬 이미지는 import하면 크기를 자동으로 알아내지만 외부 이미지는 사정이 다릅니다. 직접 크기를 지정하거나 inferSize 속성을 써야 해요.
<!-- 외부 이미지: 크기 직접 지정 -->
<Image
src="https://example.com/photo.jpg"
alt="외부 사진"
width={800}
height={600}
/>
<!-- 또는 inferSize로 자동 감지 -->
<Image
src="https://example.com/photo.jpg"
alt="외부 사진"
inferSize
/>
inferSize를 쓰려면 astro.config.mjs에서 해당 도메인을 허용해 줘야 합니다.
export default defineConfig({
image: {
domains: ["example.com"],
},
});
포맷이나 품질을 직접 지정할 수도 있습니다. format 속성으로 출력 포맷을, quality 속성으로 압축률을 조절합니다.
<Image src={heroImage} alt="히어로" format="avif" quality={80} />
AVIF는 WebP보다 압축률이 높지만 인코딩에 시간이 더 걸립니다. 이미지가 몇 장 안 되면 AVIF가 좋지만 수백 장을 처리해야 한다면 빌드 시간이 눈에 띄게 늘어나요. 그런 경우에는 WebP 쪽이 현실적입니다.
Picture 컴포넌트
하나의 포맷만으로는 모든 브라우저를 커버하기 어려울 때가 있습니다. AVIF를 아직 지원하지 않는 브라우저도 있거든요. <Picture />는 HTML의 <picture> 요소를 활용해 여러 포맷을 동시에 제공하는 컴포넌트입니다.
---
import { Picture } from "astro:assets";
import heroImage from "../assets/images/hero.png";
---
<Picture src={heroImage} formats={["avif", "webp"]} alt="히어로 이미지" />
이 코드가 빌드되면 아래와 같은 HTML이 생성됩니다.
<picture>
<source srcset="/_astro/hero.avif" type="image/avif" />
<source srcset="/_astro/hero.webp" type="image/webp" />
<img src="/_astro/hero.png" alt="히어로 이미지"
width="1200" height="800" loading="lazy" decoding="async" />
</picture>
브라우저는 위에서 아래로 <source>를 확인하면서 자기가 지원하는 첫 번째 포맷을 선택합니다. AVIF를 지원하면 AVIF를, 아니면 WebP를, 둘 다 안 되면 원본 PNG로 떨어지는 식이죠.
<Image />와 <Picture /> 중 뭘 써야 할지 고민된다면 간단합니다. WebP 하나로 충분하다면 <Image />, AVIF의 압축률을 살리면서 하위 호환성도 챙기고 싶다면 <Picture />를 쓰면 됩니다.
마크다운에서의 이미지
블로그처럼 마크다운으로 글을 쓰는 환경에서는 매번 컴포넌트를 import하기 번거로운데요. 다행히 마크다운에서도 상대 경로로 이미지를 참조하면 동일한 최적화가 적용됩니다.

별도 컴포넌트를 import하지 않아도 WebP 변환, 크기 추론, lazy loading이 모두 적용됩니다. .astro 파일에서 <Image />를 쓸 때와 결과가 같으니 마크다운만으로 글을 쓰는 데 불편함이 없죠.
콘텐츠 컬렉션의 커버 이미지도 마찬가지입니다. 스키마에서 image() 헬퍼를 쓰면 빌드 시 경로 유효성까지 검증해 줍니다.
import { defineCollection, z } from "astro:content";
const posts = defineCollection({
type: "content",
schema: ({ image }) =>
z.object({
title: z.string(),
cover: image().optional(),
}),
});
프론트매터에서는 상대 경로로 지정합니다.
---
title: 나의 포스트
cover: ../../assets/images/cover.png
---
반응형 이미지
모바일에서 데스크톱용 2000px짜리 이미지를 내려받는 건 데이터 낭비입니다. Astro 5.10부터 안정화된 반응형 이미지 기능을 쓰면 기기 화면에 맞는 크기의 이미지를 자동으로 생성해 줍니다.
astro.config.mjs에서 layout을 설정하면 모든 이미지에 전역으로 적용됩니다.
export default defineConfig({
image: {
layout: "constrained",
responsiveStyles: true,
},
});
layout에는 세 가지 옵션이 있습니다.
constrained— 원본 크기를 넘지 않는 범위에서 컨테이너에 맞게 축소됩니다. 블로그 본문 이미지처럼 최대 폭이 정해진 경우에 적합합니다.full-width— 항상 컨테이너 너비에 맞게 늘어납니다. 히어로 배너처럼 화면 전체를 채우는 이미지에 적합합니다.fixed— 지정한 크기 그대로 표시됩니다. 아이콘이나 아바타처럼 크기가 변하면 안 되는 경우에 적합합니다.
전역 설정을 해두면 마크다운의 ![]() 문법으로 삽입한 이미지에도 반응형이 적용됩니다. 개별 이미지에서 layout 속성을 지정해 전역 설정을 덮어쓸 수도 있습니다.
<Image src={heroImage} alt="히어로" layout="full-width" />
이 설정이 적용되면 Astro가 여러 크기의 이미지를 자동 생성하고 srcset 속성을 만들어 브라우저가 화면에 맞는 이미지를 선택하게 합니다. 모바일에서 데스크톱용 고해상도 이미지를 내려받는 낭비를 막을 수 있는 거죠.
LCP 이미지 우선 로딩
여기서 한 가지 주의할 점이 있습니다. loading="lazy"가 기본값이다 보니 페이지 최상단의 히어로 이미지도 지연 로딩되거든요. 사용자가 가장 먼저 보는 이미지가 늦게 뜨면 Largest Contentful Paint(LCP) 점수가 떨어질 수밖에 없습니다. 이럴 때 priority 속성을 붙여주면 됩니다.
<Image src={heroImage} alt="히어로 이미지" priority />
priority를 붙이면 loading="eager", decoding="sync", fetchpriority="high"가 한꺼번에 적용됩니다. 브라우저가 이 이미지를 최우선으로 가져오게 되는 거죠. 다만 여러 이미지에 남발하면 오히려 역효과니까 페이지당 하나, 스크롤 없이 보이는 가장 큰 이미지에만 쓰는 게 좋습니다.
getImage()로 프로그래밍 방식 사용
지금까지는 컴포넌트 안에서 이미지를 다루는 방법을 봤는데, OG 이미지 생성이나 API 엔드포인트처럼 컴포넌트 밖에서 이미지를 처리해야 할 때도 있습니다. 이때 getImage() 함수를 씁니다.
---
import { getImage } from "astro:assets";
import photo from "../assets/images/photo.png";
const webp = await getImage({ src: photo, format: "webp", width: 800 });
const avif = await getImage({ src: photo, format: "avif", width: 800 });
---
<picture>
<source srcset={avif.src} type="image/avif" />
<source srcset={webp.src} type="image/webp" />
<img src={webp.src} {...webp.attributes} alt="사진" />
</picture>
getImage()는 <Image />와 동일한 옵션을 받지만 HTML을 렌더링하지 않고 최적화된 이미지의 경로와 속성 정보만 돌려줍니다. 반환값을 보면 src에 최적화된 이미지 경로가, attributes에 width나 height 같은 HTML 속성이 담겨 있어요. 직접 <img> 태그를 조립할 때 유용합니다. 다만 서버 사이드에서만 동작하므로 클라이언트 코드에서는 쓸 수 없다는 점은 주의하세요.
빌드 과정에서 일어나는 일
Astro가 이미지를 어떻게 처리하는지 간략히 정리해 보겠습니다.
빌드할 때는 Sharp 라이브러리가 이미지를 변환해서 dist/_astro/에 최적화된 파일을 만듭니다. 개발 모드에서는 좀 다른데, /_image 엔드포인트가 요청이 들어올 때마다 그때그때 변환해요. 한 번 처리된 이미지는 node_modules/.astro/에 캐시되기 때문에 이미지를 수정하지 않았다면 다음 빌드가 훨씬 빠릅니다.
포맷별 압축 효율도 알아두면 좋습니다. WebP는 동일 화질 기준으로 JPEG보다 25~35% 정도 작고, AVIF는 50% 가까이 줄일 수 있어요. 대신 AVIF 인코딩은 WebP보다 오래 걸리기 때문에 이미지가 많은 사이트에서는 빌드 시간과의 트레이드오프를 따져봐야 합니다.
마치며
다른 프레임워크에서는 이미지 최적화를 위해 별도 플러그인을 설치하고 빌드 파이프라인까지 손봐야 하는 경우가 많은데요. Astro는 이미지를 src/assets/에 넣고 컴포넌트를 쓰기만 하면 나머지를 알아서 해준다는 점이 가장 마음에 드는 부분입니다.
실제로 이 블로그에 반응형 이미지 설정을 적용한 뒤 모바일 Lighthouse 점수가 눈에 띄게 올랐습니다. 전역 layout: "constrained" 하나만 켜두면 마크다운으로 쓴 이미지까지 전부 반응형이 되니까요.
Astro의 전체적인 개요를 아직 안 읽어보셨다면 프레임워크 전반을 먼저 훑어보시는 것도 좋고, 이미지의 가로세로 비율을 CSS로 제어하는 방법이 궁금하시다면 CSS의 object-fit 속성도 참고해 보세요. 더 자세한 내용은 Astro 공식 이미지 가이드에서 확인할 수 있습니다.
This work is licensed under
CC BY 4.0