シリーズ目次 / 前章: 03. .astroコンポーネントの基本 / 次章: 05. Astroのファイルベースルーティング
Table of contents
Open Table of contents
この章のねらい
ヘッダー、フッター、<head> のメタ情報。全ページで同じものを毎回書くのは無駄ですし、直し漏れが必ず出ます。
そこで使うのがレイアウトです。あわせて、見た目を整えるスタイリングの基本も見ていきます。
レイアウトコンポーネントとは
レイアウトの正体は、ページ全体の外枠を担当するただの .astro コンポーネントです。専用の仕組みが用意されているわけではありません。慣例として src/layouts/ に置きます。
---
// src/layouts/BaseLayout.astro
interface Props {
title: string;
description?: string;
}
const { title, description = "サイトの説明" } = Astro.props;
---
<html lang="ja">
<head>
<meta charset="utf-8" />
<meta name="viewport" content="width=device-width" />
<title>{title}</title>
<meta name="description" content={description} />
</head>
<body>
<header>
<nav>
<a href="/">ホーム</a>
<a href="/about/">このサイトについて</a>
</nav>
</header>
<main>
<slot />
</main>
<footer>
<p>© 2026</p>
</footer>
</body>
</html>
<html> から </html> までをレイアウトが丸ごと持ちます。個々のページは <slot /> に入る中身だけを書けば済みます。
使う側のページはこうなります。
---
// src/pages/about.astro
import BaseLayout from "../layouts/BaseLayout.astro";
---
<BaseLayout title="このサイトについて">
<h1>このサイトについて</h1>
<p>Astroの学習記録を公開しています。</p>
</BaseLayout>
レイアウトを入れ子にする
ページの種類が増えると、「全ページ共通の部分」と「記事ページだけ共通の部分」を分けたくなります。そんなときは、レイアウトを入れ子にします。
---
// src/layouts/BlogPostLayout.astro
import BaseLayout from "./BaseLayout.astro";
interface Props {
title: string;
pubDate: Date;
}
const { title, pubDate } = Astro.props;
---
<BaseLayout title={title}>
<article>
<h1>{title}</h1>
<time datetime={pubDate.toISOString()}>
{pubDate.toLocaleDateString("ja-JP")}
</time>
<slot />
</article>
</BaseLayout>
BlogPostLayout は、自分の <slot /> で受け取った内容を <article> で包み、それをさらに BaseLayout に渡しています。こうすることで、共通部分の重複をなくしながら、ページ種別ごとの装飾を追加できます。
Markdownからレイアウトを指定する
Markdownファイルをページとして使う場合、フロントマターの layout プロパティにレイアウトのパスを書くだけで適用できます。
---
layout: ../../layouts/BlogPostLayout.astro
title: "はじめての記事"
pubDate: 2026-08-02
---
ここが本文です。Markdownで書けます。
レイアウト側では、Markdownのフロントマターを Astro.props.frontmatter から取得できます。
---
// src/layouts/BlogPostLayout.astro
const { frontmatter } = Astro.props;
---
<h1>{frontmatter.title}</h1>
<slot />
ただし、記事をまとまった単位で管理するなら、フロントマターの型チェックが効くコンテンツコレクションを使うほうが安全です。詳しくは06. Astro コンテンツコレクションで記事を管理するで扱います。
スタイリングの基本:スコープ付きCSS
Astroの <style> タグに書いたCSSは、既定でそのコンポーネント内だけに適用されます。これをスコープ付きCSS(scoped CSS)と呼びます。
<h1>タイトル</h1>
<style>
h1 {
color: crimson;
}
</style>
この h1 { color: crimson; } は、同じファイルの <h1> にしか効きません。他のコンポーネントの <h1> は影響を受けません。
CSSは書けば書くほど、どこに影響するのか分からなくなります。Astroは既定でこの問題を封じました。セレクタの命名規則を工夫しなくても衝突しない。地味ですが、これがいちばん効きます。
内部的には、Astroが要素に一意の属性を付け、セレクタにその属性条件を追加することで実現しています。
グローバルに適用したいとき
リセットCSSやサイト全体の基本スタイルなど、あえて全体に効かせたいものもあります。その場合は is:global を付けます。
<style is:global>
body {
margin: 0;
font-family: system-ui, sans-serif;
}
</style>
または、外部のCSSファイルをインポートする方法もあります。インポートしたCSSはグローバルに適用されます。
---
import "../styles/global.css";
---
サイト全体に効かせたいスタイルは、レイアウトコンポーネントで一度インポートしておくのが定番です。
スコープ付きとグローバルで、効く範囲は次のように変わります。
JavaScriptの値をCSSに渡す:define:vars
コンポーネントスクリプトで計算した値をCSSで使いたいときは、define:vars ディレクティブを使います。指定した値がCSSカスタムプロパティ(CSS変数)として利用可能になります。
---
const accentColor = "rgb(24 121 78)";
const columns = 3;
---
<div class="grid">
<slot />
</div>
<style define:vars={{ accentColor, columns }}>
.grid {
display: grid;
grid-template-columns: repeat(var(--columns), 1fr);
border-top: 4px solid var(--accentColor);
}
</style>
Propsで受け取った値をそのまま見た目に反映できるため、「色違いのカード」「幅の違うレイアウト」といったバリエーションを素直に表現できます。
Sassなどのプリプロセッサ
lang 属性を付けると、SassやSCSS、Lessなども使えます(対応するパッケージのインストールは必要です)。
<style lang="scss">
$primary: crimson;
.title {
color: $primary;
&:hover {
opacity: 0.8;
}
}
</style>
Tailwind CSSを使う
ユーティリティクラスでスタイルを当てるTailwind CSSも利用できます。Astro 5.2以降はTailwind 4に対応しており、次のコマンドで導入できます。
npx astro add tailwind
導入後は、グローバルCSSでTailwindを読み込みます。
/* src/styles/global.css */
@import "tailwindcss";
あとはレイアウトでこのCSSをインポートすれば、各コンポーネントでユーティリティクラスを使えます。
<h1 class="text-3xl font-bold text-slate-800">タイトル</h1>
スコープ付きCSSとTailwindは併用できます。基本はTailwindで書き、込み入った部分だけ <style> に逃がす。自分はこの分け方に落ち着きました。
クラス名を条件で組み立てる:class:list
条件によってクラスを付け外ししたいときは、class:list ディレクティブが便利です。配列やオブジェクトを渡すと、Astroが適切な文字列に組み立ててくれます。
---
interface Props {
isActive?: boolean;
size?: "sm" | "lg";
}
const { isActive = false, size = "sm" } = Astro.props;
---
<button class:list={["btn", `btn-${size}`, { "is-active": isActive }]}>
<slot />
</button>
オブジェクトで渡した項目は、値が真のときだけクラスとして出力されます。文字列連結や三項演算子を重ねるより読みやすくなります。
同じ記述でも、条件の値によって出力されるクラス名が変わります。
スタイルの優先順位
複数の指定が競合したときの強さは、公式ドキュメントによると次の順です。
<head>内の<link>タグで読み込んだCSS(最も弱い)- インポートしたCSS
- スコープ付きCSS(最も強い)
コンポーネント固有の指定が共通スタイルに負けないようになっています。ただし同じ強さで競合したら、あとは通常のCSSの詳細度ルール次第です。ここが複雑になってきたら、設計を見直す合図です。
要点
- レイアウトは特別な仕組みではなく、外枠を担当する普通の
.astroコンポーネントである。 - レイアウトを入れ子にすると、「全体共通」と「種別ごと共通」を分離できる。
<style>は既定でスコープ付きになり、クラス名の衝突を気にせず書ける。- 全体に効かせたいときは
is:globalか外部CSSのインポートを使う。 define:varsでJavaScriptの値をCSS変数として渡せる。Tailwindもastro add tailwindで導入できる。
参考資料
シリーズ目次 / 前章: 03. .astroコンポーネントの基本 / 次章: 05. Astroのファイルベースルーティング