Astro 가이드

블로그를 만들 때마다 “이 작은 사이트에 React가 정말 필요할까?” 하는 생각이 든 적 있나요? 글 몇 개 보여주는데 수백 KB의 JavaScript를 다운로드해야 한다는 게 비효율적으로 느껴지더라고요.

Astro는 이런 고민에서 출발한 웹 프레임워크입니다. 블로그, 마케팅 페이지, 포트폴리오처럼 콘텐츠를 보여주는 것이 주목적인 사이트를 만들 때, 불필요한 JavaScript를 배제하고 빠른 로딩 속도와 뛰어난 SEO에 집중합니다.

왜 Astro를 배워야 할까요?

콘텐츠 중심 웹사이트를 만든다면 Astro가 탁월한 선택지입니다. 특히 성능과 SEO가 중요한 프로젝트에서 빛을 발합니다.

주요 사용 사례

전통적인 방식                          Astro
─────────────────────────────────────────────────────────────
블로그 (React + Next.js)           →   기본적으로 정적 HTML 생성
마케팅 페이지 (SPA)                →   0 JS 기본, 필요할 때만 추가
문서 사이트 (Gatsby)               →   빠른 빌드, 작은 번들 크기
포트폴리오 (Vue + Nuxt)            →   다양한 프레임워크 혼용 가능
이커머스 (무거운 JS)               →   상품 페이지는 정적, 장바구니만 동적

흔한 오해와 실제

  • ❌ “정적 사이트 생성기는 기능이 제한적이지 않나?” → Astro는 필요한 곳에만 JavaScript를 추가할 수 있습니다
  • ❌ “React를 포기해야 하나?” → React, Vue, Svelte 등 원하는 프레임워크를 함께 사용 가능
  • ❌ “동적 기능은 못 만들지?” → Islands Architecture로 인터랙티브 컴포넌트를 독립적으로 추가

Astro란?

콘텐츠 중심 웹사이트를 위한 올인원 웹 프레임워크입니다. JavaScript 오버헤드를 줄이기 위해 “Islands Architecture“라는 새로운 프런트엔드 아키텍처를 도입했습니다.

핵심 특징

1. 제로 JavaScript 기본값

Astro는 기본적으로 JavaScript를 클라이언트로 보내지 않습니다. 모든 컴포넌트를 빌드 타임에 HTML로 렌더링합니다.

---
// 이 코드는 빌드 타임에만 실행됩니다
const posts = await fetchPosts();
---

<ul>
  {posts.map(post => (
    <li>{post.title}</li>
  ))}
</ul>

결과:

  • 브라우저로 전송되는 것: 순수 HTML
  • JavaScript 번들 크기: 0 KB
  • 렌더링 속도: 즉시 표시

2. Islands Architecture

페이지의 대부분은 정적 HTML이고, 필요한 부분만 인터랙티브하게 만드는 아키텍처입니다.

전통적인 SPA              Islands Architecture
┌─────────────────┐      ┌─────────────────┐
│                 │      │  정적 HTML       │
│    전체가         │      │  ┌───────┐      │
│   JavaScript    │  →   │  │Island1│      │
│                 │      │  └───────┘      │
│                 │      │  정적 HTML       │
│                 │      │      ┌────────┐ │
└─────────────────┘      │      │Island2 │ │
   전체 무거움              └──────┴────────┘─┘
                             필요한 곳만 무거움

실제 예시:

<!-- 정적 헤더 -->
<header>
  <h1>My Blog</h1>
</header>

<!-- 인터랙티브한 검색 바 -->
<SearchBar client:load />

<!-- 정적 컨텐츠 -->
<article>
  <p>블로그 글 내용...</p>
</article>

<!-- 인터랙티브한 댓글 섹션 -->
<Comments client:visible />

이 페이지에서 JavaScript가 필요한 부분은 SearchBar와 Comments뿐입니다. 나머지는 순수 HTML로 제공됩니다.

3. UI 프레임워크 무관

React, Vue, Svelte, Preact 등 원하는 프레임워크를 자유롭게 선택할 수 있습니다. 심지어 하나의 페이지에서 여러 프레임워크를 섞어 쓸 수도 있습니다.

---
import ReactCounter from './ReactCounter.jsx';
import VueCalendar from './VueCalendar.vue';
import SvelteChart from './SvelteChart.svelte';
---

<ReactCounter client:load />
<VueCalendar client:visible />
<SvelteChart client:idle />

왜 이렇게 할까요?

  • 기존 컴포넌트를 재사용하고 싶을 때
  • 팀마다 선호하는 프레임워크가 다를 때
  • 점진적으로 프레임워크를 전환하고 싶을 때

먼저, 기초부터 이해하기

Astro를 이해하려면 먼저 왜 기존 프레임워크가 무거워졌는지 알아야 합니다.

전통적인 SPA의 문제점

React나 Vue로 만든 Single Page Application은 모든 페이지를 JavaScript로 렌더링합니다.

// 브라우저가 해야 할 일
1. HTML 다운로드 (거의 비어있음)
2. JavaScript 번들 다운로드 (수백 KB~ MB)
3. JavaScript 파싱  실행
4. React/Vue가 DOM을 생성
5. 화면에 콘텐츠 표시 👈 여기서야 사용자가 뭔가   있음

문제:

  • 느린 네트워크에서는 흰 화면이 오래 지속
  • 검색 엔진 봇은 JavaScript 실행이 제한적
  • 단순히 글을 읽는데도 수백 KB 다운로드

Astro의 접근 방식

Astro는 빌드 타임에 모든 것을 HTML로 만들어 둡니다.

// 브라우저가 해야 할 일
1. HTML 다운로드 (완전한 콘텐츠 포함)
2. 화면에 콘텐츠 표시 👈 즉시!
3. (선택적) 인터랙티브 컴포넌트만 JavaScript 로드

장점:

  • 즉시 First Contentful Paint
  • 검색 엔진이 완전한 HTML을 바로 읽음
  • 필요한 경우에만 JavaScript 로드

개념 비교

측면 전통적인 SPA Astro
렌더링 시점 런타임 (브라우저) 빌드 타임 (서버)
JavaScript 번들 항상 필요 선택적
초기 로딩 느림 (JS 파싱 필요) 빠름 (HTML만)
SEO 추가 설정 필요 기본적으로 최적
사용 사례 인터랙티브 앱 콘텐츠 중심 사이트

시작하기

설치 및 프로젝트 생성

# 새 프로젝트 생성
npm create astro@latest

# 프로젝트 디렉토리로 이동
cd my-astro-site

# 개발 서버 실행
npm run dev

대화형 설치 과정에서 다음을 선택할 수 있습니다:

  • 템플릿 (빈 프로젝트, 블로그, 포트폴리오 등)
  • TypeScript 사용 여부
  • Git 초기화 여부
  • 의존성 자동 설치 여부

프로젝트 구조

my-astro-site/
├── src/
│   ├── components/     # 재사용 가능한 컴포넌트
│   ├── layouts/        # 페이지 레이아웃
│   ├── pages/          # 페이지 (파일 기반 라우팅)
│   └── styles/         # CSS 파일
├── public/             # 정적 파일 (이미지, 폰트 등)
├── astro.config.mjs    # Astro 설정
└── package.json

핵심 개념:

  • src/pages/: 이 폴더의 파일이 자동으로 라우트가 됩니다
    • pages/index.astro/
    • pages/about.astro/about
    • pages/blog/post.astro/blog/post

Astro 컴포넌트 작성하기

Astro 컴포넌트는 .astro 확장자를 사용하며, 세 부분으로 구성됩니다.

기본 구조

---
// 1. 컴포넌트 스크립트 (빌드 타임에만 실행)
const title = "Hello, Astro!";
const items = ['Apple', 'Banana', 'Cherry'];
---

<!-- 2. 컴포넌트 템플릿 (HTML + 표현식) -->
<div>
  <h1>{title}</h1>
  <ul>
    {items.map(item => <li>{item}</li>)}
  </ul>
</div>

<style>
  /* 3. 스코프 스타일 (이 컴포넌트에만 적용) */
  h1 {
    color: blue;
  }
</style>

중요한 점:

  • --- 사이의 코드는 빌드 타임에만 실행됩니다
  • 클라이언트 JavaScript가 아니므로 window, document 같은 브라우저 API 사용 불가
  • 데이터 페칭, 파일 읽기 등 서버 작업에 적합

데이터 페칭

---
// fetch는 빌드 타임에 실행됩니다
const response = await fetch('https://api.github.com/repos/withastro/astro');
const data = await response.json();
---

<div>
  <h1>{data.name}</h1>
  <p>⭐ {data.stargazers_count}</p>
  <p>{data.description}</p>
</div>

어떻게 동작할까요?

  1. 빌드 시 API 호출
  2. 응답 데이터를 HTML에 삽입
  3. 완성된 HTML을 정적 파일로 저장
  4. 사용자가 페이지 방문 → 이미 완성된 HTML 즉시 표시

Props 전달하기

---
// src/components/Card.astro
interface Props {
  title: string;
  description: string;
  url?: string;
}

const { title, description, url } = Astro.props;
---

<div class="card">
  <h2>{title}</h2>
  <p>{description}</p>
  {url && <a href={url}>Learn more →</a>}
</div>

<style>
  .card {
    border: 1px solid #ddd;
    padding: 1rem;
    border-radius: 8px;
  }
</style>

사용:

---
import Card from '../components/Card.astro';
---

<Card
  title="Astro"
  description="콘텐츠 중심 웹사이트를 위한 프레임워크"
  url="https://astro.build"
/>

Client Directives - 인터랙티브 컴포넌트 만들기

Astro에서 JavaScript를 클라이언트로 보내려면 client directive를 사용합니다.

사용 가능한 Directives

1. client:load - 페이지 로드 즉시

<Counter client:load />

언제 사용?

  • 즉시 보여야 하는 중요한 인터랙션 (메인 네비게이션 등)

2. client:visible - 화면에 보일 때

<Comments client:visible />

언제 사용?

  • 페이지 하단의 댓글, 무한 스크롤, 게으른 로딩이 적절한 곳

어떻게 동작?

  • Intersection Observer API 사용
  • 컴포넌트가 뷰포트에 들어오면 JavaScript 로드

3. client:idle - 브라우저가 한가할 때

<Analytics client:idle />

언제 사용?

  • 중요하지 않은 기능 (분석, 채팅 위젯 등)

어떻게 동작?

  • requestIdleCallback API 사용
  • 메인 스레드가 한가할 때 로드

4. client:only - 서버에서 렌더링 안 함

<Chart client:only="react" />

언제 사용?

  • 브라우저 전용 API를 사용하는 컴포넌트
  • 서버 렌더링이 불가능한 라이브러리

❌ 흔한 실수

<!-- ❌ directive 없이 React 컴포넌트 사용 -->
<Counter />
<!-- 결과: HTML만 렌더링, 클릭해도 동작 안 함 -->

<!-- ✅ client directive와 함께 사용 -->
<Counter client:load />
<!-- 결과: JavaScript가 로드되어 정상 동작 -->

✅ 최적화 팁

<!-- 페이지 상단의 중요한 네비게이션 -->
<Navigation client:load />

<!-- 페이지 중간의 이미지 갤러리 -->
<ImageGallery client:visible />

<!-- 페이지 하단의 분석 스크립트 -->
<Analytics client:idle />

레이아웃 사용하기

레이아웃은 여러 페이지에서 공통으로 사용하는 구조를 정의합니다.

---
// src/layouts/BaseLayout.astro
interface Props {
  title: string;
}

const { title } = Astro.props;
---

<!DOCTYPE html>
<html lang="ko">
<head>
  <meta charset="UTF-8">
  <meta name="viewport" content="width=device-width">
  <title>{title}</title>
</head>
<body>
  <header>
    <nav>
      <a href="/">Home</a>
      <a href="/blog">Blog</a>
      <a href="/about">About</a>
    </nav>
  </header>

  <main>
    <slot /> <!-- 페이지 콘텐츠가 여기에 삽입됩니다 -->
  </main>

  <footer>
    <p>&copy; 2025 My Site</p>
  </footer>
</body>
</html>

사용:

---
// src/pages/about.astro
import BaseLayout from '../layouts/BaseLayout.astro';
---

<BaseLayout title="About">
  <h1>About Me</h1>
  <p>안녕하세요! Astro로 웹사이트를 만들고 있습니다.</p>
</BaseLayout>

함정과 주의사항

콘텐츠 중심 사이트를 만들 때 Astro가 탁월하지만, 몇 가지 주의할 점이 있습니다.

1. 클라이언트 상태 관리의 복잡성

문제:

<!-- ❌ Islands끼리 상태를 공유하기 어려움 -->
<Counter client:load />
<Display client:load />
<!-- 두 컴포넌트가 같은 카운트를 공유하려면? -->

해결책:

// src/store/counter.ts
import { atom } from 'nanostores';

export const count = atom(0);
// React 컴포넌트에서
import { useStore } from '@nanostores/react';
import { count } from '../store/counter';

export default function Counter() {
  const $count = useStore(count);
  return (
    <button onClick={() => count.set($count + 1)}>
      Count: {$count}
    </button>
  );
}

언제 문제가 될까요?

  • 여러 컴포넌트가 복잡한 상태를 공유해야 할 때
  • 실시간 동기화가 필요한 인터랙티브 앱

대안:

  • 복잡한 인터랙티브 앱이라면 Next.js나 Remix 고려
  • Astro는 콘텐츠가 주, 인터랙션이 부가적인 경우에 적합

2. 빌드 타임 vs 런타임 혼동

문제:

---
// ❌ 이 코드는 브라우저에서 실행되지 않습니다
const now = new Date();
---

<p>현재 시간: {now.toLocaleString()}</p>
<!-- 결과: 빌드 시점의 시간이 표시됨, 페이지 새로고침해도 변하지 않음 -->

해결책:

<p id="current-time"></p>

<script>
  // ✅ 이 코드는 브라우저에서 실행됩니다
  function updateTime() {
    const now = new Date();
    document.getElementById('current-time').textContent =
      `현재 시간: ${now.toLocaleString()}`;
  }

  updateTime();
  setInterval(updateTime, 1000);
</script>

3. 대용량 사이트 빌드 시간

문제:

  • 수천 개의 페이지를 가진 사이트는 빌드가 오래 걸릴 수 있음
  • 예: 10,000개 페이지 → 10분 이상 빌드 시간

해결책:

// astro.config.mjs
export default defineConfig({
  // 증분 빌드 활성화
  experimental: {
    contentCollections: true,
  },

  // 병렬 빌드 최적화
  vite: {
    build: {
      rollupOptions: {
        output: {
          manualChunks: (id) => {
            if (id.includes('node_modules')) {
              return 'vendor';
            }
          }
        }
      }
    }
  }
});

언제 문제가 될까요?

  • 뉴스 사이트, 대규모 전자상거래
  • 매일 수백 개 콘텐츠가 추가되는 경우

대안:

  • SSR 모드 활용
  • Astro의 하이브리드 렌더링 (일부 페이지만 정적 생성)

Astro vs 다른 프레임워크

Astro vs Next.js

측면 Astro Next.js
렌더링 기본값 정적 (SSG) 하이브리드 (SSG + SSR)
JavaScript 기본값 0 KB React 번들 포함
사용 사례 콘텐츠 중심 풀스택 앱
러닝 커브 낮음 중간
서버 기능 제한적 풍부함 (API Routes, Middleware)

언제 Astro?

  • 블로그, 문서 사이트, 마케팅 페이지
  • 성능이 최우선
  • 인터랙션이 제한적

언제 Next.js?

  • 인증, 데이터베이스 연동 등 서버 기능 필요
  • 복잡한 클라이언트 상태 관리
  • 실시간 데이터 업데이트

Astro vs Gatsby

측면 Astro Gatsby
빌드 속도 매우 빠름 느림 (대규모 사이트)
GraphQL 선택사항 필수
플러그인 생태계 성장 중 성숙함
JavaScript 번들 선택적 React 포함

Gatsby에서 Astro로 마이그레이션 이유:

  • 빌드 시간 단축 (10분 → 1분)
  • 복잡한 GraphQL 쿼리 제거
  • 번들 크기 감소

마이그레이션 고려사항

기존 사이트를 Astro로 전환하려면 다음을 확인하세요.

Next.js에서 Astro로

적합한 경우:

  • 대부분의 페이지가 정적 콘텐츠
  • API Routes를 사용하지 않음
  • getStaticProps 위주의 데이터 페칭

마이그레이션 체크리스트:

✓ 페이지를 .astro 파일로 변환
✓ getStaticProps 로직을 컴포넌트 스크립트로 이동
✓ 동적 라우팅 구조 유지 (파일 기반 라우팅 동일)
✓ 인터랙티브 컴포넌트에 client:* directive 추가
✓ API Routes는 별도 백엔드나 Netlify Functions로 대체

React/Vue SPA에서 Astro로

적합한 경우:

  • SEO가 중요하지만 SSR 설정이 복잡
  • 대부분의 콘텐츠가 정적
  • 클라이언트 상태가 복잡하지 않음

마이그레이션 전략:

1. 정적 페이지부터 시작
   - About, Contact 등 단순 페이지를 .astro로 변환

2. 동적 컴포넌트는 기존 프레임워크 유지
   - React 컴포넌트를 그대로 가져와서 client:load 추가

3. 점진적으로 .astro 컴포넌트로 전환
   - 불필요한 JavaScript 제거

실무 활용 사례

1. 기술 블로그 (Astro 공식 블로그)

문제:

  • Gatsby로 구축했지만 빌드가 5분 이상 소요
  • 단순히 글을 보여주는데 React 번들이 과도

해결:

  • Astro로 마이그레이션
  • 빌드 시간: 5분 → 30초
  • JavaScript 번들: 200KB → 0KB (인터랙티브 검색만 10KB)

2. 마케팅 랜딩 페이지

요구사항:

  • Lighthouse 점수 95+ 필수
  • 인터랙티브 요소: 네비게이션, 폼 검증, 애니메이션

구현:

<!-- 대부분 정적 HTML -->
<Hero />
<Features />
<Pricing />

<!-- 인터랙티브 폼만 JavaScript -->
<ContactForm client:visible />

결과:

  • Lighthouse Performance: 100
  • FCP: 0.5초
  • TTI: 1.2초

3. 전자상거래 상품 페이지

전략:

  • 상품 설명, 이미지 갤러리: 정적 HTML
  • 장바구니 추가, 리뷰: 인터랙티브 컴포넌트
---
const product = await fetchProduct(Astro.params.id);
---

<!-- 정적 SEO 콘텐츠 -->
<ProductInfo {...product} />
<ImageGallery images={product.images} />

<!-- 인터랙티브 기능 -->
<AddToCart productId={product.id} client:visible />
<Reviews productId={product.id} client:idle />

다음 단계

Astro의 기초를 배웠다면, 다음을 탐색해보세요:

  1. Content Collections - 타입 안전한 콘텐츠 관리
  2. SSR 모드 - 서버 사이드 렌더링으로 동적 데이터 처리
  3. View Transitions API - 페이지 전환 애니메이션
  4. Integrations - Tailwind, MDX, Partytown 등

참고 자료


핵심 정리:

  • Astro는 콘텐츠 중심 웹사이트에 최적화된 프레임워크입니다
  • 기본적으로 0 JS, 필요한 곳에만 선택적으로 추가
  • Islands Architecture로 성능과 인터랙티브함을 모두 확보
  • 블로그, 문서, 마케팅 페이지, 포트폴리오에 탁월한 선택

댓글