remark-affiliate-card
v1.0.0
Published
remark plugin that renders Amazon / Rakuten / Yahoo! affiliate product cards
Maintainers
Readme
remark-affiliate-card
A remark plugin that renders Amazon product cards from a ::amazon
directive, with optional Rakuten and Yahoo! Shopping links alongside.
この記事で使ったウマです。
::amazon{asin="B003F5VYKW"}Product data comes from the Amazon Creators API, is committed to your repository as JSON, and is refreshed with a CLI — so builds and deploys never depend on the API being up.
Install
bun add remark-affiliate-card remark-directiveAny unified pipeline can use it:
import remarkDirective from 'remark-directive'
import remarkParse from 'remark-parse'
import { createRemarkAmazon } from 'remark-affiliate-card'
import { unified } from 'unified'
unified()
.use(remarkParse)
.use(remarkDirective)
.use(...createRemarkAmazon(options))The default export is the raw remark plugin, but it expects already-resolved
product data and credentials — nothing a consumer should assemble by hand.
createRemarkAmazon(options) does that resolution and hands back a
[plugin, resolvedOptions] pair, which is why it is spread into .use()
here rather than passed as the plugin itself.
remark-directive is yours to register: nothing parses ::amazon on its own,
and without it the directive reaches the page as literal text. Sites that only
convert bare Amazon URLs (bareUrls: true) do not need it.
With Astro
Astro 7's native Markdown processor runs no remark or rehype plugins, so a site whose Markdown depends on them declares the unified processor itself. This is the shape every site using this plugin is on:
// astro.config.mjs
import { unified } from '@astrojs/markdown-remark'
import { createRemarkAmazon } from 'remark-affiliate-card'
markdown: {
processor: unified({
remarkPlugins: [remarkDirective, createRemarkAmazon(), /* … */],
}),
}createRemarkAmazon() reads the product data and the credentials for you and
returns the [plugin, options] pair. Import the stylesheet yourself:
@import "remark-affiliate-card/card.css";Configure
createRemarkAmazon({
dataFile: 'src/data/amazon-products.json',
labels: {
kicker: 'PR', // overrides the brand; see Disclosure below
cta: 'Amazonで見る',
shopsLabel: '他で探す',
showAsin: false,
showDescription: false,
frameClass: '', // extra class on the card root, for your own skin
linkClass: '', // extra class on every anchor; see Styling below
},
bareUrls: false, // also convert standalone Amazon URLs
})Credentials come from .env or the environment:
| Variable | Required | Purpose |
| --- | --- | --- |
| AMAZON_AFFILIATE_TAG | yes | Associates tag on Amazon links |
| AMAZON_CREATOR_CREDENTIAL_ID | for fetch | Creators API credential |
| AMAZON_CREATOR_SECRET | for fetch | Creators API credential |
| AMAZON_CREATOR_CREDENTIAL_VERSION | no | defaults to 3.3 |
| AMAZON_MARKETPLACE | no | defaults to www.amazon.co.jp |
| RAKUTEN_AFFILIATE_ID | no | omit to hide the Rakuten button |
| YAHOO_VC_SID | no | ValueCommerce site id |
| YAHOO_VC_PID | no | ValueCommerce program id |
A shop with no credentials gets no button, rather than a link that goes nowhere.
Fetching product data
bunx affiliate-card fetchScans your posts for ASINs, asks the Creators API about the ones that are new
or older than 90 days, and writes src/data/amazon-products.json. Commit that
file: it is both the build's data source and its own cache.
--posts <dir> posts to scan (default src/content/posts)
--out <file> product file (default src/data/amazon-products.json)
--force refetch everything, ignoring cache freshnessAn ASIN the API stops resolving keeps whatever was captured when it still existed. Products get delisted and never come back, and overwriting those records with empty stubs would blank out cards that render perfectly well today. A brand-new ASIN that cannot be resolved falls back to a plain affiliate link.
The command exits non-zero only when every batch fails, which means a credential or connectivity problem. Individual ASINs going missing is routine and does not fail a build.
Note on the API
This talks to the Creators API, not Product Advertising API v5 — Amazon retired PA-API on 2026-05-15, and its access keys do not work here. Issue a credential pair in Associates Central → Tools → Creators API.
The API does not return customer reviews: customerReviews comes back null,
so the card renders no star rating. It does return the brand, which the card
shows above the title.
What the card shows
Image, brand, title, price, and a buy button — plus Rakuten and Yahoo! search links when those are configured.
Deliberately absent by default:
- The ASIN. It identifies the product to Amazon, not to a reader.
- The description. Amazon's description is the same keyword-stuffed marketing copy as the title, so showing both doubles the noise.
- A full-length title. Amazon titles run past 100 characters of search terms; the card clamps to two lines so the price and the buy button stay above the fold.
Each is available through labels for a site that wants it.
Disclosure
Japan's ステマ規制 requires advertising to be identifiable as advertising, and affiliate placements fall under it. The card carries no disclosure by default, because a site can equally disclose once per article — repeating it on every card is the noisier of the two options.
If you want it on the card, set labels: { kicker: 'PR' }. If you disclose at
the article level instead, make sure something on the page actually says so.
Styling
The stylesheet drives every colour, radius and font through an --aff-*
custom property, so you re-skin by overriding variables rather than forking
markup:
.amazon-card {
--aff-accent: #d2691e;
--aff-radius: 0;
}Dark mode follows your page's own marker — .dark or [data-theme="dark"] on
the root element. It deliberately does not use prefers-color-scheme: sites
that resolve an "auto" setting in JavaScript stamp the result on <html>, and
a media query would fight that and darken the card for a reader who had
explicitly chosen light.
If your site styles every <a> in prose, its rules will outrank this
package's. Rather than escalating specificity, pass your own escape-hatch
class:
// for a site with `.custom-md a:not(.no-styling) { … }`
createRemarkAmazon({ labels: { linkClass: 'no-styling' } })Bare Amazon URLs
Off by default. With bareUrls: true, a paragraph containing nothing but an
Amazon link becomes a card. A link inside a sentence stays a link.
It is opt-in because enabling it rewrites existing prose — that is a content change, and it should be a decision rather than a surprise after an upgrade.
Using the pieces directly
Every layer is exported, so you can render a card outside the remark pipeline:
import { renderAmazonCard } from 'remark-affiliate-card/card'
import { resolveShopLinks } from 'remark-affiliate-card/shops'
const links = resolveShopLinks('メダリスト ジェル', { rakutenAffiliateId: '…' })
const html = renderAmazonCard({ url, title, price }, links)renderAmazonCard and the URL builders are pure: credentials and text arrive
as arguments, never from the environment.
License
MIT
