Astro でコンテンツの変更を反映させる

更新: 開発

Astro 開発サーバーで src/content/ の変更時に自動リロードさせる方法を紹介します。

目次

astro dev で開発サーバーを起動していても src/content/ 配下のコンテンツの変更は即時反映されません。反映させるためには Ctrl+C で停止させてから再度起動してあげる必要があります。それだと面倒なので、コンテンツを保存したら自動的に反映させるようにします。

環境

  • Bun: 1.2.12
  • Astro: 5.7.11

実装

.astro ファイルを修正する

おそらく pages/blog/[...slug].astro のような場所にコンテンツをレンダリングする処理を書いていると思いますが、以下のように書いてください。

pages/blog/[...slug].astro
---
import { getCollection } from "astro:content";
import { unified } from "unified";
import { VFile } from "vfile";

export async function getStaticPaths() {
  return (await getCollection("blog"))
    .map(entry => ({
      params: {
        slug: entry.slug,
      },
      props: {
        // entry.body や entry.data は使わない。
        filePath: entry.filePath!,
      },
    }));
}

const { filePath } = Astro.props;
const vfile = await unified()
  // ここに必要なプラグインを追加してください。
  // .use()
  .process(new VFile({
    path: filePath,
    value: await Bun.file(filePath).bytes(),
  }));
// frontmatter を取得しているなら:
// const {
//   title,
//   description,
// } = vfile.data["frontmatter"];
const body = vfile.toString();
---

<div set:html={body} />

重要なポイントは、entry から body や data を使わず、逐一 filePath からファイルを読み取ることです。コンテンツを保存すると Astro がコンテンツの情報を更新した旨のログを表示しますが、実は更新されていません。entry がら取得できるファイルの内容やフロントマターは更新前のデータが残り続けています。astro.config.mjs で remark/rehype のプラグインを導入している場合は、それらの実装をこちらに移す必要があります。

インテグレーションを追加する

astro.config.mjs に自作のインテグレーションを追加します:

astro.config.mjs
import { defineConfig } from "astro/config";
import * as path from "node:path";

export default defineConfig({
  integrations: [
    refresh(),  // 追加する。
  ],
});

/**
 * @returns {import("astro").AstroIntegration}
 */
function refresh() {
  return {
    name: "refresh",
    hooks: {
      "astro:config:setup": options => {
        const {
          config,
          command,
          updateConfig,
        } = options;
        if (command !== "dev") {
          return;
        }

        updateConfig({
          vite: {
            plugins: [
              {
                name: "refresh",
                enforce: "pre",
                configureServer(server) {
                  const contentDir = path.join(config.srcDir.pathname, "content");
                  const filePthPrefix = path.normalize(contentDir) + path.sep;
                  server.watcher.on("all", async (_, filePth) => {
                    if (filePth.startsWith(filePthPrefix)) {
                      await setTimeout(300);  // リロードを若干遅らせる。
                      server.ws.send({
                        type: "full-reload",
                      });
                    }
                  });
                },
              },
            ],
          },
        });
      },
    },
  };
}

コンテンツ内のファイルに何らかの更新が入ったらブラウザをリロードさせます。こうすることで pages/blog/[...slug].astro がまた実行され、変更後のコンテンツを読み取るので、即時反映につながります。よくわかりませんがリロードを若干遅らせないとごく稀に変更が反映されないことがあります。