Astro

最新のIslands Architectureを採用した革新的SSG。必要なJavaScriptのみを送信し、超高速なサイトを生成。

言語:JavaScript/TypeScript
フレームワーク:Multi-framework
ビルド速度:Fast
GitHub Stars:38k
初回リリース:2021
人気ランキング:第4位

トレンド・動向

次世代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>&copy; 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> ```