Next.jsブログの関連記事を、カテゴリー・タグ・公開日のスコアで自動選出する【多様性より関連性を選んだ話】
このブログの記事末にある関連記事は、カテゴリー一致・共通タグ数・公開日の近さを足し合わせたスコアで自動選出しています。「別カテゴリーを最低1件混ぜる」ルールが関連性の薄い記事を押し込んでいた問題と、それを最低スコアの閾値で解決した経緯をコード付きで解説します。
AIツクリラボは、Next.js(App Router)で構築しています。始める前の経験はWordPressで記事を書いたことがある程度で、Next.jsもApp Routerも今回が初めてでした。このページでは、その状態からブログを作り、今も運用を続けるまでの流れを一本の道筋として整理します。個々の実装の詳細は、各見出しからリンクしている記事にまとめているので、気になった部分から読み進めてください。
作ったのはこのブログ自体です。データベースやCMSの管理画面は使わず、記事をfrontmatter付きのMarkdownファイルとしてリポジトリに直接置き、Next.jsのApp Routerでビルド時に静的なHTMLへ変換しています。カテゴリーは「AIツール」「レンタルサーバー・VPS」「ノーコード・ローコード」「Next.js制作Tips」の4つで、このNext.js制作Tipsカテゴリーには、この文章を書いている時点で8本の記事が公開されています(執筆・公開準備中のものを含めると10本)。
もともとの経験はWordPressで記事を書いたことがある程度で、Next.jsもReactのサーバーコンポーネントも初めて触りながら組み立てました。CMSに慣れていたはずなのに、なぜあえてコードで作るNext.jsを選んだのかという一番手前の判断は、Next.jsとWordPress、ブログを作るならどっちがいい?にまとめています。
技術選定は、大きく3段階で決めています。
1つ目は「Next.js自体を選ぶか」です。WordPressの管理画面から書ける手軽さより、コードで作る自由度と表示速度を優先したいという判断が先にありました。この比較はNext.jsとWordPress、ブログを作るならどっちがいい?の通りです。
2つ目は「App RouterかPages Routerか」です。今後の新機能がApp Router側に追加されていく方針であること、layout.tsxでヘッダー・フッターのような共通レイアウトをシンプルに書けることを理由に、App Routerを選びました。詳しい比較はNext.jsのApp RouterとPages Router、なぜApp Routerを選んだかで解説しています。
3つ目は「CMSを使うか、Markdownで管理するか」です。記事数がまだ少ない個人ブログの規模では、CMSの管理画面よりもGitでファイルごと管理するほうが、変更履歴が残り、実質的なバックアップも兼ねられると判断しました。この判断が、後述する記事管理の仕組み全体の土台になっています。
実際のディレクトリ構成は、主要な部分だけ抜き出すと次のようになっています。
src/
app/
blog/[slug]/page.tsx # 記事詳細ページ
blog/[slug]/opengraph-image.tsx
category/[slug]/page.tsx # カテゴリーページ(このページ)
tags/[tag]/page.tsx
sitemap.ts / robots.ts
layout.tsx
components/ # UIコンポーネント
lib/
posts.ts # 記事の読み込み・公開判定
categories.ts # カテゴリー本文の読み込み
markdown.ts # Markdown→HTML変換
site-config.ts # サイト名・カテゴリー定義
types/ # frontmatterの型定義
content/
posts/*.md # 記事本文
categories/*.md # カテゴリーページの本文(このファイル)
app/配下のディレクトリ名がそのままURLになる、という規約は、Pages Routerのpages/ディレクトリとは感覚が異なりました。この違いはApp RouterとPages Routerの違いでも比較表として整理しています。
記事はすべてfrontmatter付きのMarkdownで管理していて、title・description・date・category・tagsが基本項目です。それ以外に、実際にどこまで確かめた記事かを示すexperienceLabel、アフィリエイトリンクの有無を示すhasAffiliate、構造化データ用のfaqなども任意で持たせています。
公開日を管理画面から設定するのではなく、dateに未来の日付を指定しておくだけで自動的に公開される「予約投稿」の仕組みも実装しています。CMSを使わない代わりに、frontmatterの日付だけで公開・非公開を切り替える具体的な実装はNext.jsブログに予約投稿機能を実装するにまとめています。
SEOまわりは、Next.jsのMetadata API・JSON-LD構造化データ・sitemap.tsによる自動生成・OGP画像の自動生成・記事同士の内部リンクという要素で構成しています。
タイトル・description・canonical URLをコードで管理する方法、ArticleとBreadcrumbListのJSON-LD、sitemap.ts/robots.tsによるサイトマップの自動生成はNext.jsブログを高速化する画像・フォント最適化Tipsにまとめています。
SNSシェア時の画像は記事ごとにnext/ogで自動生成していて、その実装はNext.jsでOGP画像を自動生成するで解説しています。
記事同士をどうつなげるかという内部リンクの設計方針はブログの内部リンク設計の考え方、検索エンジンへの登録とアクセス解析の導入手順はNext.jsブログにGoogle Search Console・Analyticsを導入する手順にまとめています。
ホスティングはVercelを使い、記事ページはgenerateStaticParamsでビルド時にすべて静的HTMLとして生成しています。公開後に内容が頻繁に変わらないアフィリエイトブログの性質上、静的生成を第一選択にするという考え方はNext.jsブログを高速化する画像・フォント最適化Tipsの通りです。
運用面ではrevalidate = 3600という設定で、1時間おきにページを再検証させています。これにより、予約投稿の記事が公開日を迎えたときも、手動で再デプロイしなくても自動的に反映されます。記事を追加・更新する作業は、Markdownファイルを編集してGitにpushするだけで完結していて、CMSの管理画面を開く工程はありません。
実装しながら実際につまずいた点を、記事へのリンクつきで一覧にしておきます。
"use client"をどこに付けるかの判断基準で最初は混乱した → App RouterとPages Routerの違いparamsがPromiseを返す仕様に変わったことを見落とし、値が取れずに動かなかった → Next.jsブログを高速化する画像・フォント最適化Tips記事数が少ないうちは気づきにくいものの、増えてから効いてくる工夫として、ブログの内部リンク設計の考え方もあわせて参考にしてください。
このブログの記事末にある関連記事は、カテゴリー一致・共通タグ数・公開日の近さを足し合わせたスコアで自動選出しています。「別カテゴリーを最低1件混ぜる」ルールが関連性の薄い記事を押し込んでいた問題と、それを最低スコアの閾値で解決した経緯をコード付きで解説します。
AI日本株自動売買の結果を公開している運用状況ページ(/results)は、自宅PCのPythonがJSONを書き出してgit pushし、Next.jsがそれを読んで表示する構成です。ライブラリを使わないSVGチャート、日経平均・TOPIXとの比較線、少ない母数を誇張しない表示の工夫をコード付きで解説します。
このブログのサイドバーにある「閲覧数ランキング」は、Google Analytics Data APIからサーバー側で実際のページビュー数を取得し、その順に並べています。認証情報をクライアントに渡さない構成と、実際に踏んだ2つの不具合(空の結果がキャッシュされ続ける・5件に満たない)をコード付きで解説します。
運用状況ページに自作のSVGチャートを追加した直後、Hydrationエラーが出始めました。最初は別の箇所を疑って的外れな修正をしてしまい、切り分け直して特定した原因はチャート内のSVGの<title>要素でした。その経緯と切り分け手順をまとめます。
Next.jsのApp Routerが提供するMetadata APIの使い方を、このブログでの実装例とあわせてまとめました。
Next.jsのApp Routerで必ず出てくる「サーバーコンポーネント」「クライアントコンポーネント」の違いと、実際の使い分け方をこのブログの実例で解説します。
CMSを使わずに、Markdownのfrontmatterの日付だけで予約投稿を実現する仕組みを実装した記録です。あえて日本時間21時固定にした運用上の理由も解説します。
next/ogのImageResponseを使って記事ごとのOGP画像を自動生成する方法と、あえてテキストを描画しない設計にした理由を実装をもとに解説します。
Next.jsにはApp RouterとPages Routerという2つの仕組みがあります。このブログでApp Routerを選んだ理由と、両者の違いを整理しました。
記事同士をどうつなげるかという内部リンクの設計方針を、実際にこのブログで行っている工夫とあわせて解説します。
端末のダークモード設定に自動追従しつつ、手動でライト/ダーク/端末追従を切り替えられるボタンをNext.jsで実装する方法を、実際のコードとあわせて解説します。
Next.jsで作ったブログをGoogle Search ConsoleとGoogle Analyticsに登録する具体的な手順を、実際の設定内容とあわせて解説します。
アフィリエイトブログをNext.jsで作るかWordPressで作るか迷っている人向けに、それぞれのメリット・デメリットを実体験ベースで整理しました。運用コストや複数人での執筆のしやすさについても触れています。
Next.jsで作ったブログの表示速度を上げるために実際に行った、画像最適化・フォント設定・静的生成のポイントをまとめました。