Kōhei Yamamoto

Webサイトを新しい環境に移行した

このブログを含むWebサイトを新しい環境に移行した。これまで持っていたWebサイト https://kymmt.comhttps://blog.kymmt.com を統合して、ブログは https://kymmt.com/blog の下から配信することにした。

目次

概要

従来は https://kymmt.com をGitHub Pagesで、https://blog.kymmt.com をブログサービスで運用していた。今後はAstroを使ってコンテンツをSSGして配信することにした。コンテンツ生成と配信はCloudflare Workersで実行する1

コンテンツ管理

サイトに掲載するコンテンツ(多くはブログ記事)はAstroのコンテンツコレクションにまとめる。コレクションとしてblogpublicationsなどを作り、そのコレクション内でMDX/MarkdownやYAMLとしてコンテンツを管理する。

src/content.config.tsの例
import { defineCollection, z } from "astro:content";
import { file, glob } from "astro/loaders";
const blog = defineCollection({
loader: glob({ pattern: "*.{md,mdx}", base: "./blog" }),
schema: z.object({
title: z.string(),
publishedAt: z.date(),
}),
});
const publications = defineCollection({
loader: file("publications/publications.yaml"),
schema: z.object({
title: z.string(),
url: z.string(),
publishedOn: z.date(),
description: z.string().optional(),
})
})
export const collections = { blog, publications };

ブログ記事の移行

旧環境のブログ記事をコンテンツコレクションに取り込むため、次の手順で作業を進めた。

既存のブログ記事で、旧環境のサービス固有の記法(埋め込みリンク、キャプション付き画像など)をかなり使っていた影響で、MDXでそれらの記法と同じ内容を出力できるように書き換える必要があった。

元のブログは一応公開状態で残してあるが、検索エンジンなどに重複コンテンツとみなされて新しいサイトが検索エンジンにインデックスされない事態を防ぐため、noindexを設定している。

旧環境から記事をエクスポート

次のようなスクリプトで旧環境から記事をエクスポートした。あとでコンテンツコレクションのエントリとして扱えるよう、記事ファイルの先頭にfrontmatterを追加している。

export.rb
require 'fileutils'
require 'hatenablog'
require 'time'
Hatenablog::Client.create do |blog|
blog.all_entries.each do |entry|
next if entry.draft?
markdown = <<~MD
---
title: #{entry.title.inspect}
publishedAt: #{entry.updated.iso8601}
---
#{entry.content}
MD
slug = entry.uri.split('/').last
FileUtils.mkdir_p('archives')
File.open("archives/#{slug}.mdx", 'w') do |file|
file.write(markdown)
end
end
end

記事の記法をMDXに合わせて書き換え

目次や脚注を表示している記事があるので、remark-tocやremark-rehypeでHTMLレンダリング時に目次や脚注を生成する。astro.config.tsで次のように設定する。

astro.config.ts
import { defineConfig, envField } from "astro/config";
import remarkRehype from "remark-rehype";
import remarkToc from "remark-toc";
export default defineConfig({
// ...
markdown: {
remarkPlugins: [
// 「目次」という見出しのセクションに自動で目次を挿入する
// remarkRehypeより先に設定しないと目次が表示されない
[remarkToc, { heading: "目次" }],
// GFMで脚注を書くと、記事の最下部に「脚注」という見出しで脚注リストを表示する
[remarkRehype, { footnoteLabel: "脚注" }],
],
},
});

ページ埋め込みリンクはastro-embedのPreviewLinkを使う。Astroのプロジェクトメンバーが開発していたのでAstro Embedを選んだ。

日付、画像など再利用することが多い部品はAstroコンポーネントとして抽出してMDXから使う。

最終的な記事のMDXは次のような形。

---
title: Webサイトを新しい環境に移行した
publishedAt: 2025-08-13
---
import { LinkPreview } from 'astro-embed';
## 目次
{/* remark-tocが目次を挿入する */}
## 本文
このブログを含むWebサイトを新しい環境に移行した。…
<LinkPreview id="https://astro.build" />

スタイルの調整

Tailwind CSSを使う。ブログ記事本体はコンテンツコレクションから取得してMDXで埋め込むので、tailwindcss-typographyで外からスタイルを当てる形をとる。場当たり的にarbitrary valueを使ったクラスを当てないようにするのが今後の課題。

RSS

Astroを使う場合は@astrojs/rssというパッケージが利用できるが、このパッケージを使おうとすると次の課題にぶつかった。

結局、フィードはjpmonotte/feedを使ってAtomとして生成している。

フィード生成
import type { CollectionEntry } from "astro:content";
import { Feed } from "feed";
export function createFeed(site: URL, posts: CollectionEntry<'blog'>[]): Feed {
const feed = new Feed({
title: 'Kōhei Yamamoto',
description: 'Kōhei Yamamoto\'s Blog',
id: new URL('blog/feed', site).toString(),
copyright: 'Kōhei Yamamoto',
generator: 'kymmt.com',
});
for (const post of posts) {
feed.addItem({
title: post.data.title,
link: new URL(`blog/posts/${post.id}`, site).toString(),
date: post.data.publishedAt,
});
}
return feed;
}
エンドポイント
import type { APIContext } from 'astro';
import { getCollection } from 'astro:content';
import { createFeed } from '@lib/feed';
import { sortByNewest } from '@lib/post';
export async function GET({ site }: APIContext) {
if (site === undefined) {
throw new Error('site is required');
}
const posts = sortByNewest(await getCollection('blog')).slice(0, 10);
const feed = createFeed(site, posts);
return new Response(feed.atom1(), {
headers: {
'Content-Type': 'application/atom+xml; charset=utf-8',
},
});
}

記事本文はまだフィードに掲載できておらず、移行前からデグレードしてしまっている。次の記事で紹介されているようにunifiedを使ってMDXをフィード掲載に適したHTMLへと変換する必要がある。

サイトマップ

@astrojs/sitemapを設定すれば大体問題なし。GoogleサーチコンソールからサイトマップのURLを送信してインデックスさせる。

Cloudflare

Workersに継続的にデプロイできるように次の設定を入れる。

リダイレクト

古いブログのルーティングに対応するページは、できるだけ301 Moved Permanentlyで新環境にリダイレクトしたいので、次のような設定を入れる。

_redirectsの内容は次のようなもの。

_redirects
/about /blog/about 301
/archive /blog/archive 301
/archive/:year /blog/archive/:year 301
/entry/:slug /blog/posts/:slug 301
/feed /blog/feed 301

TODO

脚注

  1. Pagesを使うつもりだったが、Workersへの移行が推奨されていた