Closed10

AstroとNext.jsのSSGを比較する

jimbojimbo

コンテンツ主体のWebサイトをJamstackで作成する場合のStatic Site GeneratorとしてAstroNext.jsの2つを比較してみる。

  • どちらもミニマムなページ + 1000ページのサイトで比較する。
  • Next.jsはStatic Generationを利用する。
jimbojimbo

準備編

まず、Headless CMSの代わりとして、簡単なAPIを用意する。

Honoを使ってCloudflare Workersに公開する。(めちゃくちゃ簡単だった)

import { Hono } from 'hono';
const app = new Hono();

app.get('/', (c) => {
  return c.text('Hello!');
});

app.get('/:id', (c) => {
  const id = c.req.param('id');
  const body = Math.random().toString(32).substring(2);
  return c.json({ id, body });
});

export default app;

https://dummy-data-api.xxxxx.workers.dev/1 にアクセスすると次のようなレスポンスが返ってくるようになった。

{"id":"1","body":"q142g79884"}
jimbojimbo

Next.jsバージョン。 getStaticProps の中でAPIを叩いてデータを取得する。

pages/posts/[id].tsx
import { GetStaticPaths, GetStaticProps } from "next";
import Head from 'next/head';

interface Props {
  id: number;
  body: string;
}

export default function Page({ id, body }: Props) {
  return (
    <>
      <Head>
        <title>{body}</title>
      </Head>
      <small>Next.js version</small>
      <h1>id: {id}</h1>
      <h2>body: {body}</h2>
    </>
  )
}

export const getStaticPaths: GetStaticPaths = async () => {
  // 1000ページ分のpathを用意
  const paths = [...Array(1000).keys()].map((i) => {
    return {
      params: { id: (i + 1).toString() }
    }
  });
  return { paths, fallback: false }
}

export const getStaticProps: GetStaticProps = async ({ params }) => {
  const response = await fetch(`https://dummy-data-api.xxxxx.workers.dev/${params?.id}`);
  const { id, body } = await response.json();
  
  return { props: { id: data?.id, body: data?.body } }
}
jimbojimbo

Astroバージョン。frontmatterの中でAPIを叩いてデータを取得する。frontmatterの中はすべてビルド時にのみ実行されるため、考えることが少なく楽に書ける印象。getStaticPaths はNext.jsとほぼ同じ。

src/pages/posts/[id].astro
---
import Layout from "../../layouts/Layout.astro";

export function getStaticPaths() {
  return [[...Array(1000).keys()].map((i) => {
    return {
      params: { id: i + 1 }
    }
  })]
}
const { id } = Astro.params;
const response = await fetch(`https://dummy-data-api.xxxxx.workers.dev/${id}`);
const { body } = await response.json();
---

<Layout title={body || 'Not found'}>
  <small>Astro version</small>
  <h1>id: {id}</h1>
  <h2>body: {body || 'Not found'}</h2>
</Layout>
jimbojimbo

ビルドして http://localhost:3000/posts/1 にアクセスするとそれぞれ次のようなページができあがる。

Next.js Astro
jimbojimbo

ビルド時間の比較

hyperfineを使ってそれぞれのビルド処理のベンチマークを計測してみた。

ビルドはNext.jsのほうが圧倒的に早い。2倍の差が出た。

Next.js

❯ hyperfine 'npm run build'
Benchmark 1: npm run build
  Time (mean ± σ):      9.675 s ±  0.483 s    [User: 17.264 s, System: 2.603 s]
  Range (min … max):    9.048 s … 10.555 s    10 runs

Astro

❯ hyperfine 'npm run build'
Benchmark 1: npm run build
  Time (mean ± σ):     22.342 s ±  0.535 s    [User: 7.807 s, System: 1.424 s]
  Range (min … max):   21.574 s … 23.284 s    10 runs
jimbojimbo

生成されたページのパフォーマンス

Astroは読み込むファイル数が圧倒的に少なく、Largest Contentful Paint (LCP) も短い。Next.jsはやはりHydrationの分、Astroよりも不利になる。

Next.js

Astro

jimbojimbo

どちらを使うか?

生成されるページのパフォーマンスを比べるとAstroがかなり魅力的に映る。コンテンツ主体のWebサイトであればNext.jsを採用する必要性はあまりないし、Astroを試したいところ。

懸念はビルド時間だが、AstroでSSGする場合の個人的ベストプラクティス - console.lealog(); にあるように、先にデータを一括でAPI取得しておいて、ビルド時にはそのデータを使うようにすることで、現実的な時間に軽減できそう。

jimbojimbo

事前に取得したデータを使うようにしてみた

Astroのビルドめちゃくちゃ早くなった

Next.js

❯ hyperfine 'npm run build'
Benchmark 1: npm run build
  Time (mean ± σ):      8.884 s ±  1.386 s    [User: 17.152 s, System: 2.717 s]
  Range (min … max):    7.423 s … 11.599 s    10 runs

Astro

❯ hyperfine 'npm run build'
Benchmark 1: npm run build
  Time (mean ± σ):      3.672 s ±  0.227 s    [User: 4.295 s, System: 0.830 s]
  Range (min … max):    3.177 s …  3.918 s    10 runs
このスクラップは2022/12/26にクローズされました