Astro
最新のIslands Architectureを採用した革新的SSG。必要なJavaScriptのみを送信し、超高速なサイトを生成。
トレンド・動向
次世代SSGとして急速に注目を集める。部分的ハイドレーションにより従来のSSGを凌ぐパフォーマンスを実現。
# 静的サイトジェネレータ
Astro
## 概要
AstroはIslands Architectureを採用した革新的な静的サイトジェネレータです。「ゼロJavaScriptがデフォルト」の設計思想により、必要最小限のJavaScriptのみを送信し、圧倒的な高速性を実現。React、Vue、Svelte、Solidなど複数のUIフレームワークを同一プロジェクト内で併用可能で、部分ハイドレーションによる最適化により従来のSSGを凌ぐパフォーマンスを提供。コンテンツ重視のWebサイト構築において次世代のソリューションとして急速に注目を集めています。
## 詳細
Astro 2025年版は、先進的なIslands Architectureにより新しいフロントエンド開発パラダイムを確立しています。サーバーサイドで高速な静的HTMLを生成し、インタラクティブな要素のみを「島(Island)」として選択的にハイドレーションする革新的なアプローチにより、従来のSSGが抱えるJavaScript肥大化問題を根本的に解決。最新のServer Islands機能により、重い処理を遅延レンダリングで最適化し、型安全な環境変数管理システムも統合されています。
### 主な特徴
- **Islands Architecture**: 必要な部分のみをハイドレーションする革新的アーキテクチャ
- **ゼロJavaScriptデフォルト**: 明示的に指定した部分のみJavaScriptを送信
- **マルチフレームワーク対応**: React、Vue、Svelte等を同一プロジェクトで併用
- **Server Islands**: 重い処理の遅延レンダリングによる高速化
- **部分ハイドレーション**: `client:load`、`client:visible`等の細かな制御
- **型安全環境変数**: クライアント・サーバーコンテキストでの安全な変数管理
## メリット・デメリット
### メリット
- 圧倒的な高速性:ゼロJavaScriptデフォルトによる最高クラスの初期表示速度
- 優れたSEO性能:静的HTML生成によるクローラビリティとパフォーマンス最適化
- フレームワーク柔軟性:既存のReact/Vue/Svelteコンポーネントを再利用可能
- Islands Architectureによる効率的な部分ハイドレーション制御
- Core Web Vitals指標の大幅改善とコンバージョン率向上効果
- 学習コストの低さ:HTMLライクな記述でフレームワーク未経験者も習得容易
### デメリット
- 大規模SPAには不向き:クライアントサイド重要アプリケーションに制約
- リアルタイム機能の実装複雑性:WebSocket等の動的機能に追加設定必要
- エコシステムの発展途上:Next.js/Gatsbyと比較してプラグイン・ツール不足
- フレームワーク固有の学習要素:Islands概念やディレクティブの理解必要
- 複雑な状態管理の制約:グローバル状態管理に別途ソリューション必要
- エンタープライズサポートの限定性:商用サポート体制が他SSGより小規模
## 参考ページ
- [Astro 公式サイト](https://astro.build/)
- [Astro ドキュメント](https://docs.astro.build/)
- [Astro GitHub リポジトリ](https://github.com/withastro/astro)
## 書き方の例
### インストールとプロジェクト作成
```bash
# Astroプロジェクトの新規作成(対話式)
npm create astro@latest
# プロジェクト名を指定して作成
npm create astro@latest my-astro-site
# Yarnを使用する場合
yarn create astro
# pnpmを使用する場合
pnpm create astro@latest
# 既存ディレクトリに作成
npm create astro@latest .
# プロジェクトフォルダに移動
cd my-astro-site
# 依存関係のインストール
npm install
# 開発サーバーの起動
npm run dev
```
### 基本的なAstroコンポーネント
```astro
---
// コンポーネントスクリプト(サーバーサイド実行)
interface Props {
title: string;
description?: string;
}
const { title, description = "デフォルトの説明" } = Astro.props;
const currentTime = new Date().toLocaleString('ja-JP');
---
<!-- コンポーネントテンプレート(HTML + JS式) -->
<article class="card">
<header>
<h2>{title}</h2>
<time>{currentTime}</time>
</header>
{description && (
<p class="description">{description}</p>
)}
<slot /> <!-- 子コンテンツの挿入場所 -->
</article>
<style>
.card {
border: 1px solid #e0e0e0;
border-radius: 8px;
padding: 1.5rem;
margin-bottom: 1rem;
background: white;
box-shadow: 0 2px 4px rgba(0,0,0,0.1);
}
.card h2 {
margin: 0 0 0.5rem 0;
color: #2c3e50;
font-size: 1.5rem;
}
.description {
color: #666;
line-height: 1.6;
}
time {
font-size: 0.9rem;
color: #999;
}
</style>
```
### レイアウトコンポーネント
```astro
---
// src/layouts/Layout.astro
import '../styles/global.css';
interface Props {
title: string;
description?: string;
}
const { title, description = "Astroで構築された高速Webサイト" } = Astro.props;
---
<!DOCTYPE html>
<html lang="ja">
<head>
<meta charset="UTF-8" />
<meta name="description" content={description} />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<link rel="icon" type="image/svg+xml" href="/favicon.svg" />
<meta name="generator" content={Astro.generator} />
<title>{title}</title>
</head>
<body>
<header>
<nav>
<a href="/">ホーム</a>
<a href="/about/">概要</a>
<a href="/blog/">ブログ</a>
<a href="/contact/">お問い合わせ</a>
</nav>
</header>
<main>
<slot />
</main>
<footer>
<p>© 2025 Astroサイト. All rights reserved.</p>
</footer>
</body>
</html>
<style>
html {
font-family: 'Helvetica Neue', Arial, 'Hiragino Kaku Gothic ProN', 'Hiragino Sans', Meiryo, sans-serif;
}
header {
background: #4f46e5;
color: white;
padding: 1rem 0;
}
nav {
max-width: 1200px;
margin: 0 auto;
padding: 0 1rem;
display: flex;
gap: 2rem;
}
nav a {
color: white;
text-decoration: none;
font-weight: 500;
}
nav a:hover {
text-decoration: underline;
}
main {
max-width: 1200px;
margin: 0 auto;
padding: 2rem 1rem;
min-height: calc(100vh - 200px);
}
footer {
background: #f8f9fa;
text-align: center;
padding: 2rem;
margin-top: 4rem;
}
</style>
```
### Islands Architecture(部分ハイドレーション)
```astro
---
// src/pages/index.astro
import Layout from '../layouts/Layout.astro';
import Counter from '../components/Counter.jsx'; // Reactコンポーネント
import TodoList from '../components/TodoList.vue'; // Vueコンポーネント
import SearchBox from '../components/SearchBox.svelte'; // Svelteコンポーネント
---
<Layout title="Astro Islands デモ">
<h1>Islands Architecture デモ</h1>
<!-- 静的コンテンツ(JavaScriptなし) -->
<section>
<h2>静的セクション</h2>
<p>このコンテンツは完全に静的でJavaScriptを含みません。</p>
</section>
<!-- インタラクティブなIsland(ページ読み込み時にハイドレーション) -->
<section>
<h2>カウンターコンポーネント</h2>
<Counter client:load initialValue={0} />
</section>
<!-- 画面に表示された時にハイドレーション -->
<section>
<h2>TODOリスト</h2>
<TodoList client:visible />
</section>
<!-- ユーザーのインタラクション時にハイドレーション -->
<section>
<h2>検索ボックス</h2>
<SearchBox client:idle />
</section>
<!-- 特定のメディアクエリ条件でハイドレーション -->
<section>
<h2>モバイル専用コンポーネント</h2>
<Counter client:media="(max-width: 768px)" initialValue={10} />
</section>
</Layout>
```
### 動的ルーティングとSSG
```astro
---
// src/pages/blog/[slug].astro
import Layout from '../../layouts/Layout.astro';
import { getEntry, getCollection } from 'astro:content';
export async function getStaticPaths() {
const blogEntries = await getCollection('blog');
return blogEntries.map((entry) => ({
params: { slug: entry.slug },
props: { entry },
}));
}
const { entry } = Astro.props;
const { Content } = await entry.render();
---
<Layout title={entry.data.title}>
<article>
<header>
<h1>{entry.data.title}</h1>
<time>{entry.data.publishDate.toLocaleDateString('ja-JP')}</time>
<div class="tags">
{entry.data.tags.map((tag: string) => (
<span class="tag">{tag}</span>
))}
</div>
</header>
<Content />
</article>
</Layout>
<style>
article {
max-width: 800px;
margin: 0 auto;
}
header {
margin-bottom: 2rem;
border-bottom: 1px solid #e0e0e0;
padding-bottom: 1rem;
}
h1 {
font-size: 2.5rem;
margin-bottom: 0.5rem;
color: #2c3e50;
}
time {
color: #666;
font-size: 0.9rem;
}
.tags {
margin-top: 1rem;
display: flex;
gap: 0.5rem;
flex-wrap: wrap;
}
.tag {
background: #4f46e5;
color: white;
padding: 0.25rem 0.75rem;
border-radius: 4px;
font-size: 0.8rem;
}
</style>
```
### Server Islands(高度な機能)
```astro
---
// src/components/UserProfile.astro
// Server Island として使用される遅延レンダリングコンポーネント
interface Props {
userId: string;
}
const { userId } = Astro.props;
// 重い処理やAPIコールを含む
async function getUserData(id: string) {
// 外部APIやデータベースからユーザー情報を取得
const response = await fetch(`https://api.example.com/users/${id}`);
return response.json();
}
const userData = await getUserData(userId);
---
<div class="user-profile">
<img src={userData.avatar} alt={userData.name} />
<h3>{userData.name}</h3>
<p>{userData.bio}</p>
<div class="stats">
<span>フォロワー: {userData.followers}</span>
<span>投稿: {userData.posts}</span>
</div>
</div>
<style>
.user-profile {
border: 1px solid #ddd;
padding: 1rem;
border-radius: 8px;
background: white;
}
img {
width: 60px;
height: 60px;
border-radius: 50%;
object-fit: cover;
}
.stats {
display: flex;
gap: 1rem;
font-size: 0.9rem;
color: #666;
}
</style>
```
### UIフレームワーク統合(React例)
```jsx
// src/components/Counter.jsx
import { useState } from 'react';
export default function Counter({ initialValue = 0 }) {
const [count, setCount] = useState(initialValue);
return (
<div className="counter">
<p>現在のカウント: <span className="count">{count}</span></p>
<div className="buttons">
<button onClick={() => setCount(count - 1)}>-</button>
<button onClick={() => setCount(count + 1)}>+</button>
<button onClick={() => setCount(0)}>リセット</button>
</div>
</div>
);
}
```
```astro
---
// src/pages/counter-demo.astro
import Layout from '../layouts/Layout.astro';
import Counter from '../components/Counter.jsx';
---
<Layout title="Counterデモ">
<h1>React Counterコンポーネント</h1>
<!-- 静的レンダリング(JavaScriptなし) -->
<h2>静的カウンター</h2>
<Counter initialValue={5} />
<!-- インタラクティブカウンター -->
<h2>インタラクティブカウンター</h2>
<Counter client:load initialValue={10} />
<!-- 条件付きハイドレーション -->
<h2>遅延ハイドレーション</h2>
<Counter client:visible initialValue={0} />
</Layout>
<style>
.counter {
border: 2px solid #4f46e5;
padding: 1.5rem;
border-radius: 8px;
margin: 1rem 0;
background: #f8f9ff;
}
.count {
font-weight: bold;
font-size: 1.5rem;
color: #4f46e5;
}
.buttons {
display: flex;
gap: 0.5rem;
margin-top: 1rem;
}
.buttons button {
padding: 0.5rem 1rem;
border: 1px solid #4f46e5;
background: white;
color: #4f46e5;
border-radius: 4px;
cursor: pointer;
font-weight: 500;
}
.buttons button:hover {
background: #4f46e5;
color: white;
}
</style>
```