Webサイトを新しい環境に移行した
このブログを含むWebサイトを新しい環境に移行した。これまで持っていたWebサイト https://kymmt.com と https://blog.kymmt.com を統合して、ブログは https://kymmt.com/blog の下から配信することにした。
目次
概要
従来は https://kymmt.com をGitHub Pagesで、https://blog.kymmt.com をブログサービスで運用していた。今後はAstroを使ってコンテンツをSSGして配信することにした。コンテンツ生成と配信はCloudflare Workersで実行する1。
コンテンツ管理
サイトに掲載するコンテンツ(多くはブログ記事)はAstroのコンテンツコレクションにまとめる。コレクションとしてblog
やpublications
などを作り、そのコレクション内でMDX/MarkdownやYAMLとしてコンテンツを管理する。
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に合わせて書き換え
- スタイルの調整
既存のブログ記事で、旧環境のサービス固有の記法(埋め込みリンク、キャプション付き画像など)をかなり使っていた影響で、MDXでそれらの記法と同じ内容を出力できるように書き換える必要があった。
元のブログは一応公開状態で残してあるが、検索エンジンなどに重複コンテンツとみなされて新しいサイトが検索エンジンにインデックスされない事態を防ぐため、noindexを設定している。
旧環境から記事をエクスポート
次のようなスクリプトで旧環境から記事をエクスポートした。あとでコンテンツコレクションのエントリとして扱えるよう、記事ファイルの先頭にfrontmatterを追加している。
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 endend
記事の記法をMDXに合わせて書き換え
目次や脚注を表示している記事があるので、remark-tocやremark-rehypeでHTMLレンダリング時に目次や脚注を生成する。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というパッケージが利用できるが、このパッケージを使おうとすると次の課題にぶつかった。
- 記事本文をフィードに掲載したいなら、それに適した形でMDXからHTMLに変換しなければならない。たとえばJavaScriptが含まれる場合に取り除いたりする必要がある
- Atomで配信できない
結局、フィードは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に継続的にデプロイできるように次の設定を入れる。
- リポジトリとの接続
- Git integration · Cloudflare Workers docs
- ブランチごとに環境が立ち上がる
- 環境変数の設定
- ビルドコマンドの設定
- pnpmを使っているので
pnpm run build
を設定
- pnpmを使っているので
リダイレクト
古いブログのルーティングに対応するページは、できるだけ301 Moved Permanentlyで新環境にリダイレクトしたいので、次のような設定を入れる。
- Cloudflare Rulesで、次の優先順位のルールを定義する
blog.kymmt.com
をkymmt.com/blog
に301リダイレクトするblog.kymmt.com/*
をkymmt.com/${1}
に301リダイレクトする
- _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
- 構造化データの設定
- 一部のページに入っているがメンテナンス性が低い
- パンくずリスト
- アクセス解析
脚注
-
Pagesを使うつもりだったが、Workersへの移行が推奨されていた ↩