GatsbyからAstro(バージョン7)へ移行しました

GatsbyからAstro(バージョン7)へ移行しました

2026.7.2 2026.7.20

この記事は約2 分で読めます

三好アキ プロフィール写真

筆者三好アキ(専門用語なしでプログラミング)

私について詳しくは → 自己紹介

GatsbyからAstroに移行

先日、本サイトをGatsbyからAstroに移行しました。

バージョンは、6月にリリースされたばかりのAstroバージョン7です。

Lighthouseの「Performance」スコアが「100」になるなど、スピードが大きく上がりました。

astro-lighthouse-score.jpg

日本ではAstroを使っている人がまだ少ないので、少し今回のAstro移行の話を書いていきます。

Gatsbyとの6年間

本サイトでは、事業を立ち上げた2020年からずっとGatsbyを使っていました。

その時のバージョンはv2です。

いま過去のブログを読み返してみると、「Gatsbyのバージョンを2から3に上げた」、そして「3から4に上げた」という記事がそれぞれあります。

3から4に上げたのが2021年11月なので、5年近く何もしていなかったようです。

しかしこのGatsby、2022年リリースのバージョン5で開発が止まっています。

そのため「このままGatsbyを使い続ける」という選択肢はなく、「移行しないといけないな」とはここ数年ずっと思っていました。

が、そこまで強いモチベーションがなかったのも事実。

Gatsbyでも、そこまで危機的に困っていることはありませんでした。

「自サイトの刷新は優先順位が低い」という、よくある話です。


困り事を挙げるとすれば…

• 6年以上運用している中で、内部のコードがスパゲッティ化していたこと

• サイトデザインが一部古くなっていたこと

• Hot reloadの動きが不安定なこと

• ターミナルに依存関係のエラーが出ること

そして、buildに時間がかかること


Gatsbyに個人的な思い入れがあったことも、移行を先延ばししていた間接的な理由だったかもしれません。

初めてGatsbyを触ったとき、ページ遷移のあのスピードには感動しました。

今でも印象深く残っています。

些細なことですが、些細なことが記憶には強く残るものです。

それでも、サイト内のブログ記事を大きく整理したことをきっかけに、Astroへの移行もすることにしました。

Claude Codeで移行

移行作業の大部分で使ったのがClaude Codeです。

とはいえ、丸投げではなく、まずAstroのv7のドキュメントをClaude Codeに読み込ませ、出てきたコードをチェックしながら進める、という流れです。

私は以前Astroを使っていたことがあるので、Astroの基礎部分は知っていました。

しかし今年3月にv6が出て、それにキャッチアップする間もなく6月にv7が出たため、その2つのバージョンで加えられた変更に沿ったコードをClaude Codeに書かせていきました。


本サイトは静的サイトで、特に複雑なロジックはありません。

ベース部分の移行に1、2時間ほど、その確認・修正に数日、といった感じです。

AIツールで本当に開発の景色は変わったなと思います。

手間と時間が大きく短縮されました。

しかしそれは「調べる」、「コードを書く」というマニュアル作業の部分が大幅に減ったということが大きく、成果物の確認やチェックということには、これまでと同じ、あるいはこれまで以上に時間がかかっていると感じます。

以下、Gatsby → Astro移行について、コードと一緒に少し詳しく見ていきましょう。

一番大きく変わったのはデータの扱い方

GatsbyからAstroへの移行で最も大きく変わったのが、データ関係の部分です。

Gatsbyではデータ操作にGraphQLを使っていました。

マークダウンファイルの本文もfrontmatterも、すべてGraphQLのクエリ経由で読み込みます。

例えば全記事のパスと前後記事を取り出すには、gatsby-node.jsの中で次のようなクエリを書いていました。

// gatsby-node.js(Gatsby時代)

exports.createPages = async ({ actions, graphql }) => {
  const { createPage } = actions
  return await graphql(`
    {
      allMarkdownRemark(
        sort: { fields: [frontmatter___id], order: DESC }
        limit: 1000
      ) {
        edges {
          node {
            fields { slug }
            frontmatter { categoryTagJP categoryTagEN }
          }
          next { frontmatter { path title } }
          previous { frontmatter { path title } }
        }
      }
    }
  `).then((res) => {
    // ここで createPage を呼んでページを作る
  })
}

GraphQLは便利ではありますが、frontmatterに項目を1つ足すたび、クエリにも同じ項目を書き足さないといけません。

それを忘れると、データが取れずエラーになります。

allMarkdownRemarkedgesnodeといったGatsby独特の書き方も、久しぶりに触ると思い出すのに時間がかかります。

AstroではContent Collectionsを使うので、GraphQLはまるごと不要になりました。

Content Collectionsでは、まず記事のfrontmatterがどういう形なのかをsrc/content.config.tsで一度だけ定義します。

// src/content.config.ts

import { defineCollection, z } from "astro:content"
import { glob } from "astro/loaders"

const blog = defineCollection({
    loader: glob({ pattern: "**/*.md", base: "./src/content/blog" }),
    schema: ({ image }) =>
        z.object({
            id: z.string(),
            path: z.string(),
            title: z.string(),
            categoryTagJP: z.string(),
            categoryTagEN: z.string(),
            // ...以下、必要な項目を並べていく
        }),
})

export const collections = { blog }

あとは記事一覧を取り出したいところで、getCollection("blog")を呼ぶだけです。

前後記事のリンクも、GraphQLのnextpreviousを使わず、取り出した配列を自分でソートして前後を決められます。

// src/pages/blog/[slug].astro

import { getCollection } from "astro:content"

const posts = await getCollection("blog")

// id順に並べて、前後の記事を決める
const sorted = [...posts].sort(
  (a, b) => Number(a.data.id) - Number(b.data.id)
)

frontmatterに項目を足したいときも、content.config.tsのスキーマに1行足すだけです。

書き忘れれば型エラーで教えてくれるので、Gatsby時代のように「クエリに書き忘れて動かない」ということも起きません。

この部分はAstro移行でラクになった部分のひとつです。

つまずいたのはマークダウン内の画像

Astro移行で少し手間がかかったのが、マークダウンファイル内の画像とHTML記法の扱い方です。

Gatsby時代、私は記事の中に下記のような装飾付きのHTMLを直接書いていました。

<a class="amazon-book-card" href="https://www.amazon.co.jp/dp/...">
    <div class="abc-img">
        <img src="../../images/commonImg/react-book-ts-ai.svg" />
    </div>
</a>

私の本の表紙画像を、Amazonへのリンクカードとして見せるためのもので、HTML記法で画像を表示しています。

画像はsrc内のimagesフォルダに置き、<img>タグの相対パスで読み込んでいました。

Gatsby内のマークダウンファイルでは、この書き方でも正しく表示されます。

一方、Astroではこの書き方が使えません。

Astroでsrc内の画像を使うには、その画像をimportする必要があるからです。

しかしマークダウンファイルでimportは当然使えません。

ここで<img src="../../images/...">のように相対パスで書いても、Astroはビルド時に画像として認識しないため、src以下の画像は表示されません。

かといって、HTMLブロックの中に通常のマークダウンファイルの画像表示の記法![ ](...)を書いても、画像として表示されないのです(下記コード参照)。

<a class="amazon-book-card" href="https://www.amazon.co.jp/dp/...">
    <div class="abc-img">
        ![Book Cover](../../images/commonImg/react-book-ts-ai.svg)
    </div>
</a>

HTMLブロックの中ではマークダウン記法が解釈されないからです。

この解決策はいくつかありました。


• 装飾部分をAstroコンポーネントにして記事から呼ぶ

• 独自記法をHTMLへ変換するremarkプラグインを書く

• 記事を.mdではなく.mdxにする

• 画像をpublicフォルダに移す


まず最初の2つの方法は、すぐに選択肢から消えました。

画像にそんな大袈裟なことをしたくないからです。

そして記事は現状の.mdで管理したいので、MDX化も選択肢から消えます。

最後のpublicフォルダを使う方法。これがもっとも楽です。

しかし少しためらいがあった理由は、画像をすべてsrc配下に入れておきたかったからです。

publicとの2箇所の管理はしたくなかったのです。

しかし考えてみると、マークダウン内でHTMLとともに表示させたい画像は私の本の表紙画像だけで、数にして35個ほど。

そしてそれは今後急激に増えることもない。

さらに画像は全てSVGなので、Astroの画像最適化の恩恵はなくてもいい。

なので、最終的にはpublic内に必要な画像を移しました。

public内の画像は<img src="/images/reactbook-ts-ai.svg" />のように、絶対パスでそのまま読み込めます。

HTMLの装飾もつけられます。

本文で![ ]()として使う画像はこれまでどおりsrcに置き、表紙画像のようなHTMLタグと一緒に装飾したい画像だけpublicに置く、という分け方に着地しました。

View Transitionsを入れて、外す

Astroには、ページ遷移をなめらかにするView Transitionsという機能があります。

ページ全体を再読み込みせず、必要な部分だけ差し替えて遷移させる機能です。

SPAのようにスムーズに動くようになります。

Gatsbyのあのスピーディなページ遷移が好きだった私としては、View Transitionsはぜひ入れたい機能でした。

なのでAstro移行作業が9割ほど終わったタイミングで、サイト全体に導入しました。

// src/layouts/Layout.astro

---
import { ClientRouter } from "astro:transitions"
---

<head>
  <ClientRouter />
</head>

ところが問題が起こりました。

本サイトにはフォームがいくつかあり、そこではスパム対策にreCAPTCHAを使っています。

View Transitionsは通常のページ再読み込みをしないため、遷移でreCAPTCHAのトークン生成が壊れ、フォーム送信が403エラーになってしまうのです。

「フォームのあるページだけView Transitionsを無効にする」という回避策も考え、一度その仕組みも作りました。

しかし本サイトにはフォームが複数のページにあり、一部の部分が意図通りに動きません。

そこに別の仕組みを当てることも考えましたが、今回の移行の方針として「例外的なコードや機能は使いたくない」というのがありました。

今後長く運用していく中で必ず内部のコードは絡まっていくので、始まりの段階で複雑なことはしたくなかったのです。

なのでView Transitionsの導入は見送りました。

Deploy時に気をつけたこと

一番気を使ったのは、従来のGatsby版サイトのSEO評価への影響です。

特に注意したのがURL、具体的には末尾のスラッシュ(Trailing Slash)。

Gatsby版では、URL末尾のスラッシュ無しを正規のURL(canonical)としていました。

 スラッシュなし
https://monotein.com/blog/react-vite-how-to-use

 スラッシュあり
https://monotein.com/blog/react-vite-how-to-use/

Astro版でも末尾のスラッシュは消したかったため、astro.config.mjsに次のコードを加えました。

trailingSlash: "never",

しかしNetlifyでは、このようなフレームワーク側の設定を上書きするようにURLが変更されることがあります。

そのため、「Project configuration」→「Post processing」→「Pretty URLs」にある「Enable Pretty URLs」のチェックを外しています。

netlify-pretty-urls.jpg

スラッシュひとつの有無でも、Googleは「異なるURL」と認識してSEOに大きな影響を与えるので、移行時には注意しましょう。

build時間が4分半から20秒に

ここからは移行して良かったことです。

一番大きいのはbuild時間の短縮。

Gatsby版ではNetlifyでのbuildに4分半かかっていました。

これがAstroに変えてみると20秒ほどになりました。

私のサイトは更新頻度も少ない単なる静的サイトなので、buildに時間がかかっても大きな問題ではなかったのですが、これほど短くなると、サイトを更新する時の妙な抵抗感は無くなります。

Gatsby時代は、記事の誤字脱字の修正といった微調整・微修正をしても、その直後にはbuildをしていませんでした。

そういう小さな修正がいくつか溜まってからbuildをかけていたのです。

しかし今は、どんなに小さな修正でも気楽にbuildをかけられるようになりました。

移行のついでに手を入れたこと

大枠の移行が終わったあとも、細かい改善をいくつか続けています。

例えばsitemapに、各記事の最終更新日(lastmod)を出すようにしました。

これはastro.config.mjsの中で、全記事のfrontmatterから更新日を読み取って組み立てています。

// astro.config.mjs(一部)
// 各記事のfrontmatterから更新日を読み、sitemapのlastmodに使う

const update = raw.match(/^update:\s*"([^"]+)"/m)?.[1]
const date = raw.match(/^date:\s*"([^"]+)"/m)?.[1]
const lastmod = update && update !== "none" ? update : date

Gatsbyのプラグインに任せていた部分を、Astroでは自分で書けるので、こうした細かい調整がやりやすくなりました。

また今回の刷新に合わせて、AIO/AI Optimization対策用のllms.txtの設置、著書一覧ページの新設(https://monotein.com/books)、全ページに構造化データ (JSON-LD)を設置といったことも行なっています。

おまけ:Prefetchはオンにしましょう

Astroでは、デフォルトでPrefetchがオフになっています。

Prefetchの有無で体感のスピードは大きく変わるので、快適なページ遷移のためにはオンにしておきましょう。

下記のコードでサイト全体でオンになります。

// astro.config.mjs

import { defineConfig } from "astro/config"

export default defineConfig({
  prefetch: {
    defaultStrategy: "viewport"
  }
})

より細かなカスタマイズも可能です。

詳しくはAstro公式ページをご覧ください。

image

⚫︎ monotein.base.shop/p/00005