ギフト紹介サイトを運営しています。集客には苦戦しています。
店に例えると、営業中の札を出して、商品も紹介のPOPも並べたのに、店長ばかりが棚を見て回っている状態です。店長の回遊だけは順調です。
そんな店で、客足より先に確認できたものがありました。
バックヤードの連絡メモです。売り場で。
自分が運営する After 6 Gift では、2026年10月11日に、商品紹介へ制作側の指示が出てしまう問題を修正しました。
今回、その修正を振り返るために、当時の修正前ソースからHTMLを生成し直しました。「贈りものの棚」には、次の一文が含まれていました。
医療的な効果や確実なストレス軽減は訴求しない。
これを読んで、贈り物を決める人はあまりいないと思います。
そもそも、商品を選ぶ人へ書いた文章ではありません。説明を書く側への指示です。実店舗なら、商品紹介のPOPに「店長へ。効能を言い切らないでください」と貼っているような状態でした。
「訴求しない」を、わざわざ訴求していました。
実装には生成AIを使っています。自分が一行ずつ書いたコードの武勇伝ではなく、自分のサイトで、情報の置き場所を後から整理することになった話です。
「紙」と書いた箱から、全部POPにしていた
原因は、商品データの note でした。
そこには、読者向けの「この品を贈る理由」と、制作側が守りたい注意や指示が、同居していました。
実データから、関係するところだけ抜き出します。
// 変更前の実データから抜粋
{
note: "会話のきっかけになる表情と、触って遊べる用途を贈る。医療的な効果や確実なストレス軽減は訴求しない。"
}
前半は商品を選ぶ人への紹介。後半は、書き手への指示です。
一つの文章として読めてしまいますが、宛先が途中で変わっています。
note は10月7日の初版から読者向けの紹介文を入れてトップページへ表示しており、10月9日に追加した棚も同じ項目を使っていました。
10月10日に商品・記事を追加した際の調査データを確認すると、この文章が含まれていました。調査データの angle では「編集提案:」という接頭辞が付いていましたが、商品データの note には、その接頭辞を外した同じ文章が入っています。後半の「訴求しない」は残ったままでした。
「編集提案」のラベルは外れたのに、指示は残っていました。
調査段階で「どう紹介するか」を考えるための情報と、読者に見せる紹介文の境界が、データ上で分かれていなかった。履歴から確認できた混在の経緯は、ここです。
店でいえば、「紙」とだけ書いた箱に、商品POPの原稿と、店長への連絡メモを一緒に入れていたようなものです。
そして、その箱の中身を売り場へ出す処理が、これでした。
// 変更前の表示処理から抜粋。eは既存のHTMLエスケープ関数
'<p class="shelf-reason">' + e(p.note) + '</p>'
e は既存のHTMLエスケープ関数です。HTMLとして解釈させない処理はしていますが、文章の宛先まで仕分けてくれるわけではありません。
印刷機は真面目でした。渡された原稿を、全部印刷しただけです。
「ちゃんと表示できる」と「そこに表示してよい」は、別の話でした。
POPと連絡ノートを、別の場所へ
修正後は、公開用の文章を giftReason、制作側の情報を editorialNotes に分けています。
同じ商品の修正後のデータは、こうなりました。
// 変更後の同じ商品から抜粋
{
giftReason: "会話のきっかけになる表情と、触って遊ぶ楽しさを。顔のある小さな雑貨が好きな友人への贈り物に。",
editorialNotes: [
"医療的な効果や確実なストレス軽減は訴求しない。"
]
}
売り場へ出すのは、公開用の文章だけです。
// 変更後の表示処理から抜粋
'<p class="shelf-reason">' + e(p.giftReason) + '</p>'
これで、商品POPとバックヤードの連絡ノートは、別の置き場所になりました。
旧 note へのフォールバックは置かれていません。公開文がないときに「とりあえず前の箱を使おう」とすると、また連絡メモを売り場へ出す経路が残るからです。
ただし、note を見つけ次第、全部消したわけではありません。
販売先ごとの offers[].note は、価格条件や購入時の注意など、読者に伝えるための項目です。こちらは用途を維持しています。
| 項目 | 店に例えると | 今回の扱い |
|---|---|---|
giftReason |
商品紹介のPOP | 棚へ表示 |
editorialNotes |
バックヤードの連絡ノート | 棚の紹介文には使わない |
商品直下の旧 note
|
宛先の違う紙が混ざった箱 | 公開用の参照を終了 |
offers[].note |
購入条件の注意書き | 用途を維持 |
同じ「注意書き」でも、お客さんに必要なものまで裏へしまうと困ります。名前だけでなく、どの場所で何を意味しているかを見る必要がありました。
実際の書き分けが分かりやすいのは、HARIOのボトルです。旧 note の末尾には「750mLと高さ30cmが相手の暮らしに合うかを判断軸にする。」とありました。
「判断軸にする」は書き手への指示です。でも、容量と高さそのものは、読者にも必要な判断材料です。修正後は、この指示を editorialNotes に置き、公開用の giftReason には「750mLの容量と高さ30cmが、相手の暮らしに合うか確かめて選びたい。」という形で残しています。冷蔵庫の高さや置き場所を確認する購入時の注意も維持しました。
指示の文体を外しても、判断材料は残す。連絡ノートを片づけるついでに、商品POPの必要な情報まで捨てない、という分け方です。
印刷する一行を変えたら、39商品分の仕分けが残った
表示処理の核心だけを取り出すと、変更は一行です。
- e(p.note)
+ e(p.giftReason)
ここだけ見ると、気軽な修正に見えます。
しかし、表示先を新しい項目へ切り替えるには、既存のデータもそろえなければなりません。コード履歴で確認できる修正範囲は、こうでした。
| 対象 | 修正内容 |
|---|---|
| 商品データ5ファイル・39商品 | 商品直下の公開用項目を移行 |
| うち8商品 | 旧 note にあった書き手向けの表現を整理し、公開文を書き直し |
| 記事本文2箇所 | 項目変更では直らない制作側の表現を整理 |
| 棚の表示処理 | 公開用項目だけを参照 |
| 関連テスト | 公開文が出ることと、旧欄・制作メモが出ないことを確認 |
39件は当時の未公開候補を含む商品データの数で、棚の公開対象は36品です。8品は、変更前の note と変更後の giftReason を比較し、公開文を書き直した商品の数です。この8品はいずれも公開対象に含まれます。残る31品は、公開文を維持して項目名を切り替えています。
箱を二つに分けるだけなら簡単です。面倒なのは、すでに入っている紙を読み直し、どちらへ移すかを決める方でした。
今回の技術的負債は、そこにあったと捉えています。
note という名前自体が悪いわけではありません。二つの役割を持たせたままデータと表示処理がつながり、後から意味をそろえ直す作業が必要になったことです。
一文の誤りだけを直しても、同じ欄に次の制作メモを入れれば、同じことが起きます。そのたびに人が読み分ける構造を残すより、置き場所と参照先を分ける方が、次の商品でも守る約束をはっきりさせられます。
今回はCMSやデータベースへの移行をするほどの話ではなく、今の構成の中で、データ・表示処理・テストの役割をそろえる修正になりました。
店を建て直す前に、まず連絡ノートを売り場から下げる。今回の規模なら、それで手を付けられる負債でした。
「POPが出る」と「連絡ノートが出ない」を、別々に確かめる
この修正で追加された回帰テストは、新しい公開文が表示されることに加えて、旧 note や editorialNotes に入れたマーカーが、棚のHTMLへ含まれないことを確認しています。
今回の振り返りでも、変更前と修正後のソースを別々に展開し、それぞれの実際の renderGiftShelf を呼び出しました。
| 確認 | 変更前 | 修正後 |
|---|---|---|
| 実データの「医療的な効果や確実なストレス軽減は訴求しない。」が棚のHTMLに入るか | 入る | 入らない |
同じ入力の giftReason のマーカーが出るか |
出ない | 出る |
同じ入力の旧 note のマーカーが出るか |
出る | 出ない |
同じ入力の editorialNotes のマーカーが出るか |
出ない | 出ない |
旧版は、旧 note をそのまま出していました。修正後は、公開用の項目を読み、旧欄を使っていないことまで確認できます。
現在のコードには、棚の検索対象を公開用項目から作り、制作メモを含めないテストもあります。POPから下げた連絡メモが、検索用データへ回り込んでいても困るためです。
とはいえ、項目名を変えれば何を入れてもよいわけではありません。giftReason に制作指示を書けば、今度はそこから表示されます。
箱に「商品POP」と書いたところで、入れ間違いを物理的に防げるわけではない。データの役割を分けることと、正しい内容を入れることは、両方必要です。
参照する項目と出力の関係を試せる、Node.jsだけの縮小例
以下は実サイトの全文ではなく、参照項目の違いを取り出した縮小例です。上の表と商品件数は実装履歴の検証結果で、この例からサイト全体を再現するものではありません。
import assert from 'node:assert/strict';
// 実装の参照項目の違いを取り出した縮小例。サイト全体のコードではありません。
const escapeHtml = value => String(value ?? '').replace(/[&<>"']/g, c => ({
'&': '&', '<': '<', '>': '>', '"': '"', "'": ''',
}[c]));
const before = product => `<p>${escapeHtml(product.note)}</p>`;
const after = product => `<p>${escapeHtml(product.giftReason)}</p>`;
const instruction = '医療的な効果や確実なストレス軽減は訴求しない。';
const oldProduct = {
note: '会話のきっかけになる表情と、触って遊べる用途を贈る。' + instruction,
};
const newProduct = {
giftReason: '会話のきっかけになる表情と、触って遊ぶ楽しさを。',
editorialNotes: [instruction],
};
const oldHtml = before(oldProduct);
const newHtml = after(newProduct);
const markedHtml = after({
...newProduct,
note: 'LEGACY_INTERNAL_NOTE',
editorialNotes: ['PRIVATE_EDITORIAL_INSTRUCTION'],
});
const result = {
oldInstructionVisible: oldHtml.includes(instruction),
newInstructionVisible: newHtml.includes(instruction),
publicTextVisible: newHtml.includes(newProduct.giftReason),
legacyMarkerVisible: markedHtml.includes('LEGACY_INTERNAL_NOTE'),
editorialMarkerVisible: markedHtml.includes('PRIVATE_EDITORIAL_INSTRUCTION'),
instructionAssignedToPublicFieldVisible: after({ giftReason: instruction }).includes(instruction),
};
assert.deepEqual(result, {
oldInstructionVisible: true,
newInstructionVisible: false,
publicTextVisible: true,
legacyMarkerVisible: false,
editorialMarkerVisible: false,
instructionAssignedToPublicFieldVisible: true,
});
assert.equal(after({ giftReason: '<b>公開文</b>' }), '<p><b>公開文</b></p>');
console.log(JSON.stringify(result, null, 2));
node reproduce-note-boundary.mjs
最後の確認は、あえて制作指示を公開用の giftReason に入れるものです。その場合は表示されます。
調査・検証の範囲
2026年10月31日、日本時間。Node.js v24.22.1/Linuxで、当時の変更前・修正後の実モジュールによるHTML生成、現在の関連テスト、掲載する縮小例を確認しました。10月11日の運用記録には公開版18への反映が記載されています。今回確認したのはコードと生成HTMLで、過去の配信やブラウザー画面を再観測したものではありません。実際に閲覧者が目にした件数、修正による工数・売上・集客の改善は計測していません。
また、editorialNotes は公開文に採用しない情報の置き場所であり、秘密情報へのアクセス制御ではありません。
実装、今回の履歴調査・再検証、この記事の構成・文章作成に生成AIを利用しています。
POPは売り場へ、連絡ノートはバックヤードへ
AIに実装を任せると、文章もデータも増やせます。今回、後から必要になったのは、その情報を「誰が読み、どこへ出すのか」に合わせて整理し直す作業でした。
次にデータや機能の追加を頼むときは、読者向けの文章と制作側の情報を別の項目として伝えたい。公開前も、「欲しい文章が出るか」に加えて、「別の宛先の文章を出していないか」を見たいと思います。
商品紹介のPOPは売り場へ。制作メモはバックヤードへ。39商品のデータを整理して、この二つの置き場所は分かれました。
ただ、一番肝心な問題は残っています。
お客さんが、なかなか来ない。