그림으로 보는 Astro — 콘텐츠 웹 프레임워크는 어떻게 동작하는가
Astro의 핵심 개념(아일랜드 아키텍처, 빌드 타임 렌더링, 부분 하이드레이션, 산출물, 배포 단위, 정적 배포의 강점)을 여섯 장면의 그림과 해설로 풀어냅니다.
콘텐츠 중심 웹 프레임워크 Astro가 코드에서 화면까지 어떻게 동작하는지를, 여섯 장면의 그림과 해설로 천천히 따라가 봅니다. “빌드하면 결국 뭐가 나오는가?”, “정적 배포가 정말 강점인가?” 같은 질문에 답합니다.
들어가며
웹 프레임워크는 많습니다. 그중 Astro가 유독 사랑받는 이유는 단순합니다 — “읽기 위주의 콘텐츠 사이트” 를 만들 때, 가장 빠르고 가장 가벼운 결과물을 내놓기 때문입니다. 블로그, 문서, 마케팅 페이지, 포트폴리오처럼 내용을 보여주는 것이 핵심인 사이트라면 Astro는 거의 정답에 가깝습니다.
이 글은 여섯 장면(scene)으로 이루어집니다. 각 장면은 한 컷의 그림과, 그 그림이 말하는 내용을 풀어쓴 해설로 짝을 이룹니다.
Scene 1 — “Astro가 대체 뭐야?”

Astro는 콘텐츠 중심 웹사이트를 빠르게 만들기 위한 웹 프레임워크입니다. 이름처럼 “별(astro)“을 닮은 작은 로켓이 무거운 짐 없이 가볍게 떠오르는 모습을 상상하면 됩니다.
- 블로그 · 문서 · 마케팅 · 포트폴리오처럼 내용이 주인공인 사이트에 특히 강합니다.
- 가장 큰 특징 한 줄: 기본 JavaScript가 0kb — 즉, 필요 없는 곳엔 JS를 아예 보내지 않습니다.
- React, Vue, Svelte, Solid, Preact 같은 UI 프레임워크를 한 프로젝트 안에서 섞어 쓸 수 있습니다.
---
// 컴포넌트 스크립트 (빌드 시 서버에서 실행됨)
const title = "안녕하세요";
---
<h1>{title}</h1>
.astro 파일은 위처럼 --- 사이에 JS/TS를 쓰고, 아래에 HTML을 작성하는 자체 컴포넌트 형식을 가집니다.
Scene 2 — 정적인 바다, 그리고 섬(Island)

Astro를 이해하는 열쇠는 아일랜드 아키텍처(Islands Architecture) 입니다.
정적인 HTML 바다 위에, 상호작용이 필요한 부분만 “섬” 처럼 떠 있게 만드는 방식.
한 페이지를 떠올려 보세요.
- 정적인 부분 (90%): 제목, 본문, 헤더, 푸터, 이미지 → 한 번 그려지면 바뀔 일이 없습니다.
- 동적인 부분 (10%): 검색창, 좋아요 버튼, 댓글, 다크모드 토글 → 사용자와 상호작용합니다.
기존 SPA(React, Vue 등)는 이 전부를 JavaScript로 만듭니다. 그래서 바뀌지도 않는 본문까지 JS로 다시 그리느라 무거운 번들을 통째로 내려받습니다.
아일랜드 아키텍처는 묻습니다 — “바뀌지도 않는 본문에 왜 JS가 필요하지?”
┌─────────────────────────────────────────┐
│ Header (정적 HTML) │
├─────────────────────────────────────────┤
│ ╭─────────────╮ │
│ Article 본문 │ 🏝️ 검색창 │ │ ← 섬 (JS 동작)
│ (정적 HTML, JS 없음) │ (인터랙티브) │ │
│ ╰─────────────╯ │
│ ╭──────────────╮ │
│ │ 🏝️ 좋아요버튼 │ │ ← 섬 (JS 동작)
│ ╰──────────────╯ │
├─────────────────────────────────────────┤
│ Footer (정적 HTML) │
└─────────────────────────────────────────┘
바다 = 정적 HTML (JS 0kb) · 🏝️ 섬 = 필요한 곳만 JS
두 가지 성질이 핵심입니다.
- 각 섬은 독립적이다. 검색창 섬과 좋아요 버튼 섬은 서로 모릅니다. 하나가 에러나도 다른 섬은 멀쩡합니다.
- 각 섬은 개별적으로 살아난다. 섬마다 언제 JS로 깨어날지를 따로 정할 수 있습니다 (다음 장면).
Scene 3 — 빌드할 때 모든 일이 끝난다

.astro 파일은 두 부분으로 나뉩니다.
---
// 🟦 [프론트매터] — 빌드 시 서버(Node)에서 "한 번만" 실행됨.
// 브라우저로는 절대 전송되지 않음.
const posts = await fetch('https://api.example.com/posts').then(r => r.json());
---
<!-- 🟩 [템플릿] — 실제 HTML로 변환되는 부분 -->
<ul>
{posts.map(post => <li>{post.title}</li>)}
</ul>
가장 중요한 포인트:
---사이의 코드는 빌드할 때, 서버에서만 실행됩니다.- 그래서 API 키나 fetch한 원본 데이터가 브라우저로 새어나가지 않습니다.
- 최종 결과는 데이터가 이미 박혀 있는 순수 HTML입니다.
npm run build를 하면 Astro는:
src/pages/안의 각 파일을 하나의 URL(라우트)로 매핑하고src/pages/index.astro → / src/pages/blog/[slug].astro → /blog/어떤-글 (동적 라우트)- 각 페이지의 프론트매터를 실행한 뒤
- 템플릿을 완성된 HTML 파일로 그려서
dist/폴더에 떨궈둡니다.
이것이 SSG(Static Site Generation), Astro의 기본 모드입니다. 무거운 일은 빌드 때 미리 다 해두고, 브라우저엔 결과물(HTML)만 보냅니다.
Scene 4 — 섬이 깨어나는 순간 (부분 하이드레이션)

인터랙티브 컴포넌트는 기본적으로 HTML로만 그려집니다. 버튼은 보이지만 클릭은 안 됩니다 — JS가 없으니까요.
여기에 client:* 지시어를 붙이면, Astro는 그 컴포넌트만 따로 JS 조각으로 떼어내 브라우저에서 되살립니다(hydrate).
---
import Counter from '../components/Counter.jsx'; // React 컴포넌트
---
<Counter /> <!-- ① JS 없는 정적 HTML -->
<Counter client:load /> <!-- ② JS 붙여서 동작 -->
②번의 동작 순서:
1. 브라우저가 HTML 받음 → Counter가 즉시 화면에 보임 (정적)
2. 백그라운드에서 Counter용 작은 JS 청크 다운로드
3. 그 JS가 해당 DOM 영역에만 React를 붙임 → 이제 클릭 동작
언제 깨어날지를 섬마다 지정합니다.
| 지시어 | 동작 시점 | 용도 |
|---|---|---|
client:load | 페이지 로드 즉시 | 당장 필요한 UI (검색창) |
client:idle | 브라우저가 한가할 때 | 급하지 않은 UI |
client:visible | 화면에 보일 때 | 하단 댓글, 캐러셀 |
client:media={…} | 특정 화면 크기에서만 | 모바일 전용 메뉴 |
client:only="react" | 서버 렌더 없이 클라이언트만 | SSR 불가 컴포넌트 |
client:visible을 쓰면, 사용자가 스크롤해서 그 영역이 보이기 전엔 JS를 다운로드조차 하지 않습니다. 이것이 성능의 비밀입니다.
Scene 5 — 빌드하면 결국 무엇이 나오는가

질문: “빌드하면 결국 HTML이 나오는 거야?” → 네, 맞습니다.
정적 모드(output: 'static', 기본)에서 dist/는 이렇게 생깁니다.
dist/
├── index.html ← / 페이지 (완성된 HTML)
├── about/index.html ← /about
├── blog/first-post/index.html
├── _astro/ ← 번들된 에셋 (해시 붙은 파일명)
│ ├── Counter.a1b2c3.js ← 섬(인터랙티브)의 JS 조각
│ └── index.x7y8z9.css ← 번들된 CSS
└── favicon.svg
*.html— 빌드 때 미리 렌더링된 완성형 HTML. 데이터가 이미 박혀 있습니다._astro/*.js—client:*가 붙은 섬들의 JS만 쪼개진 조각. 섬이 없으면 JS는 0개입니다.- 브라우저는 Astro가 뭔지 전혀 모릅니다. 그냥 평범한
.html을 받습니다.
그렇다면 배포 단위는? 모드가 곧 배포 단위를 결정합니다.
output | 배포 단위 | 배포 방식 |
|---|---|---|
static (기본) | 정적 파일 뭉치 | 파일을 올리기만 하면 끝 (GitHub Pages, Netlify, S3, Cloudflare Pages…) |
server | 서버 프로세스 | node ./dist/server/entry.mjs 처럼 실행해야 함 |
hybrid | 둘 다 | 페이지별로 정적/동적 선택 |
여기서 헷갈리기 쉬운 지점 하나 — “어댑터”가 배포 단위의 모양을 바꿉니다.
server/hybrid라도 어떤 어댑터를 쓰느냐에 따라 달라집니다.
| 어댑터 | 실제 배포 단위 |
|---|---|
@astrojs/node | 내가 직접 띄우는 Node 프로세스 (Docker 등) |
@astrojs/vercel | Vercel 서버리스 함수 (상시 서버 없음) |
@astrojs/cloudflare | Cloudflare Worker |
즉 “서버 모드”라도 서버리스로 배포하면, 내가 관리하는 상시 서버는 없고 요청이 올 때만 함수가 실행됩니다.
Scene 6 — 그래서, 정적 배포가 강점일까?

결론부터: 정적 배포는 그 자체로 만능 우위가 아니라, 사이트 성격이 정적에 맞을 때 비로소 강점이 됩니다.
정적 배포가 강점인 이유 ✅
| 강점 | 왜 |
|---|---|
| 빠름 | 서버 렌더링 없이 CDN이 완성된 HTML을 바로 던져줌 (전 세계 엣지에서 응답) |
| 싸다/무료 | 죽을 서버가 없으니 서버 비용 0 |
| 안 죽는다 | 실행 프로세스가 없음 → 트래픽 폭주에도 다운 X |
| 안전 | 서버·DB가 없으니 공격 표면이 거의 없음 |
| 운영이 편함 | 스케일링·패치할 서버가 없음. 그냥 파일 |
한계 ❌
- 실시간성 없음 — 데이터가 바뀌면 다시 빌드해야 반영됩니다.
- 개인화 어려움 — “로그인한 OO님 환영합니다” 같은 사용자별 화면은 서버 없이 불가합니다.
- 서버 로직 부재 — 결제·인증·DB 쓰기는 정적만으론 못 합니다.
그래서 현대적 방식은 “정적을 기본으로 깔고, 동적 1%만 필요한 만큼 외부/함수로 떼어내기” 입니다.
정적 HTML (메인) + 동적 1%는 외부에 위임
├─ 댓글 → giscus / Disqus
├─ 검색 → 클라이언트 JS or Algolia
├─ 폼 전송 → 서버리스 함수 1개
└─ 인증 → 외부 인증 서비스
그리고 이것이 바로 Astro가 노리는 자리입니다.
정적의 강점(빠름·쌈·안전)은 누리되, 동적이 필요해지면 같은 프레임워크 안에서
server/hybrid로 자연스럽게 확장.
Hugo·Jekyll은 정적만 됩니다. Next.js는 동적에 무게를 둡니다. Astro는 “정적으로 시작해서 필요한 만큼만 동적으로” 를 한 도구로 풀어줍니다. 그래서 미리 정하지 못한 미래를 위한 여지를 남겨줍니다.
한 장으로 정리
- Astro란? 콘텐츠 중심 웹 프레임워크. 기본 JS 0kb, UI 프레임워크 혼용 가능.
- 아일랜드 아키텍처 — 정적 HTML 바다 + 인터랙티브 섬. 섬만 JS로 동작.
- 동작 방식 — 빌드 때 서버에서 컴포넌트를 HTML로 렌더.
---코드는 브라우저로 안 감. - 부분 하이드레이션 —
client:*로 표시된 섬만, 지정한 타이밍에 깨어남. - 산출물 — 기본은 완성된 정적 HTML(+CSS+섬 JS 조각). 섬이 없으면 JS 0개.
- 배포 단위 — 기본은 “파일을 올림”.
server/hybrid면 서버 또는 서버리스 함수. - 정적의 강점 — 빠름·쌈·안 죽음·안전. 블로그엔 정적이 정답. 동적은 필요한 만큼만 확장.
블로그 같은 사이트라면 — 빌드 = 정적 HTML 생성이 맞고, 그게 곧 강점입니다. Astro는 “개발할 땐 컴포넌트로 편하게, 결과물은 빠른 정적 HTML로”가 핵심 철학입니다.