3강. 컴포넌트와 레이아웃으로 반복 없애기

헤더·푸터처럼 모든 페이지에 반복되는 코드를 Astro 컴포넌트와 레이아웃으로 분리하고, props와 slot으로 재사용하는 방법을 배웁니다.

·

Astro 강의 3강 - 컴포넌트와 레이아웃

2강에서 만든 소개 페이지에는 <html>, <head>, <body>가 전부 들어 있었습니다. 페이지가 열 개가 되면 같은 코드를 열 번 복사해야 할까요? 이번 강의에서는 컴포넌트레이아웃으로 반복을 없애는 방법을 배웁니다.

컴포넌트: 재사용 가능한 UI 조각

src/components/ 폴더에 .astro 파일을 만들면 그것이 컴포넌트입니다. 다른 파일에서 HTML 태그처럼 불러 쓸 수 있습니다.

src/components/Header.astro를 만들어 봅니다.

---
const links = [
  { label: '홈', href: '/' },
  { label: '소개', href: '/about' },
  { label: '블로그', href: '/blog' },
];
---

<header>
  <a href="/" class="logo">My Site</a>
  <nav>
    {links.map((link) => <a href={link.href}>{link.label}</a>)}
  </nav>
</header>

<style>
  header { display: flex; justify-content: space-between; padding: 1rem; }
  nav a { margin-left: 1rem; }
</style>

<style> 태그 안의 CSS는 이 컴포넌트에만 적용됩니다. Astro가 자동으로 고유한 클래스를 붙여 주기 때문에 다른 곳의 header 스타일과 충돌하지 않습니다. 이것을 스코프드 CSS(scoped CSS)라고 합니다.

컴포넌트 사용하기

페이지에서 import 한 뒤 태그로 씁니다. 컴포넌트 이름은 반드시 대문자로 시작해야 합니다.

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

<Header />
<h1>홈</h1>

Props: 컴포넌트에 값 전달하기

같은 컴포넌트를 조금씩 다르게 쓰고 싶을 때 props를 사용합니다. Astro.props로 전달받은 값을 꺼냅니다.

src/components/Card.astro:

---
interface Props {
  title: string;
  description?: string;   // ? 는 선택 사항이라는 뜻
}
const { title, description = '설명 없음' } = Astro.props;
---

<div class="card">
  <h3>{title}</h3>
  <p>{description}</p>
</div>

사용하는 쪽:

<Card title="빠른 속도" description="필요한 JS만 전송합니다." />
<Card title="쉬운 문법" />

interface Props를 적어 두면 잘못된 이름의 값을 넘겼을 때 편집기가 바로 알려줍니다. 필수는 아니지만 습관을 들이면 실수가 크게 줄어듭니다.

레이아웃: 페이지의 공통 뼈대

레이아웃은 “내용만 바뀌는 컴포넌트”입니다. <html>, <head>, 헤더, 푸터를 레이아웃에 넣어 두고, 페이지마다 바뀌는 본문 자리는 <slot />으로 비워 둡니다.

src/layouts/BaseLayout.astro:

---
import Header from '../components/Header.astro';
import Footer from '../components/Footer.astro';

interface Props {
  title: string;
}
const { title } = Astro.props;
---

<html lang="ko">
  <head>
    <meta charset="utf-8" />
    <meta name="viewport" content="width=device-width" />
    <title>{title} | My Site</title>
  </head>
  <body>
    <Header />
    <main>
      <slot />   <!-- 페이지의 내용이 이 자리에 들어갑니다 -->
    </main>
    <Footer />
  </body>
</html>

이제 2강의 소개 페이지는 이렇게 짧아집니다.

---
import BaseLayout from '../layouts/BaseLayout.astro';
---

<BaseLayout title="소개">
  <h1>소개 페이지</h1>
  <p>Astro를 배우고 있습니다.</p>
</BaseLayout>

<BaseLayout> 태그 사이에 넣은 내용이 레이아웃의 <slot /> 자리에 그대로 들어갑니다. 헤더나 푸터를 고치고 싶으면 레이아웃 파일 하나만 수정하면 모든 페이지에 반영됩니다.

이름 있는 slot

한 레이아웃에 여러 빈자리가 필요하면 slot에 이름을 붙입니다.

<!-- 레이아웃 -->
<aside><slot name="sidebar" /></aside>
<main><slot /></main>

<!-- 페이지 -->
<BaseLayout title="문서">
  <nav slot="sidebar">목차</nav>
  <p>본문</p>
</BaseLayout>

전역 CSS 적용하기

컴포넌트의 <style>은 해당 파일에만 적용되므로, 글꼴이나 기본 색상처럼 사이트 전체에 쓸 스타일은 src/styles/global.css에 작성하고 레이아웃에서 불러옵니다.

---
import '../styles/global.css';
---

레이아웃의 스크립트 영역에 이 한 줄만 넣으면 그 레이아웃을 쓰는 모든 페이지에 적용됩니다.

정리

  • 컴포넌트는 src/components/에 두고 태그처럼 사용합니다. <style>은 해당 컴포넌트에만 적용됩니다.
  • props로 값을 전달하고, interface Props로 타입을 적어 두면 실수를 줄일 수 있습니다.
  • 레이아웃은 <slot />으로 본문 자리를 비워 둔 공통 뼈대입니다. 헤더·푸터 수정은 레이아웃 한 곳에서 끝납니다.

다음 강의에서는 마크다운 파일로 글을 쓰고 자동으로 블로그 페이지를 만들어 주는 콘텐츠 컬렉션을 배웁니다.