---
title: Nuxt ContentからOx Contentへ、1000ページ超のブログ基盤刷新への挑戦 - Slidev
tags: 
author: [Yutaro Koizumi](https://image.docswell.com/user/ykoizumi0903)
site: [Docswell](https://www.docswell.com/)
thumbnail: https://bcdn.docswell.com/page/LE3WMMZDE5.jpg?width=480
description: Vue Fes Japan 2026 での登壇資料です。 https://vuefes.jp/2026/speaker/ykoizumi0903
published: October 24, 26
canonical: https://image.docswell.com/s/ykoizumi0903/5WR6PY-vuefesjapan2026
---
# Page. 1

![Page Image](https://bcdn.docswell.com/page/LE3WMMZDE5.jpg)

Nuxt ContentからOx Contentへ
1000ページ超のブログ基盤刷新への挑戦
Yutaro Koizumi
2026-10-24 @ Vue Fes Japan 2026


# Page. 2

![Page Image](https://bcdn.docswell.com/page/8EDK66R27G.jpg)

Yutaro Koizumi ( @ykoizumi0903 )
2019年 アンドパッドに入社
ERP（引合粗利管理）フロントチーム テックリード
過去の登壇
Nuxt × Vue Router の 力を最大限に引き出す機能を紹介 (Vue Fes Japan 2024)
Vue・React マルチプロダクト開発を支える Vite (#vuefes_fec_tokyo)
Vue にまつわる3つのよくある誤解と、私がVueを選ぶ4つの理由


# Page. 3

![Page Image](https://bcdn.docswell.com/page/V7PKYYWLJ8.jpg)

株式会社アンドパッド
建設業に特化したクラウドサービス「ANDPAD」を開発しています
スポンサーブース出展しているのでぜひ遊びに来てください！


# Page. 4

![Page Image](https://bcdn.docswell.com/page/2JVVGG86JQ.jpg)

※今日の発表内容は、業務とは関係ない趣味の話です


# Page. 5

![Page Image](https://bcdn.docswell.com/page/5EGL8852JL.jpg)

今回の題材：個人ブログのリニューアルの話
大学時代に開設、記事数は1000本以上
Nuxt Content で開発していたが、表示速度や運用面などの課題が出てきていた
当初の計画： Nuxt・SSR・Nuxt Content を一気に置き換える大規模なリニューアル
（Vue Fes での発表も意識して、派手な改善を見せたかった）
アプリ基盤
Nuxt → Vite + Vue
コンテンツ基盤
Nuxt Content → Ox Content
レンダリング方式
SSR → SPA
しかし、最終的に、NuxtとSSRの置き換えは断念し、コンテンツ基盤だけを Ox Contentへ移行しました
なぜこの判断に至ったのか、それがどのような効果が出たのかを紹介します


# Page. 6

![Page Image](https://bcdn.docswell.com/page/4JQY99Z97P.jpg)

今日のアジェンダ
1. ブログサイトの改善前の構成
2. Nuxt Content + NuxtHub で見えてきた課題
3. 技術基盤の見直しと、どう意思決定したか
4. Markdown 変換の高速化
5. Ox Content での全文検索の実装
6. 移行結果と技術選定の学び


# Page. 7

![Page Image](https://bcdn.docswell.com/page/K74WXXDRE1.jpg)

1. ブログサイトの改善前の構成


# Page. 8

![Page Image](https://bcdn.docswell.com/page/LJ1YNNZVEG.jpg)

個人ブログの概要
元々は単なる趣味のブログだった
2019年にアンドパッドに入社してからは、興味のある技術を試す場として活用するようになった
〜2019
WordPress
2020
Nuxt.js
2021
Next.js
2023
Nuxt 3
2025〜
Nuxt 4
定期的にリニューアルを行うメリット
チュートリアルを触るだけでは得られない知見がある
実際の使用感に基づいて技術記事を書くことができる
祝・正式リリース！5つのテーマで理解する Nuxt3 の魅力
Nuxt3 サイトを Google Cloud Run にデプロイする方法（Dockerfile サンプル付き）
会社での技術選定や新しい挑戦に繋がる
個人開発で Next.js を試していたおかげで、会社で急に React プロジェクトを任されても対応できた


# Page. 9

![Page Image](https://bcdn.docswell.com/page/GJWG449372.jpg)

最新の技術構成 (Ox Content 導入前)
① Markdown
Front Matter
本文
→
② Nuxt Content
読み取り・クエリ・検索
コレクション化
主な技術
→
③ Cloudflare
Workers
D1 Database
R2 Storage
→
④ Nuxt
ページ
Server API
フロントエンド：Nuxt 4
ホスティング：NuxtHub (Cloudflare Workers)
記事管理・検索：Nuxt Content（D1連携）
記事本文・画像：R2 Storage
Nuxt 向けの Git ベース CMS。Markdown などの記事を管理
し、クエリで取得・表示できる
DB・ファイル保存・キャッシュなど、フルスタックアプリの
構築・デプロイをサポート。


# Page. 10

![Page Image](https://bcdn.docswell.com/page/4EZL229G73.jpg)

Markdown は公開前に変換
① Markdown
→ ② Nuxt Content →
③ Cloudflare
可能な限り前処理で集計や変換を済ませ、CI やクライアントのコストを削減する
→
④ Nuxt
記事の原本は Markdown ファイルとして Git 管理
公開前にローカルで HTML へ変換し、本文は R2、メタデータは記事データとして保存
変換前の Markdown + Frontmatter
---
変換後の記事データ
{
id: 100
title: タイトル
date: &quot;2026-10-01T22:57:40+09:00&quot;
&quot;id&quot;: 100,
&quot;slug&quot;: &quot;title&quot;,
&quot;title&quot;: &quot;タイトル&quot;,
--記事の本文がここに入ります。
&quot;contentPath&quot;: &quot;posts/2026/10/title.txt&quot;,
&quot;contentPlainText&quot;: &quot;記事の本文がここに入ります。&quot;,
&quot;date&quot;: &quot;2026-10-01T22:57:40+09:00&quot;,
&quot;postType&quot;: &quot;post&quot;,
&quot;postStatus&quot;: &quot;publish&quot;
}


# Page. 11

![Page Image](https://bcdn.docswell.com/page/Y76WVVKQ7V.jpg)

Nuxt Content で記事をコレクション管理
① Markdown
→ ② Nuxt Content →
③ Cloudflare
SQL を意識せずに、コレクションとして記事を管理する
→
④ Nuxt
変換後の JSON をコレクションとして登録し、本文とメタデータを読み取る
nuxt.config.ts：モジュール登録
// nuxt.config.ts
export default defineNuxtConfig({
modules: [&quot;@nuxt/content&quot;],
});
content.config.ts：記事コレクション定義
// content.config.ts
import { defineCollection, defineContentConfig } from &#039;@nuxt/content&#039;
export default defineContentConfig({
collections: {
posts: defineCollection({
type: &quot;page&quot;,
source: &quot;posts/*/*/*.json&quot;,
})
}
})


# Page. 12

![Page Image](https://bcdn.docswell.com/page/G75MDDP274.jpg)

queryCollection で一覧・絞り込み・全文検索
① Markdown
→ ② Nuxt Content → ③ Cloudflare
SQL を意識せずに、コレクションとして記事を管理する
→
④ Nuxt
Nuxt Contentで記事をコレクションとして管理し、 queryCollection で一覧・絞り込み・ページネーショ
ンを実行
const posts = await queryCollection(&quot;posts&quot;)
.where(&quot;category&quot;, &quot;=&quot;, &quot;Vue&quot;)
.order(&quot;date&quot;, &quot;DESC&quot;)
.limit(10)
.all()
全文検索も LIKE クエリで実現（※タグや記号を除去したプレーンテキストを別に保持しておく）


# Page. 13

![Page Image](https://bcdn.docswell.com/page/9J298861ER.jpg)

NuxtHubでフルスタック Nuxt アプリを構築
① Markdown
→ ② Nuxt Content → ③ Cloudflare
NuxtHub が Cloudflare Workers との連携をスムーズにする
export default defineNuxtConfig({
modules: [&quot;@nuxthub/core&quot;, &quot;@nuxt/content&quot;],
hub: {
blob: {
driver: &quot;cloudflare-r2&quot;,
binding: &quot;BLOB&quot;,
bucketName: &quot;**&quot;,
},
db: {
driver: &quot;d1&quot;,
connection: {
databaseId: &quot;**&quot;,
},
},
},
content: {
database: { type: &quot;d1&quot;, bindingName: &quot;CF_BINDING_NAME&quot; },
→
④ Nuxt
nuxt.config.ts で Cloudflare D1 を設定
content.database ：Nuxt Content のデータ
ベースに D1 を指定
bindingName ：Cloudflare Workers 側の D1
接続名を指定
hub.db.driver ：NuxtHub のデータベースに
も D1 を指定


# Page. 14

![Page Image](https://bcdn.docswell.com/page/DEY46694JM.jpg)

Cloudflare の D1・R2 の使われ方
① Markdown
→ ② Nuxt Content →
NuxtHub が Cloudflare Workers との連携をスムーズにする
D1：SQLite 互換のサーバーレスデータベース
記事のメタデータや検索用データを保存し、Nuxt
Content のクエリで取得
const post = await queryCollection(&quot;posts&quot;)
.where(&quot;postId&quot;, &quot;=&quot;, Number(id))
.first();
③ Cloudflare
→
④ Nuxt
R2：本文・画像用オブジェクトストレージ
記事本文や画像ファイルを保存し、NuxtHub の Blob
API から取得・配信
import { blob } from &quot;hub:blob&quot;;
const file = await blob.get(contentPath);
const content = await file?.text();
return blob.serve(event, imagePath);


# Page. 15

![Page Image](https://bcdn.docswell.com/page/VJNYZZL678.jpg)

Nuxt Server APIがメタデータと本文を組み合わせて返す
① Markdown
→ ② Nuxt Content → ③ Cloudflare →
④ Nuxt
Nuxt の Server API から queryCollection を呼び出し、必要なデータだけを取得する
ブラウザは Nuxt Server API のみを呼び、API が D1 のメタデータと R2 の本文を組み合わせて返す
記事詳細 API
R2 から本文を取得
// server/api/post.ts
import { blob } from &quot;hub:blob&quot;;
const getTextFromBlobFile = async (pathname: string) =&gt; {
const file = await blob.get(pathname);
return file?.text() || &quot;&quot;;
export default defineEventHandler(async (event) =&gt; {
const { id } = getQuery(event);
const post = await queryCollection(&quot;posts&quot;)
};
.where(&quot;postId&quot;, &quot;=&quot;, Number(id))
.first();
if (!post) return { data: null };
const content = await getTextFromBlobFile(post.contentPath);
return { data: { ...post, content } };
});


# Page. 16

![Page Image](https://bcdn.docswell.com/page/YE9PZZ4RJ3.jpg)

ここまでの構成を整理
Markdown
remark + rehype
Nuxt Content
D1
Server API
Nuxt Client
Cloudflare
Workers
1. 公開前に Markdown を変換して記事データを生成、本文と画像は R2 に保存
2. メタデータは Nuxt Content を通して D1 に保存
3. Nuxt Server API が NuxtHub を通し、 D1 と R2 から必要なデータを取得
4. クライアントは Nuxt Content やストレージに直接アクセスせず、API を通してデータを取得
5. Cloudflare Workers でホスティング
Cloudflare, Nuxt, NuxtHub, Nuxt Content を全面的に活用し、少ないコードで効率的に記事管理・配信の仕
組みを構築


# Page. 17

![Page Image](https://bcdn.docswell.com/page/GE8D11QGED.jpg)

2. Nuxt Content + NuxtHub で見えてきた課題


# Page. 18

![Page Image](https://bcdn.docswell.com/page/LELMQQXX7R.jpg)

大前提：NuxtHub + Nuxt Content は非常に便利
決して「オススメしない」という発表ではありません！
Nuxt の開発体験を活かしながら、記事の管理・検索・配信まで少ないコードで構築できる
D1 Database との連携も、NuxtHub が多くの部分を面倒見てくれる
コスト的にも非常に安価
大きな不満はなく、個人ブログや小規模なメディアサイトであれば、十分に現実的な選択肢


# Page. 19

![Page Image](https://bcdn.docswell.com/page/4JMY11L3JW.jpg)

ただし、運用を続ける中で、気になる点も出てきました


# Page. 20

![Page Image](https://bcdn.docswell.com/page/PJR9LLKR79.jpg)

①D1 Database を経由する必要がやや過剰
毎回 D1 Database とのデータ同期が入ることで、ビルド・デプロイに時間がかかる
Nuxt Content を通すことで DB を自分で管理しなくて良いが、全記事分のマイグレーションが毎回走る
ため、ビルド時間が長くなる
実際の表示速度面でも、D1 Database へのアクセスがボトルネックになっている
Markdown を元にした静的ファイルを D1 経由で扱うのは、コスト面でも無駄が大きい
⇒ 静的コンテンツに対して、データベースを経由する構成は重い


# Page. 21

![Page Image](https://bcdn.docswell.com/page/PEXQWWLYJX.jpg)

②自動生成されたDB処理がブラックボックス化している
Nuxt Content が Migration ファイルを自動生成してくれるので、SQL を意識せず使えるが、
データベースの中身や処理の流れが見えづらい
大きな記事データの登録で SQLITE_TOOBIG エラーが発生し、自分で書いた SQL ではないため原因特定
に時間がかかった
最終的に本文を txt ファイルへ分離して R2 Storage を経由することで回避したが、根本原因は追いづら
かった
Failed to apply database query content-database-002: Failed to query database: statement too long: SQLITE_TOOBIG
他にも、Nuxt のアップデート後、トップに古い記事が表示されてしまう問題が起きたが、原因を特定でき
ずにバージョンを戻して対処していた
⇒ ライブラリに任せる便利さとのトレードオフ


# Page. 22

![Page Image](https://bcdn.docswell.com/page/3EK9VVL4ED.jpg)

③Nuxtへの依存度が高くなりすぎている
Nuxt 関連ライブラリは密結合になりやすく、特定のライブラリだけをアップデートしたり入れ替えたりし
づらい
個人開発というよりは業務の方で、 Nuxt への依存度が高すぎることの弊害を感じていた
NuxtHub Admin のサービス終了 により、Nuxt Modules に依存しすぎるリスクを意識するようになった
Vapor Mode や Oxc, Vite Plus といった VoidZero 関連の最新技術への追従が遅れやすい
⇒ Nuxt を使わずにサイトを構築できるならその方が良いという気持ちが出てきた


# Page. 23

![Page Image](https://bcdn.docswell.com/page/L73WMM3D75.jpg)

④Ox Content に興味を持った
Nuxt Content からの移行先を探している中で偶然流れてきた
筋と憎悪，もののけ王
@ubugeeei · Follow
はドキュメントジェネレータで
あると同時に，マークダウンフレームワーク
でもあるし，ライブラリでもあるし，ユーテ
ィリティでもある．そして宇宙最速
Ox Content
7:07 PM · Jun 8, 2026
2
Reply
Copy link
Read more on X
筋と憎悪，もののけ王
@ubugeeei · Follow
の面白いところは，Framework
だけど Framework Integration が実
装されてて，Dynamic Og Image もコンポー
ネントでかけつつ，Chromium ベースだから
CSS が自由なところ
Ox Content
Agnostic
11:20 PM · Mar 7, 2026
Reply
Copy link
Read 1 reply


# Page. 24

![Page Image](https://bcdn.docswell.com/page/87DK6642JG.jpg)

④Ox Content に興味を持った
OXCONTENT ( )
ubugeeei 氏が開発しているRust 製の Markdown ツールキット
ドキュメントサイト生成に加え、Markdown の解析・HTML 描画・変換機能を提供
特定のフレームワークに依存せず必要な機能を選んで利用できる
⇒ 高性能な Rust 製ツールであれば、単純な置き換えでも速度改善が期待でき、
Nuxt への依存を下げたいという意図ともマッチしている


# Page. 25

![Page Image](https://bcdn.docswell.com/page/VJPKYYMLE8.jpg)

3. 技術基盤の見直しと、どう意思決定したか


# Page. 26

![Page Image](https://bcdn.docswell.com/page/2EVVGG96EQ.jpg)

最初に目指していたリニューアル
アプリ基盤
Nuxt → Vite + Vue
コンテンツ基盤
Nuxt Content → Ox Content
目的
ビルド・デプロイ時間を短縮し、コスト削減に繋げたい
表示速度をさらに向上させたい
Nuxtへの依存度を下げ、Nuxt を使わない開発を経験したい
レンダリング方式
SSR → SPA


# Page. 27

![Page Image](https://bcdn.docswell.com/page/57GL88Z2EL.jpg)

検討①：Nuxtを外し、Vite + Vueで組み直す
アプリ基盤
Nuxt → Vite + Vue
コンテンツ基盤
Nuxt Content → Ox Content
検証結果：置き換えるコード量があまりにも膨大
Nuxt では特に意識せず使える細かい機能に頼っていることに気づかされた
File-based Routing、useFetch / useAsyncData、middleware、layout
既存アプリを移行するのは現実的ではない
NuxtHub Module も、Cloudflare を使い続ける前提ならやはり便利
⇒ 周辺機能の再実装コストが目的に見合わず、Nuxt をそのまま活用
レンダリング方式
SSR → SPA


# Page. 28

![Page Image](https://bcdn.docswell.com/page/4EQY99L9JP.jpg)

検討②：Nuxt ContentをOx Contentへ置き換える
アプリ基盤
Nuxt → Vite + Vue
コンテンツ基盤
Nuxt Content → Ox Content
レンダリング方式
SSR → SPA
検討理由： Ox Content が特定のFW に依存しないなら、Nuxt とも組み合わせられるはず
Nuxt は維持しつつ、D1 への依存を解消するためにコンテンツ処理だけを置き換える
ページや Server API の役割は変えず、記事取得の query を差し替えられるか検証する
Before：Nuxt Content
After：Ox Content


# Page. 29

![Page Image](https://bcdn.docswell.com/page/KJ4WXX5R71.jpg)

Nuxt へ Ox Content plugin を登録
import { resolve } from &quot;node:path&quot;;
import { defineCollection, oxContent } from &quot;@ox-content/vite-plugin&quot;;
import { defineNuxtConfig } from &quot;nuxt/config&quot;;
import type { Plugin as RollupPlugin } from &quot;rollup&quot;;
const oxContentPlugin = oxContent({
srcDir: resolve(&quot;content&quot;),
docs: false,
collections: {
posts: defineCollection({
source: &quot;posts/**/*.mdx&quot;,
}),
},
}) as RollupPlugin[];
export default defineNuxtConfig({
nitro: {
rollupConfig: { plugins: oxContentPlugin },
},
});
Ox Content を Nitro の Rollup pluginと
して登録することで動作
でコレクションを定義、
virtual:ox-content/collections を利
用可能にする
oxContent()
※ virtual:ox-content/collections を参照する
Server API ( server/ 配下)をバンドルするのは
Nitro 側のパイプラインであるため、Vite プロパテ
ィではなく Nitro への登録が必要


# Page. 30

![Page Image](https://bcdn.docswell.com/page/LE1YNNWV7G.jpg)

検証結果：十分な改善効果が得られた
アプリ基盤
Nuxt → Vite + Vue
コンテンツ基盤
Nuxt Content → Ox Content
レンダリング方式
SSR → SPA
既存のページやAPIを大きく作り直さずに移行
D1 Database を通さず記事を取得し、表示速度を改善
計測対象
Before（Nuxt Content）
After（Ox Content）
最新記事一覧
465 ms
13 ms
カテゴリー別一覧
21 ms
10 ms
※ Nuxt Content v3 は SQL ベースのため、Workers 上では D1 利用が前提
D1 依存を解消するためにも仮想モジュール形式の Ox Content に切り替える必要があった
短縮率
97.2%
52.4%


# Page. 31

![Page Image](https://bcdn.docswell.com/page/GEWG4463J2.jpg)

Lighthouse スコアの比較
Before
After


# Page. 32

![Page Image](https://bcdn.docswell.com/page/47ZL22YGJ3.jpg)

検討③：SSRをやめて、記事を Client で処理する
アプリ基盤
Nuxt → Vite + Vue
コンテンツ基盤
Nuxt Content → Ox Content
レンダリング方式
SSR → SPA
検証結果：Ox Content は仮想モジュールのため、記事全体を含めると CF Workers のサイズ制限を超過
⇒ 回避のために有料プランに上げる・外部ストレージ等へ逃がすのは本末転倒なので、SSR を維持
※ SSG は、1000以上のページを事前生成すると時間がかかりすぎるため最初から検討しなかった


# Page. 33

![Page Image](https://bcdn.docswell.com/page/YJ6WVVDQJV.jpg)

技術刷新の方針を見直す
アプリ基盤
Nuxt → Vite + Vue
コンテンツ基盤
Nuxt Content → Ox Content
記事取得方法を置き換える小さな変更で、取得時間を大きく短縮できた
⇒ 脱 Nuxt や脱 SSR が手段ではなく目的になってしまっていたのではないか？
費用対効果の低い Nuxt・SSR の脱却はせず、Ox Content への置き換えに注力する
レンダリング方式
SSR → SPA


# Page. 34

![Page Image](https://bcdn.docswell.com/page/GJ5MDD32J4.jpg)

4. Markdown 変換の高速化


# Page. 35

![Page Image](https://bcdn.docswell.com/page/9E2988Z17R.jpg)

課題：公開前の Markdown 変換処理をさらに高速化したい
これまでの変換処理： unified, remark, rehype を組み合わせたパイプライン
const html = await unified()
.use(remarkParse)
.use(remarkRehype)
.use(rehypeStringify)
.process(markdown);
安定性は高いが、変換処理の速度には改善の余地があった
Markdown → @ox-content/napi
Node.js から使える native API → HTML
Ox Content が提供している、 Markdown のパース・HTML 変換のみの API に置き換えてみる


# Page. 36

![Page Image](https://bcdn.docswell.com/page/D7Y466R4EM.jpg)

Markdown (frontmatter) のパースを Ox Content に移行
BEFORE
AFTER
import { parseFrontMatter } from &quot;remark-mdc&quot;;
import { prepareSource } from &quot;@ox-content/napi&quot;;
fs.readFile(path, &quot;utf-8&quot;, (err, data) =&gt; {
const { frontmatter, content } = prepareSource(data, {
frontmatter: true,
});
});
fs.readFile(path, &quot;utf-8&quot;, (err, data) =&gt; {
const parsed = parseFrontMatter(data);
const { data: frontmatter, content } = parsed;
});
remark-mdc
2.977 ms
→
94.3% 短縮
Ox Content (prepareSource)
0.171 ms


# Page. 37

![Page Image](https://bcdn.docswell.com/page/VENYZZD6J8.jpg)

記事本文の HTML 変換を Ox Content に移行
BEFORE
AFTER
import rehypeStringify from &quot;rehype-stringify&quot;;
import remarkParse from &quot;remark-parse&quot;;
import remarkRehype from &quot;remark-rehype&quot;;
import { unified } from &quot;unified&quot;;
import { parseAndRender } from &quot;@ox-content/napi&quot;;
const { html, errors } = parseAndRender(markdown);
const html = await unified().use(remarkParse)
.use(remarkRehype)
.use(rehypeStringify)
.process(markdown);
処理が高速化されただけでなく、コードや依存関係もシンプルに
remark + rehype
28.32 ms
→
98.7% 短縮
Ox Content (parseAndRender)
0.37 ms


# Page. 38

![Page Image](https://bcdn.docswell.com/page/Y79PZZGRE3.jpg)

5. Ox Content での全文検索の実装


# Page. 39

![Page Image](https://bcdn.docswell.com/page/G78D11VG7D.jpg)

現状：全文検索は Nuxt Content に任せていた
検索対象の本文を Nuxt Content で検索
カテゴリーやタグで検索するのとほぼ同じ実装
LIKE 演算子をサポートしているため、全文検索を意識して実装する必要はなかった
// Before：Nuxt Content queryで本文をLIKE検索
import { queryCollection } from &quot;@nuxt/content/server&quot;;
const searchResults = await queryCollection(&quot;posts&quot;)
.where(&quot;contentPlainText&quot;, &quot;LIKE&quot;, `%${keyword}%`)
.order(&quot;date&quot;, &quot;DESC&quot;)
.all();
参考：https://content.nuxt.com/docs/utils/query-collection


# Page. 40

![Page Image](https://bcdn.docswell.com/page/L7LMQQ5XJR.jpg)

Ox Contentで全文検索を実装できるか検証
Nuxt Contentと同じように、 Ox Content も LIKE 演算子をサポートしている
const matches = await queryCollection(&quot;posts&quot;)
.where(&quot;contentPlainText&quot;, &quot;LIKE&quot;, `%${keyword}%`)
.all();
⇒ 本文を JSON に入れると、 CF Workers のサイズ制限を超過（SPA化と同じ問題）
外部ライブラリ (Pagefind、Algolia など) を導入するか
自前で検索機能を実装するか


# Page. 41

![Page Image](https://bcdn.docswell.com/page/4EMY1133EW.jpg)

結論：検索だけを自前実装した
D1 に記事のタイトル・本文を登録し、単語ごとの索引から検索に一致する記事IDを取得
Markdown の事前変換時に、公開記事のID・タイトル・本文をD1へ登録
検索結果として返却された記事IDから、Ox Contentで記事情報を取得
-- 記事データと全文検索用の索引を更新
INSERT INTO posts_search (id, title, body)
VALUES (:id, :title, :body)
ON CONFLICT(id) DO UPDATE SET title = excluded.title,
body = excluded.body;
DELETE FROM posts_search_index WHERE id = :id;
INSERT INTO posts_search_index (id, title, body)
VALUES (:id, :title, :body);
// server/utils/useContentQuery.ts
const rows = await db.all(sql`
SELECT id FROM posts_search_index
WHERE posts_search_index MATCH ${keyword}
`);
const ids = rows.map(({ id }) =&gt; id);
// 検索結果の記事IDでOx Contentを絞り込む
query = query.where(&quot;postId&quot;, &quot;IN&quot;, ids);
実装には AI を活用しながら、最小限の機能に抑えることでメンテナンス性を担保


# Page. 42

![Page Image](https://bcdn.docswell.com/page/PER9LLQRJ9.jpg)

効果：表示時間を短縮し、D1読み取りを削減
検索は新サイト内では相対的に遅いが、利用頻度を踏まえて許容
計測対象
検索
旧サイト
1.11 s
新サイト
547 ms
短縮率
51%
D1参照を全文検索時だけに限定したことで、D1 Database の読み取り回数が激減し、コスト圧縮に成功


# Page. 43

![Page Image](https://bcdn.docswell.com/page/P7XQWW5YEX.jpg)

まとめ：移行結果と技術選定の学び


# Page. 44

![Page Image](https://bcdn.docswell.com/page/37K9VV647D.jpg)

大規模な作り直しではなく「部分刷新」という選択
基盤は大きく変えず、ボトルネックだけを Ox Content に置き換えた
変更前
Markdown
remark + rehype
Nuxt Content
D1
Nuxt Client
Server API
Cloudflare Workers
Server API
Nuxt Client
変更後
Markdown
@ox-content/napi
Ox Content
Cloudflare Workers
「脱〇〇」を目的にせず、費用対効果を見極める
大規模な作り直しは魅力的だが、再実装コストや Workers の制約に対して割に合わない
課題の根本（D1 依存・変換速度）だけをピンポイントで解消するのが最も効果的だった


# Page. 45

![Page Image](https://bcdn.docswell.com/page/LJ3WMM6DJ5.jpg)

フレームワークやライブラリは「必要な機能だけ」を使う
「Nuxt を辞めれば複雑性が下がる」という単純な話ではなかった
Nuxt は無理に剥がさず、安定したルーティングやサーバー基盤として活用する
一方で Nuxt Modules を重ねて密結合にすると、ブラックボックス化や移行の障壁になる
Nuxt Modules を減らすだけでも「Nuxt の依存度を下げる」ことに繋がる
ライブラリの機能も、必要な範囲だけを疎結合に利用する
Ox Content も全機能（ページ生成等）ではなく、コンテンツ管理や Markdown 変換 API のみを利用
全文検索も D1 を最小限（ID・タイトル等）に絞ることで、シンプルさと保守性を両立
汎用的な Vite / Rollup プラグインを活用し、ポータビリティを保つ


# Page. 46

![Page Image](https://bcdn.docswell.com/page/8JDK66V2EG.jpg)

AI時代だからこそ、要件に合った「筋の良い」技術を選ぶ
技術方針さえ決まれば、実装の置き換えは AI によって劇的に低コスト化
今回の移行作業でも、具体的なコードの実装や query の書き換えはほとんど AI を活用
だからこそ、「筋の良い技術」を判断することが人間の重要な役割
Nuxt も Ox Content も、すべての技術に一長一短がある
技術選定に銀の弾丸はない
制約や要件、将来性や拡張性を考慮して、技術選定を行うことが重要
(私は) Vue, Vite, Nuxt, Oxc の開発チームの&quot;筋の良さ&quot;を信頼しているので、積極的に使っていきたい


# Page. 47

![Page Image](https://bcdn.docswell.com/page/VEPKYYVL78.jpg)

ご清聴ありがとうございました！


