tech.sinayaka.com

GatsbyからAstroに移行したら、つまずきポイントが7つあった

2026-06-02
2026-06-04
8分
1422語
Web技術 AstroGatsbyNetlifyMDX

Gatsby製のブログサイトをAstroに移行した。

ビルドが遅い。それだけの理由だった。記事30本程度で2〜3分かかっていたのが、Astroにしたら7秒になった。それだけで移行した甲斐があったと思っている。

ただ、思ったより罠が多かった。同じような移行を考えている人のために、踏んだ地雷を全部書いておく。

1. slug がAstro 5の予約フィールドと衝突する

GatsbyのMarkdownフロントマターでは slug フィールドをURLに使うのが一般的だ。

slug: "what-is-endorphin"

AstroのContent Collectionsでも同じように使おうとして schemaslug: z.string().optional() を定義したが、Astro 5では slug が内部で自動生成される予約フィールドと衝突し、entry.data.slugundefined になるケースが発生した。

対策はシンプルで、ディレクトリ名からスラッグを導出するユーティリティ関数を作った。

export function deriveSlug(entry: any): string {
  if (entry.data?.slug) return entry.data.slug;
  // 例: "2025/0105-what-is-endorphin" → "what-is-endorphin"
  const dir = entry.id.split('/').slice(-2, -1)[0] || '';
  return dir.replace(/^\d{4}[-_]?/, '');
}

2. lazyLoadImage プラグインが記事内画像を壊す

tech.sinayaka.com(同じくAstroで動いているテックブログ)から設定を流用したとき、lazyLoadImage というrehypeプラグインを引き継いだ。このプラグインはスクロール連動の遅延ロードを実現するもので、画像の src/spinner.gif に差し替えて元のパスを data-src に保存する。

問題は、MDX内の相対パス指定の画像(![画像](./asahi.jpg) のような書き方)を使っている場合だ。

Astroはビルド時に相対パス画像を /_astro/asahi.HASH.webp のような最適化済みURLに変換するが、このrehypeプラグインが変換前の相対パスdata-src に保存してしまう。結果として本番環境で画像が404になる。

解決策:元サイト(Gatsby)が画像遅延ロードを使っていないなら、素直にプラグインを外す。

// astro.config.js
markdown: {
  rehypePlugins: [], // lazyLoadImageを除去
}

3. Netlifyの環境変数はビルド前に設定しないと反映されない

AstroはSSG(静的サイト生成)なので、import.meta.env.PUBLIC_MAILFORM_URL のような環境変数はビルド実行時にHTMLへ焼き込まれる。Netlifyでデプロイした後に環境変数を追加しても、再ビルドするまで反映されない

さらに、Netlifyの環境変数には「スコープ」設定がある。

  • Builds → ビルド時に使える(Astroはこれが必要)
  • Runtime → サーバー関数などの実行時のみ
  • All scopes → 両方

「All scopes」か「Builds」を選んでいないと、Astroのビルド中に変数が見えず空文字で焼き込まれる。

4. お問い合わせフォームの no-corsContent-Type の罠

GASへのフォーム送信に fetch を使っているが、こんな書き方をしていた。

// ❌ バグ:Content-Type がヘッダーとして送信されない
const postparam = {
  method: "POST",
  mode: "no-cors",
  "Content-Type": "application/x-www-form-urlencoded", // トップレベルは無視される
  body: JSON.stringify(forms)
};

Content-Typeheaders オブジェクトの中に書かないと送信されない。さらに mode: 'no-cors' の場合、Content-Type: application/x-www-form-urlencoded と宣言してJSONボディを送るとGASがパースに失敗する。

動いていた別サイトのコードを調べたら、実は Content-Type がトップレベルに書かれたバグを含んだまま動いていた。ヘッダーが送られないことで text/plain 扱いになり、GASが e.postData.contents をそのまま読めていたという皮肉な結果だった。

// ✅ Content-Type なしで送ると text/plain になりGASが読める
await fetch(url, {
  method: 'POST',
  mode: 'no-cors',
  body: JSON.stringify({ site: 'example.com', name, email, message }),
});

5. OGP画像のURLにスラッシュが足りない

export const SITE_URL = 'https://example.com';  // 末尾スラッシュなし
export const SITE_IMAGE = 'thumbnail.png';       // 先頭スラッシュなし
<!-- 結果 -->
<meta property="og:image" content="https://example.comthumbnail.png">

ドメインとファイル名がくっついてLINEでURLを貼ってもアイコンが出ない、という症状になる。どちらかにスラッシュを追加するだけで直る。

export const SITE_IMAGE = '/thumbnail.png';  // 先頭スラッシュを追加

6. SVGファビコンはLINEとChromeの一部で使えない

Astroのスターターは <link rel="icon" type="image/svg+xml" href="/favicon.svg"> だけが設定されていることが多い。SVGファビコンはSafariの一部やLINEのリンクプレビューで表示されないため、PNG・ICO形式も追加しておく必要がある。

<link rel="icon" type="image/x-icon" href="/favicon.ico">
<link rel="icon" type="image/png" sizes="32x32" href="/favicon-32x32.png">
<link rel="icon" type="image/png" sizes="16x16" href="/favicon-16x16.png">

7. CommonMarkの日本語太字問題

MDXで **コルチゾール(ストレスホルモン)** のように書くと、閉じる ** の直前が全角括弧 の場合に太字として認識されないことがある。

原因はCommonMarkの仕様で、** が「right-flanking delimiter」として認定されるには直前がUnicode句読点でないか、直後がUnicode句読点または空白である必要がある。全角括弧はUnicode句読点なので条件を満たさない。

remark-cjk-friendly を入れると解決する。

pnpm add remark-cjk-friendly
// astro.config.js
import remarkCjkFriendly from 'remark-cjk-friendly';

markdown: {
  remarkPlugins: [remarkCjkFriendly, ...],
}

まとめ

問題原因解決策
entry.data.slug が undefinedAstro 5の予約フィールド衝突ディレクトリ名からslugを導出
記事内画像が404lazyLoadImageが相対パスを保存プラグインを外す
フォームURLが空SSGのビルド時焼き込みNetlifyのスコープを「Builds」に
フォームが届かないContent-Type誤設定no-corsではJSON bodyをそのまま送る
OGP画像のURLが壊れているスラッシュ欠落SITE_IMAGEの先頭に/を追加
LINEにアイコンが出ないSVGファビコン非対応PNG/ICOも追加
日本語の太字が効かないCommonMarkの仕様remark-cjk-friendlyを追加

Gatsbyの資産はほぼそのまま移行できたが、細かいところでこれだけ詰まった。特に2・3・4は「動いているはずなのになぜ?」という類で時間を食った。同じ轍を踏む人が減れば幸いだ。




Copyright 2026
サイトマップ