Astro 5からAstro 7へ完全移行した全記録|Sätteri・脱JS依存184個削除とEXDEV対策
作成日:

Astro 5からAstro 7へ完全移行した全記録|Sätteri・脱JS依存184個削除とEXDEV対策


論旨

「Astro 7 は内部エンジンが Rust 化されて爆速になったらしいけれど、自作の remark や rehype プラグインが動かなくなるのが怖くて Astro 5 のまま据え置いている」「とりあえず旧互換モード(unified パイプライン)を残してアップデートすべきか迷っている」――記事数が増えた個人ブログや技術サイトほど、こうしたメジャー移行の判断には慎重になるものです。

結論からお伝えすると、公開 650本(出力 2,251 ページ・画像 3,287 枚)の当サイトを Astro 5.18 から Astro 7.4 へ移行し、旧互換モードに頼らず 「Rust 製 3 大エンジン(.astro コンパイラ・Sätteri・Vite 8 Rolldown)+厳格 TypeScript プラグイン 1 本」へ完全純化させた結果、古い JavaScript 依存パッケージ 184 個が一掃され、静的バンドル時間は 35.25 秒から 10.50 秒へ約 3.3 倍高速化(全体ビルドも 28.36 秒) しました。本記事では、移行で実際に遭遇した 4 つの破壊的変更と、RAM ディスク出力特有の EXDEV クラッシュを透過吸収した全コードを公開します。

合わせて読みたい深まる関連情報
対象となる方
  • Astro 5 または Astro 6 から最新の Astro 7 へ安全に移行したい 個人ブロガー・メディア運営者の方

  • 旧来の remark / rehype 互換モードを引きずらず、node_modules の肥大化や JavaScript パッケージの依存警告から解放されたい 方

  • ビルド出力先(outDir)を macOS の RAM ディスクや別ドライブへ逃がしており、Astro 7 で EXDEV: cross-device link not permitted エラーに直面した 方

Astro 5から7への移行結果とビルド速度比較

Astro 7 最大の転換点は、これまで JavaScript(一部 Go)で動いていた中核パイプラインが、上から下まで Rust 製のネイティブ実装 へ一気に置き換わったことです。具体的には、① .astro ファイルを解析する Rust コンパイラ(@astrojs/compiler-rs)、② Markdown / MDX を処理する Rust エンジン Sätteri(サッテリ)、③ バンドラを統一した Vite 8(Rolldown + LightningCSS) の 3 本柱で構成されています。

当サイト(iMac 2020 / Core i9 / 64GB メモリ / 16GB APFS RAMDisk 環境)で実測した移行ビフォーアフターは以下の通りです。

計測・比較項目 移行前(Astro 5.18.0) 移行後(Astro 7.4.7) 実測変化・メリット
Markdown / MDX 処理系 JS 製 unified(remark / rehype) Rust 製 Sätteri(@astrojs/markdown-satteri) 全 650本 の同期が 約 2.0 秒 で完了
バンドラ / CSS 圧縮 Vite 6(Rollup + esbuild) Vite 8(Rust 製 Rolldown + LightningCSS) 静的バンドル時間が 35.25 秒 → 10.50 秒(3.3 倍速)
不要パッケージの削減数 従来構成のまま残置 node_modules から計 184 パッケージ削除 アップグレード時 140 個 + 旧数式系撤去 44 個を一掃
総ビルド時間(2,251 ページ) 約 45〜50 秒超 28.36 秒(WebP 3,287 枚キャッシュ再利用時) 初回コールド WebP 全生成込みでも 48.59 秒 で完走
デプロイ時のログ視認性 Astro バージョン出力なし Astro v7.4.7 構成バナーを自動印字 転送ログだけで稼働エンジンを一目で判別可能
質問するおじさん

184 個もパッケージが消えて、バンドル時間が 35 秒から 10 秒台に縮んだんか!でもネットの記事を見ると「自作の rehype プラグインがある人は従来の unified 互換モードに戻しましょう」と書いてあることも多いんじゃが、なんでそうせんかったん?

解説ナビゲーター

そこが一番の分かれ道なんです!互換モード(@astrojs/markdown-remark)を指定すれば一時的な書き換えはサボれますが、裏側では 古い JavaScript の構文木パーサーと百個以上の依存パッケージを抱え続ける ことになります。「Node まわりの依存地獄で二度と悩みたくない」なら、移行のタイミングで Sätteri ネイティブへ揃えてしまう方が圧倒的にスッキリするんですよ。

Astro 7 移行における2つのアプローチ比較
設計判断
非推奨(先送り)
旧 unified 互換モードを残す妥協案
  • remark / rehype 関連の百数十個の JS パッケージがそのまま残留する

  • Markdown と MDX の構文解析で Rust 製 Sätteri の高速化恩恵を受けられない

  • 将来のメジャー更新のたびに古いエコシステムの型ズレや非推奨警告に悩まされる

本記事の採用構成
Sätteri ネイティブ+TS 1本への純化
  • 不要な JS 依存 184 パッケージを即時撤去 し package.json を極限まで軽量化

  • 広告注入を不要とする 500 本超の記事は 構文解析前にスキップして 100% Rust 直通

  • Node.js v24 標準の TypeScript 直読みを使い、トランスパイラ無しで厳格な型検査を実現


脱・旧JS依存!Sätteri移行とプラグイン1本化

まず最初に、package.json や設定ファイル群のバックアップを退避したうえで、コアパッケージを一括更新しました。

npm install astro@latest @astrojs/mdx@latest @astrojs/rss@latest @astrojs/sitemap@latest @astrojs/check@latest

このコマンドだけで、astro が 7.4.7、@astrojs/mdx が 8.1.3 へ上がり、Rollup や古い unified 関連のパッケージが 140 個 自動削除されました。ここからさらに踏み込んで、残っていた旧プラグインとスキーマ定義を整理します。

Zod 4対応と不要な数式プラグインの完全撤去

Astro 7(正確には Astro 6 以降)では、コンテンツコレクションの型検証ライブラリが Zod 4 へ刷新され、astro:content から直接インポートしていた z が非推奨・廃止されました。対応自体は非常にシンプルで、src/content.config.ts のインポート元を astro/zod へ切り替えるだけです。

// src/content.config.ts(抜粋)
import { defineCollection } from "astro:content";
import { glob } from "astro/loaders";
import { z } from "astro/zod"; // Astro 7 標準の Zod 4 エントリポイント

なお、当サイトの src/content.config.ts には、コンテンツストアの非同期デバウンス書き込み(500ms)がバンドル開始前に確実に完了するのを待つカスタムローダー保護を入れています。「Astro 7 でもこの待機は必要か?」を推測で判断せず、node_modules/astro/dist/content/mutable-data-store.js の内部実装を直接確認したところ、Astro 7 でも SAVE_DEBOUNCE_MS = 500 の仕様がそのまま継続していたため、安全ガードを温存しました。

さらに大きかったのが 数式プラグインの断捨離 です。以前の astro.config.mjs には、念のため remark-math と rehype-katex が入っていましたが、当サイトの全 650本 を検索インデックスで全件スキャンしたところ、Markdown の生 $...$ 数式を使っている記事は 0 件(すべて専用の <KaTeXFormula> コンポーネントに統一済み)でした。

そこで npm uninstall remark-math rehype-katex を実行したところ、さらに 44 個のパッケージが消え、全記事の <pre> / <code> タグに対する不要な走査フックも完全にゼロ になりました。

SätteriとNode 24ネイティブTSで広告注入

残る自作プラグインは、検証録カテゴリの最初の <h2> 直前にだけ Google AdSense の記事内広告ユニット(<AdSenseUnit variant="in-article" />)を自動挿入していた処理 1 本だけです。

ここで「Astro 7 が Rust 製になったなら、自作プラグインも .rs(Rust)で書けるのか?」と疑問に思う方も多いでしょう。Sätteri の実体を確認すると、本体は コンパイル済みのネイティブ Rust バイナリ(.node) として配布されており、構文木(AST)自体は Rust 側のアリーナメモリ(Handle)に保持されます。一方、サイト固有の加工ルールを外から渡す公式窓口(defineHastPlugin)は、バイナリの再ビルドなしで動かせるよう JS / TS の関数を受け取って変更差分コマンド(commandBuf)だけを Rust へ返す設計 になっています。

質問するおじさん

なるほど、エンジン本体がコンパイル済みの Rust バイナリじゃけえ、外付けの窓口は Node 側になるんか。でも前みたいな型なしの .mjs ファイルはもう増やしたくないぞ!

解説ナビゲーター

ご安心ください!現在の Node.js v24 には、トランスパイラ無しで .ts ファイルを直接実行できる Type Stripping(型削除実行) が標準搭載されています。さらに Sätteri の PluginFactoryContext を使えば、広告を入れない記事は「構文解析が始まる前」に切り離して 100% Rust 内部だけで完結させられるんです!

Sätteri × TypeScript 1本化プラグインの高速処理フロー
Step 1
解析前の即時フィルタ

PluginFactoryContext でパスと非公開フラグを判定し、対象外記事は null を返して即離脱します。

Step 2
H2位置の特定と1枠注入

検証録のみルート直下の <h2> を走査し、導入枠の直後に <AdSenseUnit /> ノードを挿入します。

Step 3
Rust側で一括コンパイル

Rust エンジン側で変更コマンドを適用し、原稿や llms.txt を汚さず静的 HTML へ高速出力します。

対象外ページでは JS 側への構文木データの受け渡し自体が発生しません

実際に本番稼働している src/plugins/satteri-in-article-adsense.ts の全文は以下の通りです。Node.js v24 のネイティブ TypeScript 実行と完全互換にするため、any や enum を一切使わず、型定義はすべて import type で分離しています。

// src/plugins/satteri-in-article-adsense.ts
// ※ ca-pub-XXXXXXXXXXXXXXXX と 1234567890 はご自身のIDへ置き換えてください
import { fileURLToPath } from 'node:url';
import { defineHastPlugin } from 'satteri';
import type {
  HastContent,
  HastNode,
  HastPluginEntry,
  MdxJsxFlowElementHast,
  PluginFactoryContext,
} from 'satteri';

interface AstroFrontmatterFlags {
  hidden?: boolean;
  private?: boolean;
}

interface AstroDataBag {
  frontmatter?: AstroFrontmatterFlags;
}

function resolveNormalizedPath(fileURL: URL | undefined): string {
  if (!fileURL) return '';
  try {
    return fileURLToPath(fileURL).split('\\').join('/');
  } catch {
    return String(fileURL).split('\\').join('/');
  }
}

function isEligibleReviewPost(
  rawPath: string,
  data: Record<string, unknown> | undefined
): boolean {
  if (!rawPath || !rawPath.includes('/src/content/blog/')) return false;
  if (
    rawPath.includes('/ai-nation-concept/') ||
    rawPath.includes('/内部資料/') ||
    rawPath.includes('/bk/')
  ) {
    return false;
  }

  const astroBag = data?.astro as AstroDataBag | undefined;
  const frontmatter = astroBag?.frontmatter;
  return !(frontmatter?.hidden === true || frontmatter?.private === true);
}

function hasClassName(node: HastNode, targetClass: string): boolean {
  if (node.type !== 'element') return false;
  const cls = node.properties?.className;
  return Array.isArray(cls) && cls.includes(targetClass);
}

export function satteriInArticleAdsense(): HastPluginEntry {
  return (factoryCtx: PluginFactoryContext) => {
    const factoryPath = resolveNormalizedPath(factoryCtx.fileURL);
    // 対象外の記事はパース前に null を返し、100% Rust 内部のみで完結させる
    if (!isEligibleReviewPost(factoryPath, factoryCtx.data)) {
      return null;
    }

    return defineHastPlugin({
      name: 'satteri-in-article-adsense',
      before(root, ctx) {
        if (!root || !Array.isArray(root.children)) return;
        const rawPath = resolveNormalizedPath(ctx.fileURL) || factoryPath;
        if (!isEligibleReviewPost(rawPath, ctx.data)) return;

        const children = root.children;
        const alreadyInserted = children.some(
          (node) =>
            (node.type === 'mdxJsxFlowElement' && node.name === 'AdSenseUnit') ||
            hasClassName(node, 'adsense-unit')
        );
        if (alreadyInserted) return;

        let firstMeaningfulNode: HastNode | null = null;
        const h2Indices: number[] = [];

        for (let i = 0; i < children.length; i++) {
          const node = children[i];
          if (!node) continue;
          if (
            node.type === 'mdxjsEsm' ||
            node.type === 'comment' ||
            (node.type === 'text' && (!node.value || !node.value.trim())) ||
            (node.type === 'element' && node.tagName === 'hr')
          ) {
            continue;
          }
          if (!firstMeaningfulNode) firstMeaningfulNode = node;
          if (node.type === 'element' && node.tagName === 'h2') {
            h2Indices.push(i);
          }
        }

        if (h2Indices.length === 0) return;
        const startsWithH2 =
          firstMeaningfulNode?.type === 'element' &&
          firstMeaningfulNode.tagName === 'h2';

        const targetH2Index = startsWithH2
          ? h2Indices.length >= 2
            ? (h2Indices[1] ?? -1)
            : -1
          : (h2Indices[0] ?? -1);
        if (targetH2Index < 0) return;

        const isMdx = ctx.sourceFormat === 'mdx' || /\.mdx$/i.test(rawPath);
        const mdxAdNode: MdxJsxFlowElementHast = {
          type: 'mdxJsxFlowElement',
          name: 'AdSenseUnit',
          attributes: [
            { type: 'mdxJsxAttribute', name: 'variant', value: 'in-article' },
          ],
          children: [],
        };

        const htmlAdNode: HastContent = {
          type: 'element',
          tagName: 'div',
          properties: {
            className: ['adsense-unit', 'adsense-in-article'],
          },
          children: [
            {
              type: 'element',
              tagName: 'ins',
              properties: {
                className: ['adsbygoogle'],
                style: 'display:block; text-align:center;',
                'data-ad-layout': 'in-article',
                'data-ad-format': 'fluid',
                'data-ad-client': 'ca-pub-XXXXXXXXXXXXXXXX',
                'data-ad-slot': '1234567890',
              },
              children: [],
            },
            {
              type: 'element',
              tagName: 'script',
              properties: {},
              children: [
                {
                  type: 'text',
                  value: '(adsbygoogle = window.adsbygoogle || []).push({});',
                },
              ],
            },
          ],
        };

        ctx.insertChildAt(root, targetH2Index, isMdx ? mdxAdNode : htmlAdNode);
      },
    });
  };
}

なお、「親の astro.config.mjs も astro.config.ts に変えなかったのはなぜか?」にも明確な理由があります。Astro 7 の設定ローダー(vite-load.js)の実装を読むと、設定ファイルが .mjs / .js の場合は 仮の Vite サーバーを起動せず Node.js のネイティブ import() で即座に直読みする高速パス が使われます。つまり、親は astro.config.mjs のまま維持してプラグイン側だけ .ts にするのが、起動の軽さと TypeScript の型安全性を両取りする最適解 なのです。


現場で踏んだ4つの落とし穴とEXDEV回避コード

ここからは、実際にフルビルド(astro build)を回して初めて表面化した 4 つのリアルな落とし穴 とその解決策を共有します。

RAMDisk出力で起きたEXDEVエラーの真因と対策

当サイトでは内蔵 SSD の書き込み寿命を守りつつビルドを高速化するため、astro.config.mjs の最終出力先 outDir をメモリ上の仮想ドライブ(/Volumes/RAMDisk/<ユーザー名>/astro-dist)へ向けています。Astro 5 ではこのまま問題なく動いていましたが、Astro 7 でビルドを走らせたところ、終盤の Rearranging server assets... で以下のエラーが出て停止しました。

EXDEV: cross-device link not permitted, rename
'.../ai-blog-project/.astro/.prerender/_astro/xxx.webp'
-> '/Volumes/RAMDisk/<user>/astro-dist/_astro/xxx.webp'

原因を Astro 7 の内部コード(static-build.js の ssrMoveAssets)まで追跡して判明した仕組みはこうです。

  1. outDir がプロジェクトフォルダの外(別ボリュームの RAM ディスク等)にある場合、Astro は Node.js が node_modules を正しく解決できるよう、プリレンダリング用の一時出力先を 内蔵 SSD 側の <プロジェクト>/.astro/.prerender に作成する。

  2. レンダリング完了後、Astro 7 は一時アセットを最終出力先へ移すために OS の rename(2) システムコール(fs.promises.rename) を直接呼び出す。

  3. POSIX 規格上、rename は同一ファイルシステム(同じマウントボリューム)内でしか使えず、内蔵 SSD から APFS RAM ディスクへの移動では必ず EXDEV(デバイス跨ぎリンク不可)例外が発生 する。

そこで astro.config.mjs の astro:build:start フックに、EXDEV が発生した瞬間だけ cp(コピー)+ rm(元削除)へ自動フォールバックする透過ラッパー を組み込みました。通常の同一ドライブ内の移動では一切オーバーヘッドが発生せず、同時に「ビルドログに Astro の稼働バージョンが表示されない」という不満も 1 つのフックで解消しています。

// astro.config.mjs(抜粋)
import fs from 'node:fs';
import os from 'node:os';
import { satteri } from '@astrojs/markdown-satteri';
import mdx from '@astrojs/mdx';
import { defineConfig } from 'astro/config';
import { satteriInArticleAdsense } from './src/plugins/satteri-in-article-adsense.ts';

const ramBase = `/Volumes/RAMDisk/${os.userInfo().username}`;
const hasRamDisk = fs.existsSync(ramBase);

// SSD (.astro/.prerender) と RAMDisk (outDir) 間の EXDEV エラーを透過吸収
let isExdevRenamePatched = false;
function installCrossVolumeRenameFallback() {
  if (isExdevRenamePatched) return;
  isExdevRenamePatched = true;
  const origRename = fs.promises.rename.bind(fs.promises);
  fs.promises.rename = async (oldPath, newPath) => {
    try {
      return await origRename(oldPath, newPath);
    } catch (/** @type {any} */ err) {
      if (err && err.code === 'EXDEV') {
        const src = Buffer.isBuffer(oldPath) ? oldPath.toString('utf8') : oldPath;
        const dest = Buffer.isBuffer(newPath) ? newPath.toString('utf8') : newPath;
        await fs.promises.cp(src, dest, { recursive: true, force: true });
        await fs.promises.rm(src, { recursive: true, force: true });
        return;
      }
      throw err;
    }
  };
}

export default defineConfig({
  outDir: hasRamDisk ? `${ramBase}/astro-dist` : './dist',
  cacheDir: hasRamDisk ? `${ramBase}/astro-cache` : './node_modules/.astro',
  vite: {
    cacheDir: hasRamDisk ? `${ramBase}/vite-cache` : './node_modules/.vite',
  },
  integrations: [
    {
      name: 'build-version-banner',
      hooks: {
        'astro:build:start': ({ logger }) => {
          installCrossVolumeRenameFallback();
          const astroPkg = JSON.parse(
            fs.readFileSync(new URL('./node_modules/astro/package.json', import.meta.url), 'utf8')
          );
          logger.info(`Astro v${astroPkg.version} (Rust Compiler + Sätteri + Vite 8 Rolldown)`);
        },
      },
    },
    mdx(),
  ],
  markdown: {
    processor: satteri({
      hastPlugins: [satteriInArticleAdsense()],
    }),
  },
});

LightningCSS警告とnpm設定・型検査の罠

EXDEV 以外にも、厳格化された Rust 製ツール群のおかげで今まで見過ごされていた細かな火種を 3 つ同時に潰すことができました。

🔧 移行時に解消した 3 つの環境・構文トラップ
  1. 素の CSS ファイルに紛れ込んでいた :global(...) の検出(LightningCSS) : Vite 8 標準の CSS ミニファイアである Rust 製 LightningCSS が、src/styles/global.css 内に書かれていた :global([data-theme="dark"]) mark に対して global is not recognized as a valid pseudo-class と警告を出してくれました。:global() は .astro ファイルの <style> 専用の記法であり、通常の .css ファイルでは無効になっていたバグだったため、[data-theme="dark"] mark に直してダークモード時の蛍光マーカー配色も正常化しました。

  2. npm 11 の .npmrc 非推奨 tmp 警告とテレメトリ書き込み抑止 : Node.js v24(npm 11)ではプロジェクト .npmrc 内の tmp=... 指定が Unknown project config "tmp" 警告になるため撤去し、package.json スクリプト側の環境変数 TMPDIR および ASTRO_TELEMETRY_DISABLED=1(~/Library/Preferences/astro への書き込み抑止)へ一本化しました。

  3. VS Code エディタ上で .ts インポートの赤波線を消す tsconfig.json 連携 : tsconfig.json の exclude に "*.mjs" が入っていると、astro.config.mjs を開いた際に Astro 標準の "allowImportingTsExtensions": true が効かず、.ts プラグインのインポート行に TS5097 エラーの赤波線が出ます。tsconfig.json の include に "astro.config.mjs" を加えることで、tsc -p tsconfig.json --noEmit がエラー・警告とも完全 0 件になりました。


快適開発を支える母艦コストとAI移行プロンプト

最後に、数千ページ規模のビルドと 16GB RAM ディスクを余裕で回しているローカル母艦のコスト計算と、読者の方がご自身の Astro サイトを安全に v7 へ引き上げるための AI 指示プロンプトをまとめます。

メモリ64GB母艦とRAMDiskの1日あたり単価

Astro 7 で Rust 化されたとはいえ、数千枚の最適化 WebP 画像キャッシュやビルド出力一式を丸ごとメモリ上の RAM ディスクに載せて内蔵 SSD の消耗をゼロにするには、メインメモリを 64GB 積んだデスクトップ母艦があると別次元の快適さになります。

たとえば 64GB ユニファイドメモリ搭載の現行 Apple Mac mini(M4 Pro 構成) や、当サイトで現役稼働している 27 インチ iMac 5K(2020 年 Intel 最終世代の整備済品+Crucial DDR4 メモリ 64GB 自己増設) を選んだ場合、仮に初期費用が 20万〜35万円前後かかったとしても、毎日 5 年間(1,825 日)使い続ければ 1 日あたりの快適コストは約 110円〜192円(推論・単純日割り試算) です。ビルド待ちのストレスも SSD 寿命の心配も缶コーヒー 1 本以下で消えると考えれば、個人開発の母艦投資として非常に合理的です。

Apple Mac mini(M4 Pro 等 Appleシリコン Pro系チップ・64GBユニファイドメモリ構成)

Apple Mac mini(M4 Pro 等 Appleシリコン Pro系チップ・64GBユニファイドメモリ構成)

【中古・整備済品】Apple iMac 27インチ Retina 5K (2020年モデル / Intel最終世代・メモリ自己増設対応)

【中古・整備済品】Apple iMac 27インチ Retina 5K (2020年モデル / Intel最終世代・メモリ自己増設対応)

【Intel Mac増設用】Crucial iMac 27インチ(2019/2020)・ノートPC用 メモリ DDR4-2666 SO-DIMM (16GB×2 / 32GB×2)

【Intel Mac増設用】Crucial iMac 27インチ(2019/2020)・ノートPC用 メモリ DDR4-2666 SO-DIMM (16GB×2 / 32GB×2)

また、Astro 7 の裏側を支える Rust のメモリモデルやエラー処理の設計思想を基礎から理解しておきたい方には、手元に 1 冊あると長く引ける定番書『プログラミングRust 第2版』もおすすめです。

オライリー・ジャパン『プログラミングRust 第2版』

オライリー・ジャパン『プログラミングRust 第2版』

最上位AIに安全に移行させるコピペ用プロンプト

自作プラグインやカスタム設定が入った Astro プロジェクトを v7 へ移行する際は、軽量モデルではなく 必ず『Gemini 4 Argon』または同等以上の最上位推論モデル を選択し、以下のプロンプトを貼り付けて「事前バックアップ → 互換モードに頼らない Sätteri 化 → 全ページ実出力の検証」の順で進めさせてください。

以下の記事の設計方針と検証手順を参照し、私の Astro プロジェクトを最新の Astro 7(Rustコンパイラ・Sätteri・Vite 8 Rolldown)へ安全に完全移行してください。
参照URL: https://lab.chihouzakki.com/blog/review/astro5-to-astro7-rust-satteri-ramdisk-exdev-migration/

【作業ルール(厳守)】
1. ファイルを書き換える前に、必ず package.json・lockファイル・astro.config.*・content.config.ts・カスタムプラグイン等のバックアップを作成してください。
2. 安易に旧互換モード(@astrojs/markdown-remark)へ逃げず、現在使っている remark / rehype プラグインが本当に全記事で必要か(専用コンポーネント等で既に代替されていないか)を調査し、必要な処理だけを Sätteri ネイティブの TypeScript プラグインへ最小限に移植してください。
3. src/content.config.ts の Zod 4 インポート(astro/zod)、LightningCSS の構文警告、および outDir を別ボリューム(RAMディスク等)へ向けている場合の EXDEV フォールバック有無を漏れなく点検してください。
4. 移行後は開発サーバーだけでなく必ず本番フルビルド(astro build)と型検査を通し、生成された HTML・RSS・サイトマップ・目次アンカー・数式・広告枠に欠落がないことを確認して報告してください。

まとめ:互換モードに逃げずRust基盤へ純化しよう

Astro 5 から Astro 7 へのメジャー移行は、単なるバージョン番号の更新ではなく、「複雑に絡み合った JavaScript パイプラインを卒業し、高速で厳格な Rust 製ビルド基盤へ生まれ変わる絶好の機会」 です。

✅ Astro 7 完全移行で幸せになる人・向かない人
  • 今すぐ移行して幸せになる人 : 記事数や画像が増えてビルド時間を短縮したい方、不要な npm パッケージをごっそり減らして長期保守を楽にしたい Astro サイト運営者の方

  • 慎重に準備すべき人 : サードパーティ製の巨大な remark / rehype プラグインに強く依存しており、Sätteri 向けの書き換えやコンポーネント代替の工数をまだ確保できない方

質問するおじさん

バックアップを取って 1 つずつ仕様変更を潰していったら、184 個も余計な荷物が消えてビルドも 28 秒になったんじゃな。ログにもちゃんと Astro v7.4.7 と出るようになって気分爽快じゃ!

解説ナビゲーター

はい!特に RAM ディスク出力時の EXDEV 回避や、対象外ページを構文解析前に切り離す Sätteri ファクトリ設計は、一度入れておけばこれから記事が 1,000 本、2,000 本に増えてもずっと効き続ける財産になりますよ。

ノルウェージャンフォレストキャット

ビルドがあっという間に終わって Mac のファンも静かなままだから、横でぐっすりお昼寝できるにゃ……!

高速で静かになったMacのそばで安心してお昼寝するノルウェージャンフォレストキャット

▲ 依存パッケージの悩みから解放されれば、人間も猫もゆっくり眠れます