Astro는 콘텐츠 중심 웹사이트를 구축하기 위한 웹 프레임워크입니다. EmDash를 사용할 때 Astro는 WordPress 테마를 대체합니다 — 템플릿, 라우팅 및 렌더링을 처리합니다.
이 가이드는 이미 이해하고 있는 WordPress 개념에 매핑하여 Astro 기초를 가르칩니다.
주요 패러다임 전환
기본적으로 서버 렌더링
PHP처럼 Astro 코드는 서버에서 실행됩니다. PHP와 달리 제로 JavaScript로 기본적으로 정적 HTML을 출력합니다.
추가하지 않으면 제로 JS
WordPress는 jQuery와 테마 스크립트를 자동으로 로드합니다. Astro는 명시적으로 추가하지 않으면 브라우저에 아무것도 전송하지 않습니다.
컴포넌트 기반 아키텍처
흩어진 템플릿 태그와 include 대신, 조합 가능하고 자체 포함된 컴포넌트로 구축하세요.
파일 기반 라우팅
리라이트 규칙이나 query_vars가 없습니다. src/pages/의 파일 구조가 URL을 직접 정의합니다.
프로젝트 구조
WordPress 테마는 마법의 파일 이름을 가진 평평한 구조를 가지고 있습니다. Astro는 명시적인 디렉토리를 사용합니다:
| WordPress | Astro | 목적 |
|---|---|---|
index.php, single.php | src/pages/ | 라우트 (URLs) |
template-parts/ | src/components/ | 재사용 가능한 UI 조각 |
header.php + footer.php | src/layouts/ | 페이지 래퍼 |
style.css | src/styles/ | 글로벌 CSS |
functions.php | astro.config.mjs | 사이트 설정 |
다음 트리는 일반적인 Astro 프로젝트 레이아웃을 보여줍니다:
src/
├── components/ # Reusable UI (Header, PostCard, etc.)
├── layouts/ # Page shells (Base.astro)
├── pages/ # Routes - files become URLs
│ ├── index.astro # → /
│ ├── posts/
│ │ ├── index.astro # → /posts
│ │ └── [slug].astro # → /posts/hello-world
│ └── [slug].astro # → /about, /contact, etc.
└── styles/
└── global.css
Astro 컴포넌트
.astro 파일은 PHP 템플릿에 해당하는 Astro의 것입니다. 각 파일에는 두 부분이 있습니다:
- 프론트매터 (
---펜스 사이) — 템플릿 상단의 PHP처럼 서버 측 코드 - 템플릿 — PHP 템플릿의 나머지처럼 표현식이 있는 HTML
다음 컴포넌트는 프론트매터에서 타입이 지정된 props를 선언하고 템플릿에서 렌더링합니다:
---
// Frontmatter: runs on server, never sent to browser
interface Props {
title: string;
excerpt: string;
url: string;
}
const { title, excerpt, url } = Astro.props;
---
<!-- Template: outputs HTML -->
<article class="post-card">
<h2><a href={url}>{title}</a></h2>
<p>{excerpt}</p>
</article>
PHP와의 주요 차이점:
- 프론트매터는 분리되어 있습니다. 거기에 선언된 변수는 템플릿에서 사용할 수 있지만, 코드 자체는 브라우저에 도달하지 않습니다.
- import는 프론트매터에 들어갑니다. 컴포넌트, 데이터, 유틸리티 — 모두 상단에서 import됩니다.
- TypeScript가 작동합니다. 편집기 자동 완성 및 검증을 위해
interface Props로 prop 타입을 정의하세요.
템플릿 표현식
Astro 템플릿은 <?php ?> 태그 대신 {중괄호}를 사용합니다. 구문은 JSX와 유사하지만 순수 HTML을 출력합니다.
Astro
---
import { getEmDashCollection } from "emdash";
const { entries: posts } = await getEmDashCollection("posts");
const showTitle = true;
---
{showTitle && <h1>Latest Posts</h1>}
{posts.length > 0 ? (
<ul>
{posts.map(post => (
<li>
<a href={`/posts/${post.id}`}>{post.data.title}</a>
</li>
))}
</ul>
) : (
<p>No posts found.</p>
)} PHP
<?php
$posts = new WP_Query(['post_type' => 'post']);
$show_title = true;
?>
<?php if ($show_title): ?>
<h1>Latest Posts</h1>
<?php endif; ?>
<?php if ($posts->have_posts()): ?>
<ul>
<?php while ($posts->have_posts()): $posts->the_post(); ?>
<li>
<a href="<?php the_permalink(); ?>"><?php the_title(); ?></a>
</li>
<?php endwhile; wp_reset_postdata(); ?>
</ul>
<?php else: ?>
<p>No posts found.</p>
<?php endif; ?> 표현식 패턴
| 패턴 | 목적 |
|---|---|
{variable} | 값 출력 |
{condition && <Element />} | 조건부 렌더링 |
{condition ? <A /> : <B />} | If/else |
{items.map(item => <Li>{item}</Li>)} | 루프 |
Props와 Slots
컴포넌트는 props (함수 인자처럼)와 slots (do_action 삽입 지점처럼)를 통해 데이터를 받습니다.
Astro
---
interface Props {
title: string;
featured?: boolean;
}
const { title, featured = false } = Astro.props;
---
<article class:list={["card", { featured }]}>
<h2>{title}</h2>
<slot />
<slot name="footer" />
</article>다음 마크업은 해당 컴포넌트를 사용하여 기본 슬롯과 이름이 지정된 footer 슬롯을 전달합니다:
<Card title="Hello" featured>
<p>This goes in the default slot.</p>
<footer slot="footer">Footer content</footer>
</Card> PHP
<?php
// Usage: get_template_part('template-parts/card', null, [
// 'title' => 'Hello',
// 'featured' => true
// ]);
$title = $args['title'] ?? '';
$featured = $args['featured'] ?? false;
$class = $featured ? 'card featured' : 'card';
?>
<article class="<?php echo esc_attr($class); ?>">
<h2><?php echo esc_html($title); ?></h2>
<?php
// No direct equivalent to slots.
// WordPress uses do_action() for similar patterns:
do_action('card_content');
do_action('card_footer');
?>
</article> Props vs $args
WordPress에서 get_template_part()는 $args 배열을 통해 데이터를 전달합니다. Astro props는 타입이 지정되고 구조 분해됩니다:
---
// Type-safe with defaults
interface Props {
title: string;
count?: number;
}
const { title, count = 10 } = Astro.props;
---
Slots vs Hooks
WordPress는 삽입 지점을 만들기 위해 do_action()을 사용합니다. Astro는 슬롯을 사용합니다:
| WordPress | Astro |
|---|---|
do_action('before_content') | <slot name="before" /> |
| 기본 콘텐츠 영역 | <slot /> |
do_action('after_content') | <slot name="after" /> |
차이점: 슬롯은 호출 사이트에서 자식 요소를 받지만, WordPress 훅은 다른 곳에서 별도의 add_action() 호출이 필요합니다.
레이아웃
레이아웃은 공통 HTML 구조로 페이지를 래핑합니다 — <head>, 헤더, 푸터 및 페이지 간에 공유되는 모든 것. 이것은 header.php + footer.php를 대체합니다. 다음 레이아웃은 공유 셸을 정의하고 페이지 콘텐츠를 위한 슬롯을 노출합니다:
---
import "../styles/global.css";
interface Props {
title: string;
description?: string;
}
const { title, description = "My EmDash Site" } = Astro.props;
---
<!doctype html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<meta name="description" content={description} />
<title>{title}</title>
</head>
<body>
<header>
<nav><!-- Navigation --></nav>
</header>
<main>
<slot />
</main>
<footer>
<p>© {new Date().getFullYear()}</p>
</footer>
</body>
</html>
페이지에서 레이아웃을 사용하세요:
---
import Base from "../layouts/Base.astro";
---
<Base title="Home">
<h1>Welcome</h1>
<p>Page content goes in the slot.</p>
</Base>
스타일링
Astro는 여러 스타일링 접근 방식을 제공합니다. 가장 특징적인 것은 스코프 스타일입니다.
스코프 스타일
<style> 태그의 스타일은 자동으로 해당 컴포넌트로 스코프됩니다:
<article class="card">
<h2>Title</h2>
</article>
<style>
/* Only affects .card in THIS component */
.card {
padding: 1rem;
border: 1px solid #ddd;
}
h2 {
color: navy;
}
</style>
생성된 HTML에는 스타일 유출을 방지하기 위한 고유한 클래스 이름이 포함되어 있으므로 선택자 특이성을 높이지 않고도 컴포넌트 스타일이 포함된 상태로 유지됩니다.
글로벌 스타일
사이트 전체 스타일의 경우 CSS 파일을 만들어 레이아웃에 import하세요:
---
import "../styles/global.css";
---
조건부 클래스
class:list 지시문은 수동 클래스 문자열 구축을 대체합니다:
Astro
---
const { featured, size = "medium" } = Astro.props;
---
<article class:list={[
"card",
size,
{ featured, "has-border": true }
]}>출력: <article class="card medium featured has-border">
PHP
<?php
$classes = ['card', $size];
if ($featured) $classes[] = 'featured';
if (true) $classes[] = 'has-border';
?>
<article class="<?php echo esc_attr(implode(' ', $classes)); ?>"> 클라이언트 측 JavaScript
Astro는 기본적으로 제로 JavaScript를 전송합니다. 이것이 WordPress로부터의 가장 큰 정신적 전환입니다.
상호작용성 추가
간단한 상호작용을 위해 <script> 태그를 추가하세요:
<button id="menu-toggle">Menu</button>
<nav id="mobile-menu" hidden>
<slot />
</nav>
<script>
const toggle = document.getElementById("menu-toggle");
const menu = document.getElementById("mobile-menu");
toggle?.addEventListener("click", () => {
menu?.toggleAttribute("hidden");
});
</script>
스크립트는 자동으로 번들링되고 중복 제거됩니다. 이 컴포넌트가 페이지에 두 번 나타나면 스크립트는 한 번 실행됩니다.
고급 상호작용 컴포넌트
더 복잡한 상호작용을 위해 Astro는 필요에 따라 JavaScript 컴포넌트(React, Vue, Svelte)를 로드할 수 있습니다. 이것은 선택 사항입니다 — 대부분의 사이트는 <script> 태그만으로도 잘 작동합니다. 다음 페이지는 뷰로 스크롤될 때만 컴포넌트를 로드합니다:
---
import SearchWidget from "../components/SearchWidget.jsx";
---
<!-- Only load JavaScript when the search box scrolls into view -->
<SearchWidget client:visible />
| 지시문 | JavaScript가 로드되는 시점 |
|---|---|
client:load | 페이지 로드 시 즉시 |
client:visible | 컴포넌트가 뷰포트에 들어올 때 |
client:idle | 브라우저가 유휴 상태일 때 |
라우팅
Astro는 파일 기반 라우팅을 사용합니다. src/pages/의 파일이 URL이 됩니다:
| 파일 | URL |
|---|---|
src/pages/index.astro | / |
src/pages/about.astro | /about |
src/pages/posts/index.astro | /posts |
src/pages/posts/[slug].astro | /posts/hello-world |
src/pages/[...slug].astro | 모든 경로 (catch-all) |
동적 라우트
CMS 콘텐츠의 경우 동적 세그먼트에 대괄호 구문을 사용하세요:
---
import { getEmDashCollection, getEmDashEntry } from "emdash";
import Base from "../../layouts/Base.astro";
import { PortableText } from "emdash/ui";
// For static builds, define which pages to generate
export async function getStaticPaths() {
const { entries: posts } = await getEmDashCollection("posts");
return posts.map(post => ({
params: { slug: post.id },
props: { post },
}));
}
const { post } = Astro.props;
---
<Base title={post.data.title}>
<article>
<h1>{post.data.title}</h1>
<PortableText value={post.data.content} />
</article>
</Base>
WordPress와 비교
| WordPress | Astro |
|---|---|
템플릿 계층 구조 (single-post.php) | 명시적 파일: posts/[slug].astro |
리라이트 규칙 + query_vars | 파일 구조 |
$wp_query가 템플릿을 결정 | URL이 파일에 직접 매핑 |
add_rewrite_rule() | 파일 또는 폴더 생성 |
WordPress 개념이 존재하는 위치
WordPress 기능의 Astro/EmDash 동등물을 찾기 위한 참고 자료:
템플릿
| WordPress | Astro/EmDash |
|---|---|
| 템플릿 계층 구조 | src/pages/에서 파일 기반 라우팅 |
get_template_part() | 컴포넌트 import 및 사용 |
the_content() | <PortableText value={content} /> |
the_title(), the_*() | post.data.title을 통한 접근 |
| 템플릿 태그 | 템플릿 표현식 {value} |
body_class() | class:list 지시문 |
데이터 및 쿼리
| WordPress | Astro/EmDash |
|---|---|
WP_Query | getEmDashCollection(type, filters) |
get_post() | getEmDashEntry(type, id) |
get_posts() | getEmDashCollection(type) |
get_the_terms() | entry.data.categories를 통한 접근 |
get_post_meta() | entry.data.fieldName을 통한 접근 |
get_option() | getSiteSettings() |
wp_nav_menu() | getMenu(location) |
확장성
| WordPress | Astro/EmDash |
|---|---|
add_action() | EmDash 훅, Astro 미들웨어 |
add_filter() | EmDash 훅 |
add_shortcode() | Portable Text 커스텀 블록 |
register_block_type() | Portable Text 커스텀 블록 |
register_sidebar() | EmDash 위젯 영역 |
| 플러그인 | Astro 통합 + EmDash 플러그인 |
콘텐츠 타입
| WordPress | Astro/EmDash |
|---|---|
register_post_type() | 관리 UI에서 컬렉션 생성 |
register_taxonomy() | 관리 UI에서 분류법 생성 |
register_meta() | 컬렉션 스키마에 필드 추가 |
| 게시물 상태 | 항목 상태 (draft, published, 등) |
| 특집 이미지 | 미디어 참조 필드 |
| Gutenberg 블록 | Portable Text 블록 |
개념 매핑
이 가이드에서 다룬 주요 WordPress에서 Astro로의 전환:
- PHP 템플릿은 Astro 컴포넌트가 됩니다: 명시적 파일 조직과 함께 서버 코드와 HTML.
- 템플릿 태그는 props와 import가 됩니다: 데이터는 전역 대신 인자를 통해 흐릅니다.
- 테마 파일은 pages 디렉토리가 됩니다: URL이 파일 구조와 일치합니다.
- 훅은 슬롯과 미들웨어가 됩니다: 삽입 지점은 콘텐츠가 전달되는 곳에서 정의됩니다.
- jQuery는 WordPress에서 기본적으로 로드됩니다; Astro는 추가할 때까지 JavaScript를 전송하지 않습니다.
첫 번째 EmDash 사이트를 구축하려면 Getting Started 가이드로 시작하거나, Working with Content를 탐색하여 CMS 데이터를 쿼리하고 렌더링하는 방법을 배우세요.