はじめに
個人開発でメディアやブログを運営している皆さん、アフィリエイトリンク(ASPタグ) をどう管理していますか?
まさか、記事のMarkdownの中に直接 <a href="https://px.a8.net/..."> と書いていませんか?
記事が10本ならそれでもいいでしょう。しかし、記事が100本を超えたとき、ASPから 「リンクの仕様が変わりました」 というメールが届いたらどうしますか?
あるいは、 「A社よりB社の方が単価が高い」 とわかったとき、全記事をgrepして置換しますか?
それはエンジニアの仕事ではありません。 「技術的負債」 です。
私は現在、Next.js × Sanity で構築した多言語メディア ibis (アイビス) を運営しています。
本記事では、Sanityの強力なクエリ言語 GROQ を活用し、アフィリエイト商品を「データ」として管理することで、メンテナンスコストをゼロにする設計手法を公開します。
この記事で得られる知見
- 🧱 Schema Design: 商品情報を「参照(Reference)」として管理する設計
- 🔍 GROQ: 記事データと一緒に商品スペックを引っこ抜くクエリ術
- ⚛️ Next.js: データを流し込むだけの「動的比較テーブル」の実装
1. 脱・ハードコード。「商品」を独立したスキーマにする
Sanityは「ヘッドレスCMS」ですが、その実態は「JSONドキュメントストア」に近いです。
記事(Post)の中にリンクを書くのではなく、商品(Service) という独立したスキーマを定義します。
Schema Example: service.ts
例えば、「Village House」という賃貸サービスを管理する場合、以下のようなスキーマを定義します。
// schemas/service.ts
import { defineType, defineField } from 'sanity'
export default defineType({
name: 'service',
title: 'Service / Product',
type: 'document',
fields: [
defineField({
name: 'name',
title: 'Service Name',
type: 'string', // 例: Village House
}),
defineField({
name: 'affiliateLink',
title: 'Affiliate URL',
type: 'url', // ここを一箇所変えれば全記事に反映される
}),
defineField({
name: 'features',
title: 'Features',
type: 'array',
of: [{ type: 'string' }], // 例: ["敷金礼金0円", "保証人不要"]
}),
defineField({
name: 'rating',
title: 'Rating',
type: 'number', // 例: 4.8
}),
defineField({
name: 'logo',
title: 'Logo',
type: 'image',
}),
],
})
これで、「Village House」は単なる文字列ではなく、「属性を持ったオブジェクト」 になりました。
2. 記事からは「参照」するだけ
記事(Post)の本文(Portable Text)では、URLを貼るのではなく、先ほど作った service ドキュメントを 参照(Reference) します。
Sanityの管理画面(Studio)では、ライターは「Village House」を選択するだけ。
裏側では以下のようなデータが保存されます。
{
"_type": "reference",
"_ref": "document-id-of-village-house"
}
これにより、Single Source of Truth (信頼できる唯一の情報源) が確立されます。
ASPのリンクが変更になっても、service ドキュメントの affiliateLink を1回書き換えるだけで、サイト内の数千箇所のリンクが瞬時に更新されます。
3. GROQの魔術:Joinしてデータを取得する
ここからがSanityの真骨頂、GROQ (Graph-Relational Object Queries) の出番です。
GraphQLよりも柔軟なこのクエリ言語を使えば、記事を取得するついでに、参照している商品の詳細データ(ロゴ、リンク、評価など)を Join(結合) して取得できます。
Use Case: 「おすすめSIMカード比較表」を作る
例えば、「SIMカード」タグがついた商品を、評価(Rating)の高い順に取得し、比較テーブルを作りたい場合。
Next.js側で投げるクエリはこれだけです。
// 評価順にSIMカード情報を取得するGROQクエリ
*[_type == "service" && "sim-card" in tags] | order(rating desc) {
name,
"url": affiliateLink,
"logoUrl": logo.asset->url,
features,
rating,
// 料金プランもJoinして取得
plans[]->{
dataAmount,
price
}
}
これを実行すると、フロントエンドには整形済みのJSONが返ってきます。
[
{
"name": "Mobal SIM",
"url": "https://affiliate.mobal.com/...",
"rating": 4.8,
"features": ["No Contract", "English Support"],
...
},
{
"name": "Rakuten Mobile",
...
}
]
4. Next.jsでコンポーネントに流し込む
あとは、Next.js (React) 側でこのJSONをコンポーネントに流し込むだけです。
HTMLを直書きする必要はありません。
// components/ComparisonTable.tsx
export const ComparisonTable = ({ services }) => {
return (
<div className="grid gap-4">
{services.map((service) => (
<div key={service.name} className="border p-4 rounded-lg">
<img src={service.logoUrl} alt={service.name} className="h-12" />
<div className="font-bold">{service.name}</div>
<div className="text-yellow-500">★ {service.rating}</div>
{/* ここが重要:リンクはデータから動的に生成 */}
<a
href={service.url}
target="_blank"
rel="sponsored"
className="bg-blue-600 text-white px-4 py-2 rounded"
>
公式サイトを見る
</a>
</div>
))}
</div>
);
};
この設計にしておけば、「順位を入れ替えたい」 と思ったときも、Sanity側で rating の数字をいじるだけ。
Next.jsのコードを修正してデプロイし直す必要すらありません。
5. 実稼働デモ:ibis (アイビス)
この「データ駆動型アフィリエイト設計」によって運用されているのが、以下のメディアです。
(※実際の記事内にある比較テーブルや、サービス紹介カードはすべてGROQから動的に生成されています)
特に「SIMカード比較」や「送金サービス比較」のページでは、複雑なスペック表をSanityのデータのみで構築しています。
まとめ:コンテンツを「文字」ではなく「データ」として扱え
アフィリエイトリンクをハードコードすることは、データベースを使わずにCSVで顧客管理をするようなものです。
Sanity と GROQ を使えば、「コンテンツの正規化」 が可能になります。
- リンク切れのリスクゼロ
- ASPの切り替え(A8 → ValueCommerce)が一瞬
- デザインとデータの完全分離
エンジニアの皆さん、アフィリエイトサイトこそ、モダンな技術設計で「楽」をしましょう。
🔗 著者情報
山本 勇志 (Yushi Yamamoto)
株式会社プロドウガ代表 / フルスタックエンジニア
「AIは育ててこそ、最強の参謀になる」。
Next.js × Sanity × AI を駆使し、メンテナンスフリーなメディア構築を実践中。
- X (Twitter): @UshiAiPro
- Company: PRODOUGA
- Media: ibis - Japan Living Guide
📢 開発・導入のご相談
「Sanityのスキーマ設計に悩んでいる」「Next.jsでのGROQ活用法を知りたい」といったご相談を承っています。
XのDMまたはプロフィールリンクよりお気軽にご連絡ください。
