Next.js 最高ですよね. SSG, SSR, ISR をパパっと実装できるし, バックエンドからフロントエンドまで一貫して開発できるし, TypeScript もサポートしてくれます. Prisma との連携は素晴らしいし, React 資源も豊富なので爆速でフルスタックアプリケーションを開発&デプロイできてしまいます.

これだけ褒めちぎっているにもかかわらず, 私が Next.js から Astro に刷新した理由を書いていきます.

Next.js をやめた理由

SPA である必要がない

公然の事実として SPA は FCP(First Contentful Paint)が遅いです. SPA は初回アクセス時に JavaScript を読み込む必要があるため, JavaScript の読み込みが完了するまでに時間がかかります.

その問題を解決するために Next.js では SSR/SSG/ISR などのアプローチを取る訳です. しかし, そもそも React DOM や next/dist がそこそこ重く, それに加えて MUI などのコンポーネントライブラリを使用するとバンドルサイズは一気に大きくなります.

これでは結局 SPA の問題は解決できません.

ビルド時間の問題

記事の数が少ない現段階ではあまり問題になりませんが, Next.js の SSG はかなりビルドに時間がかかります. Next.js で 2 分ほどかかるビルドを Astro で 10 秒ほどに短縮できました. 後のことを考えるとビルド時間の短縮は大事です.

Astro を採用した理由

コンテンツファースト

Astro はコンテンツファーストのフレームワークです. ブログは複雑なアプリケーションではないので, Astro を採用することで他のフレームワークと比較して圧倒的なパフォーマンスを得ることができます. 一方ネイティブアプリケーションのような複雑なアプリケーションを開発する場合は Next.js などのフレームワークを採用するべきと考えます.

Jamstack の廃止

当ブログは元々 StrapiMicroCMS などの Headless CMS を使用し, API 経由で記事の取得とページの生成を行っていました. いわゆる Jamstack です.

しかし, Headless CMS で記事を書くより, VSCode の補完機能を活用して Markdown で記事を書く方が素早く記事を書くことができます. また, 記事の管理も Git で行うことができるので, 記事のバージョン管理もできます.

以上の理由から

  1. 記事をMarkdownで書く
  2. Git で記事を管理する
  3. Astroで記事をビルドする

というような方式を採用しました. (昔ながらの方式!)

また Astro にはShikiprismが組み込みでサポートされているため, コードブロックのシンタックスハイライトも設定不要で実現できます.

Astro アイランド

恐らくAstro Island (Component Island)という言葉を聞いたことがない方が殆どでしょう. Astro Islandの採用によって殆どのコンテンツは静的な HTML に変換され, JavaScriptコンポーネント単位で必要に応じて読み込まれます. 詳しくは公式ドキュメントを参照してください.

以下は Astro で作成したブログを読み込んだときのネットワークの状況です(もちろんキャッシュは無効化しています). ネイティブな Astro コンポーネント のみでページを生成した場合, クライアントサイドではJavaScript は一切使用されません. 画像等のリソースを除いた場合, ページのサイズはなんと5kBほどしかありません!

image

コードの可読性が高い

個人的な感想ですが, React(jsx/tsx)と比較してコードの可読性が高いと感じました. VueSvelteと似たような感じで書けます. 以下にコードの一例を示します.

記事一覧を表示しているページの実際のコードは以下の通りです.

---
import ArticleCard from "@/components/ArticleCard.astro";
import Container from "@/components/Container.astro";
import Document from "@/layouts/Document.astro";
import Stack from "@/components/Stack.astro";
import Hero from "@/components/Hero.astro";

import { getSummaries } from "@/utils/article.astro";

const summaries = await getSummaries();
---

<!-- HTML -->
<Document title="Blog">
  <Hero title="Tech Blog🚀" />
  <Container marginY={2}>
    <h2 style="margin-bottom: 1rem;">記事一覧</h2>
    <Stack>
      {summaries.map((summary) => <ArticleCard summary={summary} />)}
    </Stack>
  </Container>
</Document>

MUIで言うところのStackのようなレイアウト用のコンポーネントは以下の通りです.

---
interface Props {
  gap?: number;
}

const { gap = 1 } = Astro.props;
const vars = {
  gap: `${gap}rem`,
};
---

<!-- HTML -->
<div class="stack">
  <slot />
</div>

<!-- CSS -->
<style define:vars={vars}>
  .stack {
    display: flex;
    flex-direction: column;
    gap: var(--gap);
  }
</style>

---で囲まれた Markdown の frontmatter のような部分はサーバーサイドで実行されるコードで, TypeScriptのように書けます. その下にHTMLCSSを書いていく感じですね.

デフォルトではCSSスコープされるので, CSS Modulesのような感じです. props で渡された値を使ってCSSを変更することもできます.

まとめ

今回は Next.js から Astro に刷新した理由と変更点をまとめました.

  • SPA である必要がない
  • ビルド時間 の問題
  • Astro のコンテンツファーストな設計
  • Jamstack から Markdown + SSG への移行
  • Astro IslandによるJavaScript の最適化
  • Astro のコード可読性 が高い

Astro はコンテンツファーストなフレームワークであり, 静的な HTML に変換され, JavaScriptコンポーネント単位で必要に応じて読み込まれます. また, コードの可読性も高く, VueSvelteと似たような感じで書けます.

ユースケースによりますがブログやコーポレートサイトの開発はAstro, 複雑なアプリケーションの開発はNext.jsを使って行こうと思います.

それではまた次回 👋