2
2

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

【Sanity】アフィリエイトリンクを「ハードコード」するのはやめろ。GROQで管理する"収益最大化"のためのデータ設計 💰📊

Posted at

はじめに

個人開発でメディアやブログを運営している皆さん、アフィリエイトリンク(ASPタグ) をどう管理していますか?

まさか、記事のMarkdownの中に直接 <a href="https://px.a8.net/..."> と書いていませんか?

記事が10本ならそれでもいいでしょう。しかし、記事が100本を超えたとき、ASPから 「リンクの仕様が変わりました」 というメールが届いたらどうしますか?
あるいは、 「A社よりB社の方が単価が高い」 とわかったとき、全記事をgrepして置換しますか?

それはエンジニアの仕事ではありません。 「技術的負債」 です。

私は現在、Next.js × Sanity で構築した多言語メディア ibis (アイビス) を運営しています。
本記事では、Sanityの強力なクエリ言語 GROQ を活用し、アフィリエイト商品を「データ」として管理することで、メンテナンスコストをゼロにする設計手法を公開します。

この記事で得られる知見

  1. 🧱 Schema Design: 商品情報を「参照(Reference)」として管理する設計
  2. 🔍 GROQ: 記事データと一緒に商品スペックを引っこ抜くクエリ術
  3. ⚛️ 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 (アイビス)

この「データ駆動型アフィリエイト設計」によって運用されているのが、以下のメディアです。

ibis (アイビス) - 外国人向け多言語生活ガイド

ibis_pricing_table.png

(※実際の記事内にある比較テーブルや、サービス紹介カードはすべてGROQから動的に生成されています)

特に「SIMカード比較」や「送金サービス比較」のページでは、複雑なスペック表をSanityのデータのみで構築しています。


まとめ:コンテンツを「文字」ではなく「データ」として扱え

アフィリエイトリンクをハードコードすることは、データベースを使わずにCSVで顧客管理をするようなものです。

Sanity と GROQ を使えば、「コンテンツの正規化」 が可能になります。

  • リンク切れのリスクゼロ
  • ASPの切り替え(A8 → ValueCommerce)が一瞬
  • デザインとデータの完全分離

エンジニアの皆さん、アフィリエイトサイトこそ、モダンな技術設計で「楽」をしましょう。


🔗 著者情報

山本 勇志 (Yushi Yamamoto)
株式会社プロドウガ代表 / フルスタックエンジニア

「AIは育ててこそ、最強の参謀になる」。
Next.js × Sanity × AI を駆使し、メンテナンスフリーなメディア構築を実践中。

📢 開発・導入のご相談
「Sanityのスキーマ設計に悩んでいる」「Next.jsでのGROQ活用法を知りたい」といったご相談を承っています。
XのDMまたはプロフィールリンクよりお気軽にご連絡ください。


2
2
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
2
2

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?