WordPress 개발자를 위한 Astro

이 페이지

Astro는 콘텐츠 중심 웹사이트를 위한 웹 프레임워크입니다. EmDash와 함께 쓰면 Astro가 WordPress 테마를 대신해—템플릿, 라우팅, 렌더링을 담당합니다.

이 가이드는 이미 이해하고 있는 WordPress 개념에 맞춰 Astro 기초를 설명합니다.

핵심 패러다임 전환

기본은 서버 렌더링

PHP처럼 Astro 코드는 서버에서 실행됩니다. PHP와 달리 기본적으로 JavaScript 없이 정적 HTML을 출력합니다.

넣지 않으면 JS 없음

WordPress는 jQuery와 테마 스크립트를 자동으로 불러옵니다. Astro는 명시적으로 추가하지 않으면 브라우저로 아무것도 보내지 않습니다.

컴포넌트 기반 아키텍처

흩어진 템플릿 태그와 include 대신, 조합 가능하고 스스로 완결된 컴포넌트로 만듭니다.

파일 기반 라우팅

리라이트 규칙이나 query_vars 없이 src/pages/ 파일 구조가 URL을 직접 정의합니다.

프로젝트 구조

WordPress 테마는 “마법 같은” 파일명을 가진 평평한 구조인 경우가 많습니다. Astro는 명시적인 디렉터리를 씁니다.

WordPressAstro역할
index.php, single.phpsrc/pages/라우트(URL)
template-parts/src/components/재사용 UI 조각
header.php + footer.phpsrc/layouts/페이지 래퍼
style.csssrc/styles/전역 CSS
functions.phpastro.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 템플릿에 해당합니다. 파일은 두 부분으로 나뉩니다.

  1. 프론트매터(--- 울타리 사이)—템플릿 상단 PHP와 같은 서버 코드
  2. 템플릿—PHP 템플릿의 나머지처럼 표현식이 있는 HTML
---
// 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; ?>

표현식 패턴

Pattern목적
{variable}값 출력
{condition && <Element />}조건부 렌더링
{condition ? <A /> : <B />}If/else
{items.map(item => <Li>{item}</Li>)}반복

Props와 슬롯

컴포넌트는 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>

사용:

<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;
---

슬롯 vs 훅

WordPress는 do_action()으로 삽입 지점을 만듭니다. Astro는 슬롯을 씁니다.

WordPressAstro
do_action('before_content')<slot name="before" />
Default content area<slot />
do_action('after_content')<slot name="after" />

차이: 슬롯은 호출 위치에서 자식 요소를 받고, WordPress 훅은 다른 곳의 add_action() 호출이 필요합니다.

레이아웃

레이아웃은 <head>, 헤더, 푸터 등 페이지 간 공통 HTML로 페이지를 감쌉니다. 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>&copy; {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는 여러 스타일 방식을 제공합니다. 가장 특징적인 것은 scoped 스타일입니다.

Scoped 스타일

<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>

스크립트는 묶이고 중복도 제거됩니다. 컴포넌트가 페이지에 두 번 있어도 스크립트는 한 번 실행됩니다.

고급: 인터랙티브 컴포넌트

더 복잡한 상호작용은 React, Vue, Svelte 등을 필요할 때 로드할 수 있습니다. 선택 사항이며 대부분 <script>만으로 충분합니다.

---
import SearchWidget from "../components/SearchWidget.jsx";
---
<!-- Only load JavaScript when the search box scrolls into view -->
<SearchWidget client:visible />
DirectiveJavaScript가 로드되는 시점
client:load페이지 로드 직후
client:visible컴포넌트가 뷰포트에 들어올 때
client:idle브라우저가 유휴일 때

라우팅

Astro는 파일 기반 라우팅을 씁니다. src/pages/의 파일이 URL이 됩니다.

FileURL
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].astroAny path (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와 비교

WordPressAstro
Template hierarchy (single-post.php)Explicit file: posts/[slug].astro
Rewrite rules + query_varsFile structure
$wp_query determines templateURL maps directly to file
add_rewrite_rule()Create files or folders

WordPress 개념은 어디에 해당하나

WordPress 기능의 Astro/EmDash 대응을 찾는 참고표입니다.

템플릿

WordPressAstro/EmDash
Template hierarchyFile-based routing in src/pages/
get_template_part()Import and use components
the_content()<PortableText value={content} />
the_title(), the_*()Access via post.data.title
Template tagsTemplate expressions {value}
body_class()class:list directive

데이터와 쿼리

WordPressAstro/EmDash
WP_QuerygetEmDashCollection(type, filters)
get_post()getEmDashEntry(type, id)
get_posts()getEmDashCollection(type)
get_the_terms()Access via entry.data.categories
get_post_meta()Access via entry.data.fieldName
get_option()getSiteSettings()
wp_nav_menu()getMenu(location)

확장성

WordPressAstro/EmDash
add_action()EmDash hooks, Astro middleware
add_filter()EmDash hooks
add_shortcode()Portable Text custom blocks
register_block_type()Portable Text custom blocks
register_sidebar()EmDash widget areas
PluginsAstro integrations + EmDash plugins

콘텐츠 타입

WordPressAstro/EmDash
register_post_type()Create collection in admin UI
register_taxonomy()Create taxonomy in admin UI
register_meta()Add field to collection schema
Post statusEntry status (draft, published, etc.)
Featured imageMedia reference field
Gutenberg blocksPortable Text blocks

요약

WordPress에서 Astro로의 도약은 크지만 논리적입니다.

  1. PHP 템플릿 → Astro 컴포넌트 — 같은 아이디어(서버 코드 + HTML), 더 나은 구조
  2. 템플릿 태그 → props와 import — 전역 대신 명시적 데이터 흐름
  3. 테마 파일 → pages 디렉터리 — URL이 파일 구조와 일치
  4. 훅 → 슬롯과 미들웨어 — 더 예측 가능한 삽입 지점
  5. 기본 jQuery → 기본 JS 없음 — 상호작용은 의도적으로 추가

첫 EmDash 사이트는 Getting Started 가이드로 시작하거나, CMS 데이터 조회·렌더링은 Working with Content를 참고하세요.