Table of Contents
목차
- 들어가며: 개발자로서의 선택
- Astro란 무엇인가?
- 왜 Astro를 선택했는가?
- Islands Architecture: Astro의 핵심
- Next.js vs Astro: 실전 비교
- Astro의 주요 특징
- 실제 사용 경험과 코드 예제
- Astro가 적합한 프로젝트
- Astro의 한계와 주의사항
- 결론: Astro로의 전환을 추천하는 이유
들어가며: 개발자로서의 선택
안녕하세요, Eddie입니다.
저는 지난 몇 년간 React, Next.js, Vue 등 다양한 웹 프레임워크를 사용해왔습니다. 하지만 최근 Astro 프레임워크를 접한 후, 제 웹 개발 워크플로우가 완전히 바뀌었습니다. 지금 이 블로그도 Astro로 만들어졌으며, 현재 Astro는 제 웹 개발의 첫 번째 선택지가 되었습니다.
:::note 왜 이 글을 쓰나요? 많은 개발자들이 관성적으로 Next.js나 Create React App을 기본으로 사용합니다. 물론 이들은 훌륭한 도구입니다. 하지만 콘텐츠 중심의 웹사이트를 만들 때는 Astro가 압도적으로 더 나은 선택이라는 것을 깨달았습니다.
이 글은 단순한 프레임워크 소개가 아닙니다. 실제로 프로젝트를 진행하면서 느낀 경험과 Next.js와의 직접적인 비교를 통해, 왜 Astro가 더 나은 선택인지 설명하겠습니다. :::
이 글을 읽어야 할 개발자
- 빠른 웹사이트를 만들고 싶은 개발자
- 블로그나 문서 사이트를 구축하려는 개발자
- 성능 최적화에 관심이 있는 개발자
- Next.js의 복잡성에 피로를 느끼는 개발자
- SEO가 중요한 프로젝트를 진행하는 개발자
- 서버 비용을 절감하고 싶은 개발자
Astro란 무엇인가?
Astro는 콘텐츠 중심 웹사이트를 위해 설계된 현대적인 웹 프레임워크입니다. 2021년에 처음 공개되어, 빠르게 성장하고 있는 오픈소스 프로젝트입니다.
Astro의 핵심 철학
// Astro의 기본 원칙
"기본적으로 자바스크립트를 전송하지 않는다"
이 한 문장이 Astro의 모든 것을 설명합니다. Astro는:
- 정적 HTML을 기본으로 생성합니다.
- 필요한 곳에만 JavaScript를 추가합니다 (Partial Hydration).
- 성능이 최우선입니다.
- 개발자 경험도 중요하게 생각합니다.
:::tip Astro의 공식 슬로건 “The web framework for content-driven websites” (콘텐츠 중심 웹사이트를 위한 웹 프레임워크) :::
Astro의 탄생 배경
현대 웹 개발은 점점 더 복잡해지고 있습니다:
- 과도한 JavaScript 번들 크기
- 느린 페이지 로드 시간
- 복잡한 설정과 빌드 과정
- 낮은 SEO 성능
Astro는 이러한 문제들을 해결하기 위해 탄생했습니다. 특히 블로그, 마케팅 사이트, 문서 사이트처럼 콘텐츠가 주가 되는 웹사이트에 최적화되어 있습니다.
왜 Astro를 선택했는가?
제가 Astro를 첫 번째 선택지로 삼은 이유를 솔직하게 말씀드리겠습니다.
1. 압도적인 성능
Lighthouse 점수 비교 (이 블로그 기준):
| 프레임워크 | Performance | SEO | Best Practices | Accessibility |
|---|---|---|---|---|
| Astro | 100 | 100 | 100 | 100 |
| Next.js (SSG) | 95 | 100 | 92 | 95 |
| Next.js (SSR) | 85 | 100 | 92 | 95 |
:::important 실제 성능 차이 같은 콘텐츠를 Astro와 Next.js로 만들었을 때:
- First Contentful Paint (FCP): Astro가 40% 더 빠름
- Time to Interactive (TTI): Astro가 60% 더 빠름
- Total Blocking Time (TBT): Astro는 거의 0
- JavaScript 번들 크기: Astro가 90% 더 작음
숫자로 말해주는 성능 차이입니다. :::
2. 단순한 개발 경험
Next.js로 블로그를 만들 때:
# 설치해야 할 패키지들
npm install next react react-dom
npm install @next/mdx @mdx-js/loader @mdx-js/react
npm install gray-matter reading-time
npm install rehype-slug rehype-autolink-headings
#... 계속해서 추가 패키지 필요
Astro로 블로그를 만들 때:
# 기본적으로 다 포함되어 있음
npm create astro@latest
# 끝!
Astro는 Markdown 지원, 이미지 최적화, RSS 피드, 사이트맵 등이 기본적으로 내장되어 있습니다.
3. 서버 비용 제로
Astro로 만든 사이트는:
- 정적 파일로 빌드됩니다.
- Netlify, Vercel, Cloudflare Pages 등에서 무료 호스팅이 가능합니다.
- 서버 없이 동작합니다 (필요시 SSR도 가능).
- CDN 캐싱으로 전 세계 어디서나 빠릅니다.
Next.js의 경우:
- 서버 비용 발생 (SSR 사용 시)
- Vercel Pro 요금제가 필요할 수 있음
- 복잡한 캐싱 전략 필요
4. 프레임워크 중립성
---
// Astro에서는 여러 프레임워크를 동시에 사용 가능!
import ReactComponent from './ReactComponent.jsx'
import VueComponent from './VueComponent.vue'
import SvelteComponent from './SvelteComponent.svelte'
---
<div>
<ReactComponent />
<VueComponent />
<SvelteComponent />
</div>
하나의 프로젝트에서 React, Vue, Svelte를 동시에 사용할 수 있습니다. 기존 컴포넌트를 재사용하거나, 팀원마다 선호하는 프레임워크를 사용할 수 있습니다.
:::tip 실무에서의 장점 레거시 프로젝트를 마이그레이션할 때, 기존 React 컴포넌트를 그대로 사용하면서 점진적으로 Astro로 전환할 수 있습니다. 이는 리스크를 최소화하면서 마이그레이션할 수 있는 큰 장점입니다. :::
Islands Architecture: Astro의 핵심
Astro의 가장 혁신적인 개념은 Islands Architecture입니다.
Islands Architecture란?
전통적인 SPA(Single Page Application)를 바다에 비유하면:
- 전체가 자바스크립트로 된 바다
- 모든 곳에 JavaScript가 hydration됨
- 무거워서 느림
Astro의 Islands Architecture는:
- ️ 정적 HTML이 바다, JavaScript는 섬(Island)
- 필요한 곳에만 JavaScript 실행
- 빠르고 가벼움
실제 동작 방식
---
// 이 페이지는 기본적으로 정적 HTML
const posts = await getPosts()
---
<html>
<body>
<header>
<!-- 정적 HTML: JavaScript 없음 -->
<h1>내 블로그</h1>
</header>
<main>
{posts.map(post => (
<article>
<h2>{post.title}</h2>
<p>{post.excerpt}</p>
</article>
))}
</main>
<!-- Island: 여기만 JavaScript 실행 -->
<SearchBox client:load />
<Comments client:visible />
</body>
</html>
Hydration 전략
Astro는 5가지 hydration 전략을 제공합니다:
<!-- 1. 로드 즉시 hydrate -->
<InteractiveWidget client:load />
<!-- 2. 페이지 로드 완료 후 hydrate (Idle) -->
<LowPriorityWidget client:idle />
<!-- 3. 화면에 보일 때 hydrate (Intersection Observer) -->
<LazyWidget client:visible />
<!-- 4. 미디어 쿼리 조건 만족 시 hydrate -->
<MobileMenu client:media="(max-width: 768px)" />
<!-- 5. 절대 hydrate하지 않음 (순수 정적) -->
<StaticComponent client:only="react" />
:::important 성능 영향
- client:load: 가장 중요한 인터랙션 (예: 검색창)
- client:idle: 중요하지만 급하지 않음 (예: 채팅 위젯)
- client:visible: 스크롤 후 필요 (예: 댓글, 이미지 캐러셀)
적절한 전략 선택으로 JavaScript 실행을 최대 80% 감소시킬 수 있습니다. :::
Next.js vs Astro: 실전 비교
같은 블로그를 Next.js와 Astro로 만들어본 경험을 바탕으로 비교하겠습니다.
프로젝트 초기 설정
Next.js:
# 1. 프로젝트 생성
npx create-next-app@latest my-blog
# 2. Markdown 지원 추가
npm install @next/mdx @mdx-js/loader @mdx-js/react
# 3. 추가 플러그인 설치
npm install remark-gfm rehype-slug rehype-autolink-headings
npm install gray-matter reading-time
# 4. next.config.js 수정 필요
# 5. MDX 설정 파일 생성 필요
# 6. 동적 라우팅 설정
Astro:
# 1. 프로젝트 생성 (대화형)
npm create astro@latest my-blog
# Blog 템플릿 선택하면 모든 설정 완료!
# Markdown, MDX, 이미지 최적화, RSS 등 기본 포함
개발 시간 차이: Next.js는 최소 2-3시간 설정, Astro는 5분 완료
빌드 결과 비교
동일한 블로그 (10개 포스트):
| 항목 | Next.js (App Router) | Next.js (Pages Router) | Astro |
|---|---|---|---|
| 빌드 시간 | 45초 | 35초 | 15초 |
| 번들 크기 | 280KB | 220KB | 25KB |
| HTML 파일 | 85KB | 75KB | 35KB |
| First Load JS | 180KB | 150KB | 0KB* |
| 페이지 수 | 10 | 10 | 10 |
*검색이나 댓글 같은 인터랙티브 기능만 JavaScript 포함
:::caution Next.js의 무거운 번들 Next.js는 기본적으로 React runtime + hydration 코드를 모든 페이지에 포함합니다. 정적 블로그에서는 불필요한 오버헤드입니다. :::
개발자 경험 (DX) 비교
1. Markdown/MDX 처리
Next.js:
// pages/posts/[slug].tsx
import fs from 'fs'
import path from 'path'
import matter from 'gray-matter'
import { serialize } from 'next-mdx-remote/serialize'
export async function getStaticProps({ params }) {
const filePath = path.join('posts', `${params.slug}.mdx`)
const source = fs.readFileSync(filePath, 'utf8')
const { content, data } = matter(source)
const mdxSource = await serialize(content, {
mdxOptions: {
remarkPlugins: [],
rehypePlugins: [],
},
})
return {
props: {
source: mdxSource,
frontMatter: data,
},
}
}
Astro:
---
// src/pages/posts/[slug].astro
import { getCollection } from 'astro:content'
const posts = await getCollection('posts')
const { slug } = Astro.params
const post = posts.find(p => p.slug === slug)
const { Content } = await post.render()
---
<article>
<h1>{post.data.title}</h1>
<Content />
</article>
코드 길이: Next.js 40줄 vs Astro 10줄
2. 이미지 최적화
Next.js:
import Image from 'next/image'
// next.config.js에 도메인 설정 필요
// 이미지마다 width, height 필수
<Image
src="/photo.jpg"
alt="Photo"
width={800}
height={600}
/>
Astro:
---
import { Image } from 'astro:assets'
import photo from '../images/photo.jpg'
---
<!-- 자동으로 최적화되고 크기 자동 계산 -->
<Image src={photo} alt="Photo" />
3. 라우팅
Next.js:
pages/
index.tsx → /
posts/
[slug].tsx → /posts/:slug
[...slug].tsx → /posts/*
api/
hello.ts → /api/hello
Astro:
pages/
index.astro → /
posts/
[slug].astro → /posts/:slug
[...slug].astro → /posts/*
api/
hello.ts → /api/hello
라우팅은 비슷하지만, Astro는 기본적으로 정적 생성이고 Next.js는 설정이 더 복잡합니다.
실제 성능 측정
Lighthouse 점수 (같은 블로그 포스트):
Next.js App Router (SSG):
├─ Performance: 92
├─ FCP: 1.2s
├─ LCP: 2.1s
├─ TBT: 180ms
└─ JS: 220KB
Next.js Pages Router (SSG):
├─ Performance: 95
├─ FCP: 0.9s
├─ LCP: 1.8s
├─ TBT: 120ms
└─ JS: 150KB
Astro:
├─ Performance: 100
├─ FCP: 0.4s
├─ LCP: 0.6s
├─ TBT: 0ms
└─ JS: 0KB (정적 페이지)
:::tip 실제 사용자 경험
- 모바일 3G 환경에서 Astro가 3배 이상 빠르게 로드
- JavaScript 파싱 시간 제로
- 메모리 사용량 최소화
- 배터리 소모 감소 :::
Astro의 주요 특징
1. 콘텐츠 컬렉션 (Content Collections)
Astro는 타입 세이프한 콘텐츠 관리를 제공합니다:
// src/content/config.ts
import { defineCollection, z } from 'astro:content'
const blog = defineCollection({
schema: z.object({
title: z.string(),
description: z.string(),
published: z.date(),
author: z.string(),
tags: z.array(z.string()),
draft: z.boolean().default(false),
}),
})
export const collections = { blog }
이제 Markdown 파일의 frontmatter가 타입 체크됩니다:
---
title: "Astro 소개"
description: "Astro 프레임워크 소개"
published: 2025-11-03
author: "Eddie"
tags: ["astro", "web"]
draft: false
# 만약 필수 필드가 누락되면 빌드 에러!
---
2. 서버 엔드포인트 (API Routes)
Astro는 서버리스 함수를 쉽게 만들 수 있습니다:
// src/pages/api/posts.json.ts
import type { APIRoute } from 'astro'
import { getCollection } from 'astro:content'
export const GET: APIRoute = async () => {
const posts = await getCollection('posts')
return new Response(JSON.stringify(posts), {
status: 200,
headers: {
'Content-Type': 'application/json'
}
})
}
3. 뷰 트랜지션 (View Transitions)
단 한 줄 추가로 페이지 전환 애니메이션을 적용할 수 있습니다:
---
import { ViewTransitions } from 'astro:transitions'
---
<html>
<head>
<ViewTransitions />
</head>
<body>
<!-- 자동으로 부드러운 페이지 전환! -->
</body>
</html>
4. 빌트인 최적화
Astro는 기본적으로 다음을 자동 최적화합니다:
- 이미지 최적화: WebP/AVIF 자동 변환
- CSS 인라인화: Critical CSS 자동 추출
- JavaScript 번들링: 필요한 것만 포함
- HTML 압축: 불필요한 공백 제거
- Font 최적화: 폰트 프리로드
- Sitemap 생성: SEO를 위한 사이트맵
- RSS 피드: 블로그 RSS 자동 생성
실제 사용 경험과 코드 예제
제가 이 블로그를 만들면서 작성한 실제 코드를 공유합니다.
블로그 포스트 목록 페이지
---
// src/pages/posts/index.astro
import { getCollection } from 'astro:content'
import Layout from '~/layouts/Layout.astro'
import PostCard from '~/components/PostCard.astro'
// 빌드 타임에 모든 포스트 가져오기
const allPosts = await getCollection('posts', ({ data }) => {
return data.draft !== true // draft 제외
})
// 날짜순 정렬
const posts = allPosts.sort((a, b) =>
b.data.published.valueOf() - a.data.published.valueOf()
)
---
<Layout title="블로그 포스트">
<main>
<h1>최신 포스트</h1>
<div class="post-grid">
{posts.map(post => (
<PostCard post={post} />
))}
</div>
</main>
</Layout>
<style>
.post-grid {
display: grid;
grid-template-columns: repeat(auto-fit, minmax(300px, 1fr));
gap: 2rem;
}
</style>
개별 포스트 페이지
---
// src/pages/posts/[slug].astro
import { getCollection } from 'astro:content'
import Layout from '~/layouts/Layout.astro'
export async function getStaticPaths() {
const posts = await getCollection('posts')
return posts.map(post => ({
params: { slug: post.slug },
props: { post },
}))
}
const { post } = Astro.props
const { Content, headings } = await post.render()
---
<Layout
title={post.data.title}
description={post.data.description}
>
<article>
<header>
<h1>{post.data.title}</h1>
<time datetime={post.data.published.toISOString()}>
{post.data.published.toLocaleDateString('ko-KR')}
</time>
</header>
<Content />
<!-- 목차 (TOC) -->
{headings.length > 0 && (
<aside>
<h2>목차</h2>
<ul>
{headings.map(h => (
<li style={`margin-left: ${(h.depth - 1) * 1}rem`}>
<a href={`#${h.slug}`}>{h.text}</a>
</li>
))}
</ul>
</aside>
)}
</article>
</Layout>
검색 기능 (Island 활용)
---
// src/components/Search.astro
import SearchBox from './SearchBox.jsx'
import { getCollection } from 'astro:content'
const posts = await getCollection('posts')
const searchData = posts.map(post => ({
title: post.data.title,
description: post.data.description,
slug: post.slug,
}))
---
<!--
client:idle: 페이지 로드 후 검색 기능 초기화
서버에서 데이터를 준비하고, 클라이언트에서 인터랙션만 처리
-->
<SearchBox client:idle searchData={searchData} />
// src/components/SearchBox.jsx
import { useState } from 'react'
export default function SearchBox({ searchData }) {
const [query, setQuery] = useState('')
const results = searchData.filter(post =>
post.title.toLowerCase().includes(query.toLowerCase()) ||
post.description.toLowerCase().includes(query.toLowerCase())
)
return (
<div className="search">
<input
type="search"
placeholder="포스트 검색..."
value={query}
onChange={(e) => setQuery(e.target.value)}
/>
{query && (
<ul className="results">
{results.map(post => (
<li key={post.slug}>
<a href={`/posts/${post.slug}`}>
<h3>{post.title}</h3>
<p>{post.description}</p>
</a>
</li>
))}
</ul>
)}
</div>
)
}
RSS 피드 생성
// src/pages/rss.xml.js
import rss from '@astrojs/rss'
import { getCollection } from 'astro:content'
export async function GET(context) {
const posts = await getCollection('posts')
return rss({
title: 'Eddie의 블로그',
description: '웹 개발과 기술에 대한 이야기',
site: context.site,
items: posts.map(post => ({
title: post.data.title,
pubDate: post.data.published,
description: post.data.description,
link: `/posts/${post.slug}/`,
})),
})
}
Astro가 적합한 프로젝트
제 경험상 Astro가 완벽하게 적합한 프로젝트:
적합한 프로젝트
- 블로그 및 개인 웹사이트
- 이 블로그처럼 콘텐츠 중심
- Markdown/MDX 지원 필요
- SEO 최적화 필수
- 빠른 로딩 속도 중요
- 마케팅 및 랜딩 페이지
- 성능이 전환율에 직접 영향
- 정적 콘텐츠 + 부분적 인터랙션
- 다양한 CMS 통합 필요
- 문서 사이트
- 기술 문서
- API 레퍼런스
- 사용자 가이드
- 검색 기능 필요
- 포트폴리오
- 개발자 포트폴리오
- 디자이너 작품집
- 사진 갤러리
- E-commerce (콘텐츠 부분)
- 상품 상세 페이지
- 블로그/매거진 섹션
- 정적 콘텐츠 부분
- (장바구니는 React Island로)
부적합한 프로젝트
- 복잡한 SPA
- Gmail 같은 웹 앱
- 실시간 협업 도구
- 대시보드 (전체가 인터랙티브)
- 실시간 애플리케이션
- 채팅 앱
- 소셜 미디어
- 게임
- 클라이언트 사이드 라우팅이 필수
- SPA 스타일 네비게이션
- 복잡한 상태 관리
:::tip 하이브리드 접근 실제로는 Astro + React Island로 대부분의 요구사항을 충족할 수 있습니다. 예를 들어, 전자상거래 사이트는:
- 상품 페이지: Astro (정적, 빠름)
- 장바구니: React Island (인터랙티브)
- 결제: React Island (복잡한 상태 관리) :::
Astro의 한계와 주의사항
완벽한 도구는 없습니다. Astro의 한계도 알아야 합니다.
1. 학습 곡선
---
//.astro 파일은 독특한 문법
// 위: JavaScript/TypeScript (서버)
// 아래: HTML (템플릿)
---
<div>
<!-- JSX와 비슷하지만 다름 -->
</div>
<style>
/* Scoped CSS (기본) */
</style>
<script>
// 클라이언트 사이드 스크립트
</script>
처음에는 익숙하지 않지만, 30분이면 충분히 익힐 수 있습니다.
2. 생태계 크기
- Next.js: 수십만 개의 예제, 튜토리얼
- Astro: 수천 개의 예제 (빠르게 성장 중)
하지만 공식 문서가 훌륭해서 큰 문제는 없습니다.
3. 빌드 시간 (대규모 사이트)
- 수백 개 페이지: 문제없음
- 수천 개 페이지: 빌드 시간 증가
- 수만 개 페이지: 증분 빌드 고려 필요
:::caution 대규모 사이트 포스트가 10,000개 이상인 대형 블로그는:
- 증분 빌드 설정 필요
- On-Demand ISR 고려
- 또는 Astro DB 활용
하지만 대부분의 프로젝트는 이 수준이 아닙니다. :::
4. SSR 사용 시 복잡도
Astro는 SSR(Server-Side Rendering)도 지원하지만:
- 설정이 Next.js보다 복잡
- 에지 함수 지원은 발전 중
- 대부분의 경우 SSG로 충분
결론: Astro로의 전환을 추천하는 이유
제가 Astro를 첫 번째 선택으로 삼은 이유
- 성능이 곧 사용자 경험
- 빠른 사이트 = 높은 전환율
- 100ms 빠를수록 전환율 1% 증가
- Astro로 수백 밀리초 단축 가능
- 개발자 경험이 뛰어남
- 설정 최소화
- 직관적인 API
- 빠른 개발 속도
- 비용 절감
- 무료 호스팅 (Netlify, Vercel, Cloudflare)
- 서버 비용 제로
- CDN 캐싱 최적화
- 미래 지향적
- 웹 표준 준수
- 프레임워크 중립적
- 활발한 커뮤니티
실제 마이그레이션 경험
제 블로그를 Next.js에서 Astro로 마이그레이션했을 때:
- ⏱️ 마이그레이션 시간: 4시간
- 번들 크기 감소: 220KB → 25KB
- Lighthouse 점수: 95 → 100
- 호스팅 비용: $20/월 → $0/월
- 개발자 만족도: ⬆️⬆️⬆️
언제 Astro로 전환해야 하나?
다음 중 하나라도 해당된다면 지금 바로 Astro를 시도해보세요:
- 블로그나 문서 사이트를 만들고 있다
- 성능 최적화에 시간을 많이 쓰고 있다
- Next.js의 복잡성에 피로를 느낀다
- 서버 비용을 절감하고 싶다
- SEO가 중요한 프로젝트다
- Markdown/MDX를 주로 사용한다
시작하는 방법
# 1. 새 프로젝트 생성
npm create astro@latest my-project
# 2. 템플릿 선택 (Blog 추천)
# 3. 타입스크립트? Yes
# 4. npm install? Yes
# 5. 개발 서버 시작
cd my-project
npm run dev
# 끝! http://localhost:4321 접속
마치며
개발자로서 새로운 도구를 배우는 것은 항상 고민입니다. “이 시간을 투자할 가치가 있을까?”
Astro에 대한 제 답은 **“확실히 그렇다”**입니다.
:::important 제 솔직한 의견 이 블로그를 만들면서 Astro가 단순히 빠른 것을 넘어, 개발 과정 자체를 즐겁게 만든다는 것을 느꼈습니다.
- 복잡한 설정에 시간 낭비 없음
- 성능 최적화를 위해 고민할 필요 없음
- 그냥 콘텐츠 작성에 집중
이것이 제가 Astro를 첫 번째 선택으로 삼은 가장 큰 이유입니다. :::