Next.js MDX ブログに Amazon 商品カードを実装する
WordPress から Next.js + MDX に移行したあと、記事内の Amazon アフィリエイトリンクが全滅した。
WordPress で使っていた Rakuten ウィジェットや Amazon の HTML は、MDX v2 では動かないことが判明。
そこで ASIN を渡すだけで商品情報を自動取得・表示する <AmazonCard> コンポーネントを作った。
Rakuten ウィジェットが MDX で壊れる理由
WordPress からエクスポートした記事には、こんな HTML が入っていた:
<table border="0" cellpadding="0" style="width:500px">
<tr><td style="font-size:12px">...</td></tr>
</table>MDX v2 以降では、本文中の HTML は JSX として解釈される。
JSX では style 属性に CSS 文字列は使えない(オブジェクトが必要)し、cellpadding も cellPadding に直さないといけない。
結果、compileMDX がエラーを出してページが 404 になっていた。
対策: 壊れた HTML を削除して、後述の AmazonCard に置き換える。
Amazon Creators API とは
Amazon アソシエイトに紐づく新しい API。
従来の Product Advertising API(PA API v5)とは別で、2025年以降に順次展開されている。
| PA API v5 | Creators API | |
|---|---|---|
| 認証 | AWS Signature V4 | LWA(Login with Amazon) |
| 申請条件 | 180日以内に1成約 | アソシエイトアカウント |
| SDK | paapi5-nodejs-sdk | creatorsapi-nodejs-sdk |
日本向けは バージョン 3.3(FE / Far East)を使う。
セットアップ
ステップ1: SDK をインストール
pnpm add creatorsapi-nodejs-sdknpm に公開されているので
pip install不要で使える(2026年3月時点 v1.2.0)。ステップ2: Creators API の認証情報を取得
- Amazon アソシエイト・セントラル → 「ツール」→「Creators API」
- アクセスキーとシークレットキーを発行
- 有効化まで最大 48 時間かかる(発行直後は 403 になる)
ステップ3: 環境変数を設定
プロジェクトルートの
.env.localに追記:AMAZON_CREATOR_API_ACCESS_KEY=(アクセスキー) AMAZON_CREATOR_API_SECRET_KEY=(シークレットキー).env.localの変更は dev server の起動時にのみ読み込まれる。
追記後はpnpm devの再起動が必要。ステップ4: next.config.ts に追加
SDK が Node.js の
superagentを使うため、Webpack バンドルから除外する:// next.config.ts serverExternalPackages: ["shiki", "creatorsapi-nodejs-sdk", "superagent"],ステップ5: 型定義ファイルを作成
SDK には TypeScript 型定義がないので
types/creatorsapi-nodejs-sdk.d.tsを作る:declare module "creatorsapi-nodejs-sdk" { export class ApiClient { setCredentialId(id: string): void; setCredentialSecret(secret: string): void; setVersion(version: string): void; } export class GetItemsRequestContent { constructor(partnerTag: string, itemIds: string[]); resources: string[]; } export class DefaultApi { constructor(client: ApiClient); getItems( marketplace: string, request: GetItemsRequestContent ): Promise<{ itemsResult?: { items?: Array<{ itemInfo?: { title?: { displayValue?: string } }; images?: { primary?: { medium?: { url?: string } } }; offersV2?: { listings?: Array<{ price?: { displayAmount?: string }; }>; }; }>; }; }>; } }
lib/amazon.ts(APIユーティリティ)
import { ApiClient, DefaultApi, GetItemsRequestContent } from "creatorsapi-nodejs-sdk";
const MARKETPLACE = "www.amazon.co.jp";
const PARTNER_TAG = "(アソシエイトID)";
const VERSION = "3.3";
export type AmazonProduct = {
title: string;
imageUrl: string | null;
price: string | null;
affiliateUrl: string;
};
function createApiClient(): ApiClient {
const client = new ApiClient();
client.setCredentialId(process.env.AMAZON_CREATOR_API_ACCESS_KEY!);
client.setCredentialSecret(process.env.AMAZON_CREATOR_API_SECRET_KEY!);
client.setVersion(VERSION);
return client;
}
export async function getAmazonProduct(asin: string): Promise<AmazonProduct | null> {
if (!process.env.AMAZON_CREATOR_API_ACCESS_KEY) return null;
try {
const client = createApiClient();
const api = new DefaultApi(client);
const request = new GetItemsRequestContent(PARTNER_TAG, [asin]);
request.resources = [
"images.primary.medium",
"itemInfo.title",
"offersV2.listings.price",
];
const response = await api.getItems(MARKETPLACE, request);
const item = response?.itemsResult?.items?.[0];
if (!item) return null;
return {
title: item.itemInfo?.title?.displayValue ?? asin,
imageUrl: item.images?.primary?.medium?.url ?? null,
price: item.offersV2?.listings?.[0]?.price?.displayAmount ?? null,
affiliateUrl: `https://www.amazon.co.jp/dp/${asin}/?tag=${PARTNER_TAG}`,
};
} catch (err) {
console.error(`[AmazonCard] getItems failed for ${asin}:`, err);
return null;
}
}components/AmazonCard.tsx(Server Component)
import { getAmazonProduct } from "@/lib/amazon";
type Props = {
asin: string;
title?: string;
};
export async function AmazonCard({ asin, title: fallbackTitle }: Props) {
const product = await getAmazonProduct(asin);
const title = product?.title ?? fallbackTitle ?? asin;
const imageUrl = product?.imageUrl ?? null;
const price = product?.price ?? null;
const url = product?.affiliateUrl
?? `https://www.amazon.co.jp/dp/${asin}/?tag=(アソシエイトID)`;
return (
<a
href={url}
target="_blank"
rel="noopener noreferrer sponsored"
className="not-prose my-6 flex items-center gap-4 rounded-lg border ..."
>
{imageUrl ? (
<img src={imageUrl} alt={title} width={64} height={64} className="..." />
) : (
<div className="...">(プレースホルダー)</div>
)}
<div className="flex-1 min-w-0">
<p className="text-sm font-medium ...">{title}</p>
{price && <p className="mt-1 text-sm ...">{price}</p>}
<p className="mt-1 text-xs text-amber-600 ...">Amazon で見る →</p>
</div>
</a>
);
}async な Server Component なので、ビルド時または SSR 時に API を叩いて商品データを埋め込む。
"use client" は不要。
MDX に登録して記事で使う
// components/Mdx.tsx
import { AmazonCard } from "./AmazonCard";
export const mdxComponents = {
AmazonCard,
// ... 他のコンポーネント
};MDX 記事内で以下のように使える:
<AmazonCard asin="B08CXRJFNG" title="YAMANI スウェー プロテクター" />API が有効な場合 → 商品画像・価格・タイトルを自動取得して表示
API が無効または 403 の場合 → title prop のテキストのみ表示(フォールバック)
ハマりポイントまとめ
API 発行直後は 403 になる
「AccessDeniedException」が返ってくるが、これは有効化待ちの状態。48時間待てば解消する。
.env.local を更新したら再起動が必要
Next.js の dev server は .env.local を起動時にしか読まない。
変数を追加・変更したら再起動しないと反映されない。
Rakuten の HTML ウィジェットは MDX で使えない
style="..." 文字列が JSX 非対応でコンパイルエラーになる。削除して <AmazonCard> に置き換えること。
SDK に TypeScript 型定義がない
types/creatorsapi-nodejs-sdk.d.ts を自前で用意する必要がある。
まとめ
ASIN と title を渡すだけで商品カードが生成できるようになった。
API が有効になれば画像・価格も自動取得されるので、手動で用意するものがなくなる。
WordPress 時代のアフィリエイトリンクは MDX 移行時に全滅しやすいので、
この構成に統一しておくと記事を書くたびの手間が減る。