Astro Islands Architect은(는) 무엇을 하나요?
Astro는 "기본적으로 HTML을 제공하고, JS가 필요한 부분만 하이드레이션"합니다. 마케팅, 문서, 콘텐츠 중심 사이트의 경우 React/Next.js보다 훨씬 작은 번들을 생성합니다. 핵심 과제는 어떤 컴포넌트를 정적으로 렌더링하고 어떤 것을 아일랜드로 만들지 선택하는 것입니다.
사용 시기
- 마케팅 사이트, 블로그, 문서 포털 — 콘텐츠가 핵심 제품일 때
- 클라이언트 JS가 과도하게 많은 Next.js 마케팅 사이트에서 마이그레이션할 때
- 멀티-프레임워크 — React 컴포넌트 라이브러리를 Vue 또는 Svelte 컴포넌트와 함께 사용할 때
- MDX + 타입-세이프 프론트매터로 콘텐츠를 많이 작성할 때
- SPA 없이 페이지 간 뷰 트랜지션을 사용할 때
핵심 기능
페이지 구조
---
// src/pages/blog/[slug].astro — 프론트매터는 빌드/SSR 시점에 실행됩니다
import { getCollection, getEntry } from 'astro:content';
import Layout from '../../layouts/Default.astro';
import Newsletter from '../../components/Newsletter.tsx';
export async function getStaticPaths() {
const posts = await getCollection('blog');
return posts.map((p) => ({ params: { slug: p.slug }, props: { post: p } }));
}
const { post } = Astro.props;
const { Content } = await post.render();
---
<Layout title={post.data.title}>
<h1>{post.data.title}</h1>
<article><Content /></article>
{/* 아일랜드 — 이 컴포넌트만 JS를 전송합니다 */}
<Newsletter client:visible />
</Layout>
--- fence 내의 코드는 서버 코드입니다. <...> 안에서는 기본적으로 정적 HTML이 생성됩니다. client:* 지시어가 있는 컴포넌트가 아일랜드가 됩니다.
클라이언트 지시어 — 올바른 선택
| 지시어 | 하이드레이션 시점 | 사용 예 |
|---|---|---|
client:load | 페이지 로드 시 | 화면 상단의 인터랙티브 요소 (장바구니, 검색) |
client:idle | 브라우저가 유휴 상태일 때 | 중요하지 않은 인터랙티브 요소 (채팅 위젯) |
client:visible | 뷰포트에 스크롤되어 보일 때 | 화면 아래쪽 요소 (뉴스레터 가입, 댓글) |
client:media="(min-width: 768px)" | 미디어 쿼리 일치 시 | 데스크톱 전용 위젯 |
client:only="react" | SSR 생략; 클라이언트 전용 렌더링 | 마운트 시 브라우저 API가 필요한 컴포넌트 |
client:idle은 대부분의 중요하지 않은 아일랜드에 적합한 기본값입니다. client:load는 무거운 도구이므로 꼭 필요한 경우에만 사용하세요.
콘텐츠 컬렉션
// src/content/config.ts
import { defineCollection, z } from 'astro:content';
const blog = defineCollection({
type: 'content', // markdown/MDX 파일
schema: z.object({
title: z.string().max(120),
description: z.string().max(160),
date: z.coerce.date(),
tags: z.array(z.string()).default([]),
draft: z.boolean().default(false),
cover: z.object({ src: z.string(), alt: z.string() }).optional(),
}),
});
export const collections = { blog };
이제 getCollection('blog')는 완전히 타입이 지정됩니다. 프론트매터 오류는 프로덕션에 도달하지 못하고 빌드 시점에 실패합니다.
const posts = (await getCollection('blog'))
.filter((p) => !p.data.draft)
.sort((a, b) => +b.data.date - +a.data.date);
멀티-프레임워크
npx astro add react vue svelte
---
import ReactSearch from '../components/Search.tsx';
import VueChart from '../components/Chart.vue';
import SvelteToggle from '../components/Toggle.svelte';
---
<ReactSearch client:idle />
<VueChart client:visible />
<SvelteToggle client:load />
각 프레임워크의 런타임은 별도로 번들링되며, 해당 프레임워크를 사용하는 페이지에서만 로드됩니다. 대부분 정적인 문서 사이트의 경우, 대부분의 페이지에 React JS를 전혀 전송하지 않을 수도 있습니다.
서버 아일랜드 (Astro 4+)
<Layout>
<Header />
<PersonalizedRecommendations server:defer>
<p slot="fallback">추천 로딩 중...</p>
</PersonalizedRecommendations>
<Footer />
</Layout>
server:defer는 페이지 나머지 부분을 즉시 렌더링하고, 지연된 컴포넌트의 HTML은 별도 요청을 통해 스트리밍합니다. 페이지 전체를 SSR로 강제하지 않으면서 개인화된 콘텐츠를 제공할 때 유용합니다.
이미지 최적화
---
import { Image } from 'astro:assets';
import cover from '../assets/cover.jpg';
---
<Image src={cover} alt="커버 사진" widths={[400, 800, 1200]} sizes="(max-width: 768px) 400px, 1200px" />
Astro는 srcset을 생성하고, Sharp를 사용해 크기를 조정하며, 필요한 크기만 전송합니다. 전체 최적화를 위해 이미지는 src/assets/ 아래에 두세요. public/은 처리되지 않습니다.
뷰 트랜지션
---
import { ViewTransitions } from 'astro:transitions';
---
<html>
<head>
<ViewTransitions />
</head>
...
</html>
<a href="/about" transition:name="hero">소개</a>
SPA 없이 페이지 간 전환. 브라우저가 대부분의 작업을 처리하고, Astro는 요소 매칭을 조정합니다.
MDX와 컴포넌트
---
title: My post
date: 2026-04-30
---
import Callout from '../../components/Callout.astro';
# {frontmatter.title}
<Callout type="warn">
이것은 서버 측에서 렌더링된 경고입니다. JS가 필요 없습니다.
</Callout>
MDX에서 임포트된 컴포넌트는 아일랜드가 아닌 경우 빌드/SSR 시점에 실행됩니다.
하이브리드 SSR
// astro.config.mjs
import { defineConfig } from 'astro/config';
import cloudflare from '@astrojs/cloudflare';
export default defineConfig({
output: 'hybrid',
adapter: cloudflare(),
});
---
// src/pages/api/search.ts — 엣지에서 실행
import { search } from '../../db';
import type { APIRoute } from 'astro';
export const prerender = false;
export const GET: APIRoute = async ({ request }) => {
const url = new URL(request.url);
const q = url.searchParams.get('q') ?? '';
const results = await search(q);
return new Response(JSON.stringify(results), { headers: { 'content-type': 'application/json' } });
};
prerender = false는 해당 경로를 SSR로 전환합니다. 사이트의 나머지는 정적 상태를 유지합니다.
안티패턴
모든 곳에서 client:load 사용
증상: Lighthouse JS 번들이 비대해지고 Astro의 하이드레이션 이점이 사라짐.
진단: 엔지니어들이 가장 익숙한 client:load를 기본으로 사용함.
해결: 기본값을 client:idle 또는 client:visible로 설정하고, client:load는 화면 상단의 인터랙티브 요소에만 사용.
클라이언트 컴포넌트에서 데이터 패칭
증상: 모든 페이지에 로딩 스피너가 나타나고 SEO에 악영향.
진단: 데이터 패칭이 Astro 프론트매터 대신 React 컴포넌트로 이동함.
해결: --- fence 내에서 데이터를 패칭하고, 서버 측에서 렌더링한 후 데이터를 props로 아일랜드에 전달.
타입 없는 프론트매터
증상: 빌드는 성공하지만 프로덕션에서 제목이 undefined로 표시됨.
진단: 콘텐츠 컬렉션 스키마가 없어 프론트매터 오타가 발견되지 않음.
해결: Zod 스키마와 함께 defineCollection을 사용하세요. 프론트매터 오류는 빌드를 실패시킵니다.
이미지에 public/과 src/assets/ 혼용
증상: 일부 이미지는 최적화되지만 다른 이미지는 원본 크기로 전송됨.
진단: public/은 그대로 제공되며, src/assets/만 Sharp를 통해 처리됨.
해결: 이미지를 src/assets/로 옮기고 <Image> 컴포넌트를 사용하세요. public/은 OG 이미지와 파비콘에만 사용.
SPA에 Astro 사용 시도
증상: 하이드레이션과의 끊임없는 싸움, 전체 페이지가 인터랙티브해질 때까지 아일랜드가 계속 증가. 진단: 잘못된 프레임워크 선택. Astro는 콘텐츠에 탁월하지만 SPA에는 Next/Remix가 필요함. 해결: 대부분의 페이지가 인터랙티브한 경우 Next/Remix를 사용하세요. 마케팅 + 문서에는 Astro를 사용.
client:only 남용
증상: 페이지 로드 시마다 레이아웃 시프트(CLS) 발생, 콘텐츠가 나타나기 전 "깜빡임" 현상.
진단: SSR을 건너뛰면 브라우저가 JS가 실행될 때까지 빈 자리 표시자를 보게 됨.
해결: 컴포넌트가 정말로 서버 측에서 렌더링될 수 없는 경우에만 client:only를 사용하세요. 최종 크기에 근접한 자리 표시자를 제공하세요.
품질 관문
- 기본 클라이언트 지시어는
client:idle또는client:visible이고,client:load는 사용 사례별로 정당화됨. - 모든 콘텐츠는
defineCollection을 통해 타입이 지정된 스키마를 가짐. - 이미지는
src/assets/아래에 있고<Image>컴포넌트를 사용함. - 멀티-프레임워크 통합(
@astrojs/react등)은 실제로 존재하는 컴포넌트에만 추가됨. - 가장 느린 대표 페이지에서 LCP가 2초 미만.
- 페이지별 첫 로드 JS 예산이 설정되었고, CI가 회귀를 감지하면 실패.
- MDX는 정적 부분에 Astro 컴포넌트를 사용하고, 아일랜드는 인터랙티브가 필요한 곳에만 사용.
- 뷰 트랜지션은 주요 탐색 경로에서 테스트됨.
사용하지 말아야 할 경우
- 완전한 SPA — Next.js, Remix, TanStack Start가 더 적합합니다.
- Astro 플러그인/통합 개발 — 별도 스킬 (전용 스킬 아직 없음).
- Vite 측 빌드 튜닝 — Astro는 내부적으로 Vite를 사용합니다. 청크 크기, HMR, 플러그인 수명주기 문제는 →
vite-build-optimizer참고. - 무거운 클라이언트 상태 앱 (대시보드, 실시간 도구) — 아일랜드가 도움이 되기에는 JS가 너무 많습니다.
- 네이티브 모바일 — Astro는 웹 전용입니다.