✍️ 필사 모드: Astro 5 심층 해부: Server Islands · Content Layer · View Transitions로 다시 그리는 '콘텐츠 사이트의 표준'
한국어"정적이라는 단어를 다시 정의하자. 캐시할 수 있는 모든 것은 캐시하고, 진짜로 사람마다 달라야 하는 부분만 서버에서 늦게 그린다." — Astro 5 Server Islands의 디자인 한 줄 요약
프롤로그 — '단순한 정적 사이트 생성기'의 시대는 끝났다
2022년의 Astro는 마크다운 블로그용 정적 사이트 생성기였습니다. 2024년 12월 3일에 공개된 Astro 5는, 같은 이름을 달고 있지만 다른 물건입니다. 콘텐츠 중심 사이트의 사실상 표준 이라고 불러도 과하지 않습니다.
이번 글에서 답하고 싶은 질문은 단순합니다.
- Astro 5는 정확히 무엇이 바뀌었는가
- Server Islands와 Content Layer는 어떤 문제를 푸는가
- 같은 카드에서 싸우는 Next.js의 RSC · Server Actions, SvelteKit과 비교하면 무엇이 다른가
- 콘텐츠 사이트, 마케팅 사이트, 문서, 커머스 스토어프런트 — 어디서 Astro가 이기고, 어디서 지는가
- Astro 4에서 5로 어떻게 옮겨가는가
이 글은 광고가 아닙니다. Astro가 잘하는 일과 못하는 일을 같은 무게로 적습니다. 결정은 마지막 표 한 장에서 하시면 됩니다.
1. 아일랜드 아키텍처 복습 — Astro가 처음부터 다르게 했던 한 가지
먼저 짧게 복습합니다. 다른 프레임워크가 SPA에서 SSR로 갔다가 다시 RSC로 우회하는 동안, Astro는 처음부터 다른 길을 갔습니다.
1.1 "모든 페이지는 HTML이다"
기본 출력물은 HTML입니다. JavaScript는 필요한 컴포넌트에만 명시적으로 붙입니다. 이 정책의 이름이 바로 아일랜드 아키텍처(Islands Architecture) 입니다.
페이지 전체가 하나의 React 트리가 아니라, 정적인 HTML 바다 위에 인터랙티브한 "섬"이 듬성듬성 떠 있는 그림입니다. 섬마다 자기 번들과 자기 하이드레이션 시점을 가집니다. 그 결과 평균적인 콘텐츠 페이지에서 클라이언트로 내려가는 JS가 압도적으로 적습니다.
1.2 클라이언트 디렉티브
섬을 만드는 방법은 컴포넌트 옆에 디렉티브 한 줄을 붙이는 것입니다.
---
import Counter from '../components/Counter.tsx'
import LikeButton from '../components/LikeButton.svelte'
import SearchBox from '../components/SearchBox.vue'
---
<h1>섬 없는 텍스트는 정적 HTML</h1>
<Counter client:load />
<LikeButton client:visible />
<SearchBox client:idle />
client:load— 페이지 로드 시 즉시 하이드레이트client:idle— 브라우저가 한가할 때client:visible— 뷰포트에 들어올 때client:media="(max-width: 600px)"— 미디어 쿼리 충족 시client:only="react"— 서버에서 렌더하지 않고 클라이언트에서만
이 한 줄의 차이로 "이 페이지의 4킬로바이트 JS"와 "이 페이지의 300킬로바이트 JS"가 갈립니다.
1.3 프레임워크 어그노스틱
같은 페이지 안에 React, Svelte, Vue, Solid, Preact, Lit을 섞을 수 있습니다. 실무에서 이걸 적극적으로 활용하는 팀은 드물지만, 레거시 위젯을 점진적으로 흡수할 수 있다는 의미는 큽니다. 디자인 시스템은 Svelte, 인터랙티브 데모는 React 같은 분할이 자연스럽습니다.
2. Astro 5가 가져온 큰 그림 — 6가지 헤드라인 변화
Astro 5의 핵심을 한 화면에 담으면 다음과 같습니다.
| 영역 | Astro 4 | Astro 5 |
|---|---|---|
| 콘텐츠 모델 | Content Collections (파일 기반) | Content Layer API (어떤 소스든) |
| 동적 페이지 | 전체 페이지 SSR 또는 정적 | Server Islands (정적 위에 서버 섬) |
| 번들러 | Vite 5 | Vite 6, 이후 6.x 라인에서 Vite 7 합류 |
| 폼/뮤테이션 | 직접 핸들러 | Astro Actions (타입 안전) |
| 라우팅 | 정적 + 부분 SSR | prerender per-route 정교화 |
| i18n | 실험적 | 안정화 + fallback/도메인 라우팅 |
이걸 한 줄로 줄이면 — "정적 우선을 유지한 채로, 풀스택의 일부를 받아들였다" 입니다. Next.js와 반대 방향에서 같은 지점에 도달하는 중입니다. Next.js는 클라이언트 React에서 출발해 RSC로 정적/서버 경계를 다시 그렸고, Astro는 정적 HTML에서 출발해 Server Islands로 서버를 다시 받아들였습니다.
3. Server Islands — '정적 우선'을 깨지 않고 개인화를 끼워 넣는 법
Server Islands는 Astro 5의 얼굴입니다. 한 문장으로:
"페이지를 CDN에서 정적으로 캐시하고, 사람마다 달라야 하는 작은 조각만 서버에서 늦게 그려서 클라이언트가 뒤따라 끼워 넣는다."
3.1 문제 — "캐시할 수 있는 99%와 캐시할 수 없는 1%"
전형적인 콘텐츠 사이트의 페이지를 떠올려 보세요. 헤더, 본문, 사이드바, 푸터 — 거의 모든 픽셀이 모든 방문자에게 동일합니다. 다만 우측 상단의 "로그인됨/안 됨", "장바구니 (3)", "내 추천 글" 같은 작은 조각이 사람마다 다릅니다.
전통적 선택지:
- 전체 페이지 SSR — 1%를 위해 99%를 매번 다시 그린다. 캐시가 무용지물.
- 전체 정적 + 클라이언트 fetch — JS 로드 → fetch → 렌더링 → 레이아웃 시프트. 코어 웹 바이탈 손상.
- ESI / Edge Side Includes — 이론적으론 좋지만 CDN 의존, 디버깅 지옥.
Server Islands는 4번째 길입니다.
3.2 사용법 — server:defer 한 줄
---
// src/pages/index.astro
import Layout from '../layouts/Layout.astro'
import Avatar from '../components/Avatar.astro'
import AvatarFallback from '../components/AvatarFallback.astro'
---
<Layout>
<h1>오늘의 추천 글</h1>
<p>이 본문은 빌드 타임에 만들어져 CDN에 캐시됩니다.</p>
<Avatar server:defer>
<AvatarFallback slot="fallback" />
</Avatar>
</Layout>
---
// src/components/Avatar.astro
import { getUserFromCookie } from '../lib/auth'
const user = await getUserFromCookie(Astro.request)
---
{user ? (
<a href="/me"><img src={user.avatar} alt={user.name} /></a>
) : (
<a href="/login">로그인</a>
)}
server:defer가 붙은 컴포넌트는:
- 빌드 타임에는 렌더되지 않습니다. 대신
slot="fallback"이 자리를 차지합니다. - 페이지는 정적 HTML로 CDN에 캐시됩니다. 0.5kB 안팎의 스크립트와 placeholder만 들어 있습니다.
- 브라우저는 그 자리에 들어갈 HTML을 별도 엔드포인트(
/_server-islands/Avatar)에서 가져옵니다. - 응답이 도착하면 placeholder가 실제 HTML로 교체됩니다.
핵심은 — 본문은 캐시되고, 섬만 사람마다 다르다는 점입니다. CDN을 100% 활용하면서 개인화가 됩니다.
3.3 GA에서 추가된 디테일
베타 동안 다음이 더해지면서 5.0 GA에 올라왔습니다.
- 헤더 설정 — Server Island 응답에
Cache-Control,Set-Cookie같은 헤더를 따로 붙일 수 있음 - 자동 압축 플랫폼 호환 — 기본 압축을 강제하는 호스팅에서도 작동
- Props 자동 암호화 — Server Island에 넘긴 props는 자동으로 암호화돼서, URL이나 placeholder를 통해 클라이언트가 들여다볼 수 없음. 권한 정보 같은 걸 넣어도 안전
3.4 언제 쓰고, 언제 쓰지 말아야 하는가
쓸 때:
- 99/1 페이지 — 본문은 모두에게 같고, 헤더의 사용자 상태/장바구니 같은 작은 조각만 다름
- 위젯 단위 개인화 — "방금 본 글", "추천 상품 3개"
- 로그인 여부에 따른 작은 UI 차이
쓰지 말아야 할 때:
- 페이지 전체가 사용자마다 다른 경우 (대시보드, 메일함, 콘솔) — 그냥 SSR 또는 SPA
- LCP 위의 위 (above-the-fold) 핵심 콘텐츠 — 늦게 채워지면 사용자 인식에 직격타
- 외부 API에 동기적으로 의존하는데 그 API가 느린 경우 — 차라리 클라이언트 페치가 나음
4. Content Layer API — '콘텐츠 컬렉션'을 '어떤 소스든' 으로 일반화
두 번째 큰 변화는 Content Layer입니다. Astro 4의 Content Collections는 파일 시스템의 마크다운/MDX 가 1급 시민이었습니다. Astro 5는 그것을 한 단계 추상화해서, 로더가 곧 컬렉션이 되도록 만들었습니다.
4.1 모델 — Loader + Schema
// src/content.config.ts
import { defineCollection, z } from 'astro:content'
import { glob } from 'astro/loaders'
const blog = defineCollection({
loader: glob({ pattern: '**/*.md', base: './src/content/blog' }),
schema: z.object({
title: z.string(),
pubDate: z.coerce.date(),
tags: z.array(z.string()).default([]),
draft: z.boolean().default(false),
}),
})
export const collections = { blog }
loader— 데이터를 어디서 가져올지schema— Zod로 타입과 검증을 동시에
glob 외에 공식 file 로더가 있고, 그 너머는 자유입니다.
4.2 커스텀 로더 — HTTP, DB, CMS 모두
가장 흥미로운 부분입니다. 임의의 데이터 소스를 컬렉션으로 만들 수 있습니다.
// src/loaders/notion.ts
import type { Loader, LoaderContext } from 'astro/loaders'
export function notionLoader(opts: { databaseId: string }): Loader {
return {
name: 'notion-loader',
async load({ store, parseData, meta, generateDigest }: LoaderContext) {
const lastSynced = meta.get('last-synced')
const pages = await fetchNotionPages(opts.databaseId, { since: lastSynced })
for (const p of pages) {
const data = await parseData({
id: p.id,
data: {
title: p.properties.Title.title[0].plain_text,
slug: p.properties.Slug.rich_text[0].plain_text,
body: p.body,
updatedAt: p.last_edited_time,
},
})
const digest = generateDigest(data)
store.set({ id: p.id, data, digest })
}
meta.set('last-synced', new Date().toISOString())
},
}
}
// src/content.config.ts
import { defineCollection, z } from 'astro:content'
import { notionLoader } from './loaders/notion'
const articles = defineCollection({
loader: notionLoader({ databaseId: process.env.NOTION_DB_ID! }),
schema: z.object({
title: z.string(),
slug: z.string(),
body: z.string(),
updatedAt: z.coerce.date(),
}),
})
export const collections = { articles }
이제 페이지에서는 똑같이 씁니다.
---
import { getCollection } from 'astro:content'
const articles = await getCollection('articles')
---
<ul>
{articles.map(a => <li><a href={`/articles/${a.data.slug}`}>{a.data.title}</a></li>)}
</ul>
데이터가 마크다운 파일이든, Notion이든, Sanity든, 자체 PostgreSQL이든 — 페이지 코드는 모릅니다. 이게 Content Layer의 본질입니다.
4.3 빌드 캐시와 인크리멘털
Content Layer는 SQLite 기반 캐시를 깔고 있어, 변경된 엔트리만 다시 처리합니다. 1만 개 콘텐츠 사이트의 두 번째 빌드가 첫 빌드보다 압도적으로 빠른 이유입니다. 로더는 meta.get/set으로 자기 마지막 동기화 시점을 기억할 수 있고, generateDigest로 콘텐츠 해시를 만들어 변경 여부를 판단합니다.
4.4 외부 로더 생태계
5.0 발표 이후 단기간에 공식·서드파티 로더가 우르르 등장했습니다 — Storyblok, Hygraph, WordPress, Ghost, Strapi, Sanity 등. 즉 "Astro = 헤드리스 CMS의 보편적 프런트엔드" 라는 포지셔닝이 실제로 작동하기 시작했습니다.
4.5 Live Loaders — 런타임 콘텐츠
GA 이후 추가된 흐름이 Live Content Loaders 입니다. 빌드 타임이 아니라 요청 시점에 데이터를 가져오는 로더로, "정적 80% + 라이브 20%" 시나리오(가격, 재고, 환율)에 적합합니다. 같은 getCollection API를 유지하면서 라이브로 갈 수 있다는 점이 인상적입니다.
5. Astro Actions — 폼과 뮤테이션을 타입 안전하게
세 번째 큰 변화는 Astro Actions입니다. 한마디로 "타입 안전한 서버 함수, Zod 입력 스키마와 함께".
5.1 정의
// src/actions/index.ts
import { defineAction } from 'astro:actions'
import { z } from 'astro:schema'
export const server = {
subscribe: defineAction({
accept: 'form',
input: z.object({
email: z.string().email(),
utm: z.string().optional(),
}),
handler: async (input, ctx) => {
const ip = ctx.request.headers.get('x-forwarded-for')
await saveSubscriber({ email: input.email, utm: input.utm, ip })
return { ok: true }
},
}),
}
5.2 호출 — 서버에서 그대로
---
import { actions } from 'astro:actions'
const result = Astro.getActionResult(actions.subscribe)
---
<form method="POST" action={actions.subscribe}>
<input type="email" name="email" required />
<input type="hidden" name="utm" value="blog-banner" />
<button type="submit">구독</button>
{result?.data?.ok && <p>고맙습니다!</p>}
{result?.error && <p class="err">에러: 다시 시도해 주세요.</p>}
</form>
JS가 꺼져 있어도 폼은 작동합니다. JS가 켜져 있으면 점진적으로 강화된 UX가 됩니다. 클라이언트 JS에서도 actions.subscribe(input) 으로 호출 가능합니다.
5.3 무엇이 좋은가
- 입력 검증과 타입이 단일 소스 — Zod 스키마 하나로 런타임 검증, TS 타입, 자동완성이 동시에 됩니다
- 점진적 강화 친화 — 일반 HTML 폼으로도 작동
- Server Islands와 잘 어울림 — 액션이 끝난 뒤 페이지 일부를 무효화/재렌더 가능
Next.js의 Server Actions와 컨셉상 형제지만, Astro는 "정적 페이지 위에 폼 한 개"라는 경량 시나리오에 더 가볍게 맞습니다.
6. Vite 6 / Vite 7 통합 — 빌드의 속살
Astro 5는 Vite 6 위에서 출시됐고, 이후 5.x/6.x 라인에서 Vite 7 합류 작업이 진행됐습니다. 개발자 입장에서 느끼는 변화:
- Environment API — 같은 빌드 그래프 안에서 클라이언트/서버/엣지 같은 다중 환경을 일관되게 다룸. Server Islands를 비롯해 서버 코드 분리가 더 깔끔
- 빠른 콜드 스타트 — 큰 사이트의 첫
astro dev가 체감상 더 가벼움 - CSS 변경 감지 — HMR이 더 정확. CSS 한 글자 바꿔도 풀 리로드 안 함
- 롤업 4.x / 5.x 라인 활용
여기서 중요한 시사점은 — Astro 팀이 Vite 코어 개발자와 거의 한 팀처럼 움직인다는 점입니다. Vite의 변화가 Astro에 빠르게 흘러들어옵니다.
7. View Transitions와 prefetching — SPA처럼 매끄럽게, MPA처럼 가볍게
콘텐츠 사이트의 약점 중 하나는 페이지 이동마다 흰 화면이 깜빡인다는 점이었습니다. View Transitions API와 핵심 prefetching이 이걸 풀어냅니다.
7.1 View Transitions
---
import { ClientRouter } from 'astro:transitions'
---
<head>
<ClientRouter />
</head>
이 한 줄을 레이아웃에 넣으면, 같은 사이트 안 내비게이션이 MPA 그대로 동작하면서 화면 전환에 브라우저 네이티브 View Transitions가 입혀집니다. 헤더는 유지되고 본문만 페이드되거나, 썸네일이 다음 페이지의 히어로 이미지로 자연스럽게 모핑되는 식입니다.
요소별로 이름을 주면 페이지 간 동일 요소가 트래킹됩니다.
<img src={post.cover} transition:name={`cover-${post.slug}`} />
7.2 prefetching — 코어로 흡수
prefetch는 Astro 3.5에서 코어로 들어왔고, 5에서는 기본 ON이 좀 더 합리적인 휴리스틱으로 다듬어졌습니다.
// astro.config.mjs
export default defineConfig({
prefetch: {
prefetchAll: true,
defaultStrategy: 'viewport',
},
})
전략은 tap, hover, viewport, load. 링크별로 data-astro-prefetch="hover" 같이 오버라이드 가능. 결과는 — 사용자가 클릭하기 전에 다음 페이지가 이미 와 있다.
7.3 i18n 개선
i18n은 4.x에서 안정화에 들어갔고 5.x에서 다듬어졌습니다.
- 기본 로캘과 로캘 목록을 한 번에 선언
- URL 전략 — prefix-other-locales(기본 로캘만 prefix 없음), prefix-always, 도메인 기반
getRelativeLocaleUrl,getAbsoluteLocaleUrl같은 헬퍼- 누락된 페이지 fallback — 일본어 페이지가 없으면 영어로 대체
콘텐츠 사이트는 거의 모두 다국어를 결국 만나기 때문에, 이 부분이 1급으로 들어왔다는 것이 큰 의미입니다.
8. Astro vs Next.js vs SvelteKit — 결정 매트릭스
이제 본론입니다. 어디서 무엇을 쓸지.
8.1 철학 한 줄
- Astro 5 — 정적 우선. 필요한 부분만 서버/클라이언트 섬으로.
- Next.js (App Router + RSC) — 서버 우선. 정적은 캐시의 한 형태.
- SvelteKit — 라우터 우선. 컴파일러로 런타임을 줄이는 통합 풀스택.
8.2 항목별 비교
| 항목 | Astro 5 | Next.js (RSC) | SvelteKit |
|---|---|---|---|
| 기본 JS 페이로드 | 가장 적음 (0이 기본) | 중간~높음 | 적음 |
| 풀스택 깊이 | 얕음~중간 | 깊음 | 중간 |
| 콘텐츠 모델 | Content Layer (1급) | App Router + MDX (애드온) | 직접 구성 |
| 폼/뮤테이션 | Actions | Server Actions | form actions |
| 동적 개인화 | Server Islands | RSC + Suspense | streaming + load |
| 다국어 | 코어 i18n | 별도 라이브러리 | 별도 라이브러리 |
| 호스팅 | Static + 어디든 어댑터 | Vercel 최적 | 어댑터 다양 |
| 학습 곡선 | 낮음 | 중간~높음 | 낮음~중간 |
| 컴포넌트 호환성 | React/Svelte/Vue/Solid 등 | React 전용 | Svelte 전용 |
8.3 시나리오별 추천
- 블로그, 문서, 마케팅 사이트, 미디어: Astro 5. Server Islands로 헤더 개인화 정도는 자연스럽게 처리됨. 코어 웹 바이탈에서 거의 이길 곳이 없음
- e커머스 스토어프런트 (헤드리스 CMS + 결제): Astro 5 우세. 상품 페이지 정적, 장바구니/추천만 Server Island. 단, 체크아웃 깊이 들어가면 다른 프레임워크가 나을 수 있음
- 풀스택 SaaS, 대시보드: Next.js. 인증, 권한, 실시간, 복잡한 폼, 깊은 라우팅 트리는 RSC가 더 자연스러움
- 소셜/실시간 앱: Next.js 또는 SvelteKit. Astro의 영역 아님
- 앱스러운 SPA + 약간의 서버: SvelteKit. 코드 양이 가장 적게 나옴
- 다국어 콘텐츠 허브, 매우 큰 사이트: Astro 5. Content Layer + i18n이 이 시나리오를 위해 만들어진 것 같음
한 줄로 — 콘텐츠가 1순위면 Astro, 애플리케이션이 1순위면 Next.js/SvelteKit.
9. 실제 적용 사례 — 콘텐츠 헤비 사이트에서의 모양
가상의 미디어 회사를 가정합니다. 월 1억 PV의 뉴스 사이트. 페이지 구성을 Astro 5 기준으로 어떻게 짤지 살펴봅니다.
9.1 페이지 단위 결정
/홈 — 정적 빌드, Server Island로 "로그인 헤더"와 "추천 섹션"만/articles/[slug]— 정적 빌드 (Content Layer가 CMS에서 가져옴), Server Island로 "헤더 + 댓글 수 + 좋아요 상태"/search— SSR (검색 쿼리가 캐시 키를 망가뜨림)/me,/me/bookmarks— SSR 또는 클라이언트 페치/auth/*— SSR + Actions
CDN 캐시 적중률이 95% 이상이 됩니다. 99/1 페이지에 99/1의 비용 구조가 따라옵니다.
9.2 데이터 흐름
- CMS (예: Sanity, Storyblok) — Content Layer 로더로 빌드 타임 동기화. 변경된 글만 재빌드(인크리멘털).
- 사용자/세션 — Server Island가 쿠키로 직접 읽음. 본문 캐시와 무관.
- 추천 알고리즘 — Server Island가 요청 시점에 추천 API 호출.
- 댓글 — Live Loader 또는 클라이언트 컴포넌트.
9.3 빌드 시간
- 1만 글 콜드 빌드: 수 분
- 1만 글 + 100개 변경 인크리멘털 빌드: 수십 초
- 캐시는 SQLite로 저장돼 CI 캐시에 그대로 올림
같은 규모의 Next.js 정적 빌드보다 빠를 가능성이 높습니다. 단, Next.js의 ISR(Incremental Static Regeneration) + 온디맨드 재검증 모델은 또 다른 매력이 있어, 절대 우위는 아닙니다.
10. Astro 4 → 5 마이그레이션 — 솔직한 이야기
가장 현실적인 질문 — 4에서 5로 옮기는 게 얼마나 아픈가.
10.1 좋은 소식
- 페이지/컴포넌트 문법은 그대로
- 라우팅 그대로
- 통합(Integrations) 대부분 그대로
10.2 손이 가는 부분
-
Content Collections → Content Layer
src/content/config.ts에loader: glob({...})추가getEntry/getCollection의 일부 시그니처 변경- 빌드 타임 캐시가 새로 생기므로
.astro/같은 캐시 디렉터리 CI에 마운트
-
Astro.glob제거- 위 Content Layer로 흡수됨. 마크다운 페이지 수집을 직접 하던 코드가 있다면
getCollection으로 옮김
- 위 Content Layer로 흡수됨. 마크다운 페이지 수집을 직접 하던 코드가 있다면
-
Image 처리 정책 변화
astro:assets의 옵션 일부 정리. 외부 이미지 도메인 화이트리스트는 더 엄격해짐
-
Adapter 업데이트
- Vercel, Netlify, Cloudflare, Node 어댑터 모두 5.x 버전 필요
-
Vite 6 환경 호환
- 커스텀 Vite 플러그인을 끼고 있다면 5/6 호환성 확인
10.3 마이그레이션 절차
- Astro 4 최신 패치로 올린 뒤 deprecation 경고를 0으로
pnpm dlx @astrojs/upgrade로 일괄 메이저 점프astro check통과시키기- 한 페이지에 Server Island 시험 도입
- 한 컬렉션부터 Content Layer로 이주
- 점진 확장
큰 사이트라도 보통 며칠 단위로 끝납니다. Next.js의 Pages → App Router 마이그레이션보다 훨씬 가볍습니다.
11. 솔직한 한계 — Astro가 여전히 지지 않는 곳, 지는 곳
광고를 빼고 적습니다.
11.1 여전히 강한 곳
- 콘텐츠 사이트 / 문서 / 마케팅 페이지에서 코어 웹 바이탈은 거의 무패
- 헤드리스 CMS 통합이 가장 자연스러움 (Content Layer 효과)
- 다국어 사이트의 1급 지원
- 빌드 시간 (인크리멘털 캐시)
- 다중 프레임워크 위젯 공존
11.2 약한 곳
- 깊은 풀스택 — 인증, 권한, 워크플로우, 큐, 백그라운드 작업이 페이지 코드와 강하게 엮인 시나리오. Astro는 의도적으로 얕음.
- 앱스러운 SPA — 모든 페이지가 동적이고 상태 공유가 깊다면, Astro의 정적 우선 모델이 이점을 잃음.
- 에코시스템 깊이 — Next.js 대비 라이브러리/예제/구인 시장이 작음.
- 호스팅 락인 없음의 그림자 — Vercel만큼 매끄럽게 통합된 호스팅이 부재. 어댑터 품질이 호스팅마다 다름.
- 개발 도구 — 강력하지만 Next.js의 RSC 디버깅/캐시 시각화 만큼 무르익지 않음.
- Server Islands의 함정 — 너무 많이 깔면 페이지 위가 placeholder투성이가 되고 사용자 인식이 나빠짐. "본문은 정적, 작은 섬만 동적" 원칙을 지켜야 함.
11.3 결정 한 줄
"당신의 사이트가 책이라면 Astro. 당신의 사이트가 도구라면 Next.js. 그 중간이면 SvelteKit도 같이 평가하라."
에필로그 — 체크리스트와 다음 글 예고
12.1 Astro 5를 도입할 때 체크리스트
- 페이지의 캐시 가능 비율이 80% 이상인가 (Server Islands가 빛나는 임계)
- 콘텐츠 소스가 1개 이상이고, 그중 일부가 외부 CMS/DB인가 (Content Layer 적합)
- 다국어가 필요한가 (코어 i18n 활용)
- 빌드 시간이 늘면 곤란한가 (인크리멘털 캐시 필요)
- 컴포넌트 라이브러리가 React/Svelte/Vue 중 하나로 고정되어 있는가 (호환성 점검)
- 호스팅 어댑터(Vercel/Netlify/Cloudflare/Node)가 안정 버전인지 확인했는가
- 폼은 Actions로, 페이지 일부 개인화는 Server Island로, 진짜 동적 페이지만 SSR로 — 룰을 합의했는가
12.2 안티패턴
- 페이지의 70%를 Server Island로 만든다 (그러면 그냥 SSR이 낫다)
- LCP 영역 위에 Server Island를 둔다 (지연이 사용자에게 직격)
- Content Layer를 우회해 페이지 컴포넌트에서 직접 fetch를 흩뿌린다 (캐시·타입 둘 다 잃음)
- Actions를 거치지 않고 직접 API 라우트만 만든다 (점진적 강화 못함)
client:load로 모든 컴포넌트를 하이드레이트한다 (Astro 쓰는 의미가 사라짐)- View Transitions 도입하면서 페이지마다 다른 레이아웃을 쓴다 (요소 추적이 깨짐)
12.3 다음 글 예고
- "Astro Server Islands로 e커머스 스토어프런트 만들기 — 본문 캐시 95%를 지키는 법"
- "Content Layer 커스텀 로더 패턴 — Notion, Postgres, S3에서 컬렉션 끌어오기"
- "Astro Actions와 점진적 강화 폼 — JS 0kb 폼이 가능한 이유"
- "Astro 6를 향한 로드맵 — Live Loaders 안정화와 그 다음"
이 시리즈의 한 줄 약속 — "콘텐츠 사이트의 표준은 다시 바뀌었고, 그 이름은 Astro 5다."
참고 / References
- Astro 5.0 Release Blog (2024-12-03)
- Astro 5.0 Beta Release
- Server Islands — Astro Docs
- Server Islands Announcement
- Islands Architecture — Astro Docs
- Content Layer — Deep Dive
- Content Collections — Astro Docs
- Content Loader API Reference
- Astro Actions — Docs
- Actions API Reference
- View Transitions — Docs
- View Transitions Router Reference
- Astro 2024 Year in Review
- Live Content Loaders Roadmap Issue #1151
- Storyblok Loader for Content Layer
- Hygraph Astro Content Loader
- Content Layer for Headless WordPress
- Comparing JS Frameworks for Content Sites — DatoCMS
- Astro 4.0 Release
- Astro 3.5 i18n Routing
현재 단락 (1/300)
2022년의 Astro는 마크다운 블로그용 정적 사이트 생성기였습니다. 2024년 12월 3일에 공개된 Astro 5는, 같은 이름을 달고 있지만 다른 물건입니다. **콘텐츠 중심...