Astro 5.0がリリース - Content Layer APIとServer Islandsで進化

2025.12.02

Astro 5.0の革新

Astro 5.0は、コンテンツ駆動型Webサイト構築フレームワークの新たなマイルストーンです。Content Layer APIによる柔軟なデータソース統合と、Server Islandsによる選択的SSRが大きな特徴です。

flowchart TB
    subgraph ContentLayer["Content Layer API (New)"]
        Local["Local Markdown"]
        CMS["CMS<br/>(Contentful)"]
        API["API<br/>(REST)"]
        Database["Database<br/>(Drizzle)"]
        Collections["Unified Type-Safe Collections"]

        Local --> Collections
        CMS --> Collections
        API --> Collections
        Database --> Collections
    end

    subgraph Rendering["Rendering"]
        SSG["SSG<br/>(静的)"]
        Islands["Server Islands<br/>(部分的SSR)"]
        SSR["Full SSR<br/>(動的)"]
    end

    Collections --> Rendering

Content Layer API

新しいコンテンツ定義

// src/content.config.ts
import { defineCollection, z } from 'astro:content';
import { glob, file } from 'astro/loaders';

// ローカルファイルからのコレクション
const blog = defineCollection({
  loader: glob({ pattern: '**/*.{md,mdx}', base: './src/content/blog' }),
  schema: z.object({
    title: z.string(),
    description: z.string(),
    publishDate: z.coerce.date(),
    tags: z.array(z.string()),
    draft: z.boolean().default(false),
  }),
});

// JSONファイルからのコレクション
const authors = defineCollection({
  loader: file('./src/data/authors.json'),
  schema: z.object({
    name: z.string(),
    email: z.string().email(),
    avatar: z.string().url(),
    bio: z.string(),
  }),
});

export const collections = { blog, authors };

カスタムローダーの作成

// src/loaders/cms-loader.ts
import { Loader } from 'astro/loaders';

export function contentfulLoader(options: {
  spaceId: string;
  accessToken: string;
  contentType: string;
}): Loader {
  return {
    name: 'contentful-loader',
    async load({ store, logger }) {
      logger.info('Fetching content from Contentful...');

      const response = await fetch(
        `https://cdn.contentful.com/spaces/${options.spaceId}/entries?content_type=${options.contentType}`,
        {
          headers: {
            Authorization: `Bearer ${options.accessToken}`,
          },
        }
      );

      const data = await response.json();

      for (const item of data.items) {
        store.set({
          id: item.sys.id,
          data: {
            title: item.fields.title,
            body: item.fields.body,
            slug: item.fields.slug,
            publishedAt: item.sys.createdAt,
          },
        });
      }

      logger.info(`Loaded ${data.items.length} entries`);
    },
  };
}

// src/content.config.ts での使用
import { contentfulLoader } from './loaders/cms-loader';

const articles = defineCollection({
  loader: contentfulLoader({
    spaceId: import.meta.env.CONTENTFUL_SPACE_ID,
    accessToken: import.meta.env.CONTENTFUL_ACCESS_TOKEN,
    contentType: 'article',
  }),
  schema: z.object({
    title: z.string(),
    body: z.string(),
    slug: z.string(),
    publishedAt: z.coerce.date(),
  }),
});

Server Islands

Server Islandsは、静的ページ内に動的なサーバーレンダリングコンポーネントを埋め込む機能です。

flowchart TB
    subgraph StaticHTML["静的HTML (キャッシュ可能)"]
        Header["Header (Static)"]

        subgraph MainContent["Main Content"]
            Article["Article Content<br/>(Static)"]
            subgraph ServerIsland["Server Island"]
                UserInfo["ユーザー情報<br/>(動的)"]
                Comments["コメント数<br/>(動的)"]
            end
        end

        Footer["Footer (Static)"]
    end

    Header --- MainContent
    MainContent --- Footer

Server Islandは遅延ロード、フォールバック表示可能

実装例

---
// src/components/UserProfile.astro
export const prerender = false; // Server Islandとして設定

import { getUser } from '../lib/auth';

const user = await getUser(Astro.cookies);
---

{user ? (
  <div class="user-profile">
    <img src={user.avatar} alt={user.name} />
    <span>{user.name}</span>
    <a href="/logout">ログアウト</a>
  </div>
) : (
  <a href="/login" class="login-button">ログイン</a>
)}
---
// src/pages/blog/[slug].astro
import { getCollection, getEntry } from 'astro:content';
import UserProfile from '../../components/UserProfile.astro';
import CommentSection from '../../components/CommentSection.astro';

export const prerender = true; // 静的生成

export async function getStaticPaths() {
  const posts = await getCollection('blog');
  return posts.map((post) => ({
    params: { slug: post.id },
    props: { post },
  }));
}

const { post } = Astro.props;
const { Content } = await post.render();
---

<html>
  <head>
    <title>{post.data.title}</title>
  </head>
  <body>
    <header>
      <!-- Server Island: 動的なユーザー情報 -->
      <UserProfile server:defer>
        <div slot="fallback">読み込み中...</div>
      </UserProfile>
    </header>

    <main>
      <article>
        <h1>{post.data.title}</h1>
        <Content />
      </article>

      <!-- Server Island: 動的なコメント -->
      <CommentSection server:defer postId={post.id}>
        <div slot="fallback">コメントを読み込み中...</div>
      </CommentSection>
    </main>
  </body>
</html>

astro:env モジュール

環境変数の型安全なアクセスを提供する新しいモジュールです。

// astro.config.mjs
import { defineConfig, envField } from 'astro/config';

export default defineConfig({
  env: {
    schema: {
      // パブリック変数(クライアントでも利用可能)
      PUBLIC_API_URL: envField.string({
        context: 'client',
        access: 'public',
        default: 'https://api.example.com',
      }),

      // シークレット変数(サーバーのみ)
      DATABASE_URL: envField.string({
        context: 'server',
        access: 'secret',
      }),

      // オプショナル変数
      ANALYTICS_ID: envField.string({
        context: 'client',
        access: 'public',
        optional: true,
      }),

      // 数値型
      CACHE_TTL: envField.number({
        context: 'server',
        access: 'public',
        default: 3600,
      }),

      // 列挙型
      NODE_ENV: envField.enum({
        values: ['development', 'production', 'test'],
        context: 'server',
        access: 'public',
        default: 'development',
      }),
    },
  },
});
// 使用例
import { PUBLIC_API_URL, ANALYTICS_ID } from 'astro:env/client';
import { DATABASE_URL, CACHE_TTL } from 'astro:env/server';

// 型安全にアクセス可能
const apiUrl: string = PUBLIC_API_URL;
const analyticsId: string | undefined = ANALYTICS_ID;
const dbUrl: string = DATABASE_URL; // サーバーでのみ利用可能

Vite 6 サポート

Astro 5.0はVite 6をデフォルトで採用し、ビルドパフォーマンスが向上しました。

ビルドパフォーマンス比較 (1000ページのサイト)

バージョンビルド時間メモリ使用開発サーバー起動
Astro 4.x (Vite 5)45秒512MB2.1秒
Astro 5.0 (Vite 6)32秒 (29% faster)380MB (26% reduction)1.4秒 (33% faster)

その他の新機能

改善されたTypeScript推論

// コレクションの型が自動推論される
import { getCollection } from 'astro:content';

const posts = await getCollection('blog');
// posts: Array<{ id: string; data: BlogSchema; ... }>

// フィルタリングも型安全
const publishedPosts = await getCollection('blog', ({ data }) => {
  return data.draft !== true; // data.draft は boolean | undefined
});

実験的機能

// astro.config.mjs
export default defineConfig({
  experimental: {
    // SVGコンポーネントサポート
    svg: true,

    // レスポンシブ画像の自動生成
    responsiveImages: true,

    // Content Intellisense
    contentIntellisense: true,
  },
});
---
// SVGコンポーネントとして使用
import Logo from '../assets/logo.svg';
---

<Logo class="w-32 h-32" fill="currentColor" />

マイグレーションガイド

v4からv5への移行

# Astro CLIでアップグレード
npx @astrojs/upgrade

主な破壊的変更

v4の機能v5での変更
src/content/config.tssrc/content.config.ts に移動
getCollection() の戻り値body プロパティが render() に移行
旧形式のコンテンツコレクションContent Layer APIへ移行推奨
Astro.glob()getCollection() を推奨
// v4: 旧方式
const { Content } = await entry.render();
const body = entry.body; // 直接アクセス

// v5: 新方式
const { Content } = await entry.render();
// body は render() 経由でのみアクセス

参考リンク

← 一覧に戻る