マキモノ
Web開発無料✅ 公式検証済みv1.0.0 / 更新

DB型サイトを「一覧だけ会員限定・個別ページは残す」に切り替える指示書(Next.js App Router)

自社DBで集客していたサイトが競合のリスト抜き取りに気づいた時の改修手順。名前が並ぶバルクな一覧だけを会員限定にし、個別ページはtitle/H1とCTAを残す。ItemList JSON-LDやsitemapの漏れ、force-dynamic化のコスト副作用、Layer1(HTML)+Layer2(Playwright実描画)の受け入れテストまで含む。

出品者: kim@orgiast.jp2 DL📖 読込 約2,991トークン (約5円)💰 コスパ 60
トークン節約メーター85%節約
ゼロからAIに作らせた場合21万トークン
このMDを読ませた場合3.2万トークン

17.8万トークンの節約 (API料金換算で約270円分)。 要件定義・技術調査・試行錯誤ぶんのトークンがまるごと不要になります。※ 出品者申告とレビューに基づく推定値。モデル・タスク内容により変動します。

この巻物について

DB型サイトを「一覧だけ会員限定・個別ページは残す」に切り替える指示書(Next.js App Router)」は、Web開発カテゴリのAI指示書(MDファイル)です。自社DBで集客していたサイトが競合のリスト抜き取りに気づいた時の改修手順。名前が並ぶバルクな一覧だけを会員限定にし、個別ページはtitle/H1とCTAを残す。ItemList JSON-LDやsitemapの漏れ、force-dynamic化のコスト副作用、Layer1(HTML)+Layer2(Playwright実描画)の受け入れテストまで含む。この巻物をAIに読み込ませると、ゼロから設計・調査する場合に比べて 約17.8万トークン(API料金換算で約270円)・85%のトークンを節約できます。

カテゴリ
Web開発
対応AI
claude-code、cursor、codex-cli
ライセンス
商用利用可 (再販不可)
価格
無料
ゼロから開発時
約21万トークン
この巻物使用時
約3.2万トークン
節約量
約17.8万トークン (約270円)
更新日
2026-08-20

使い方 (AIに渡す3つの方法)

いちばん簡単なのはワンライナー。Claude Code のターミナルに貼るだけです。

⬇ .md をダウンロード
claude "https://makimono-md.vercel.app/api/v1/files/db-next-js-app-router/raw を読み込んで、この指示書どおりに実装して"
claude-codecursorcodex-cliライセンス: 商用利用可 (再販不可)

中身

データベース型サイトを「一覧だけ会員限定・個別ページは残す」に切り替える指示書(Next.js App Router)

自社DBを公開して集客していたサイトが、競合にリストを丸ごと抜かれるリスクに気づいた時の改修手順。 遮断するのは「名前が並ぶバルクな一覧」だけにして、個別ページは title/H1 と CTA を残す。 これにより「リストは守るが、検索流入の受け皿は捨てない」を両立させる。

対象: Next.js App Router + Postgres 系(Supabase 等)+ Cookie セッション認証。 所要: 実装 1〜2 時間 + 検証 30 分。


0. 前提の確認(ここを飛ばすと的を外す)

改修前に必ず次を実測する。「SEO資産を守る」前提が既に無効化されているケースがある。

curl -s https://<本番ドメイン>/robots.txt
curl -s https://<本番ドメイン>/sitemap.xml | grep -c '<loc>'
curl -s https://<本番ドメイン>/sitemap.xml | grep -c '/<個別ページのパス>/'
  • robots.txt で既に個別ページ配下が Disallow されている、または sitemap から除外済みなら、 その領域の検索流入はすでに意図的に捨てられている。その場合「SEOと両立させる」設計は不要で、 素直に全面ログイン必須にした方が単純。
  • コンバージョン実績(問い合わせ件数・会員登録数)も確認する。流入があっても転換ゼロなら 「守るべきSEO資産」の価値はゼロなので、判断が変わる。

1. 会員判定を1箇所に集約する

src/lib/member.ts:

import { createServerClient } from '@/lib/db/server';

/**
 * 承認済み会員かどうか。競合としてマークされた会員は false を返す。
 * cookie を読むので、これを呼ぶページは動的レンダリングになる。
 */
export async function isApprovedMember(): Promise<boolean> {
  const server = await createServerClient();
  const { data: { user } } = await server.auth.getUser();
  if (!user) return false;
  const { data: member } = await server
    .from('members')
    .select('status, is_competitor')
    .eq('auth_user_id', user.id)
    .maybeSingle();
  return member?.status === 'approved' && member?.is_competitor !== true;
}

既存コードに同等のインライン判定が散っているなら、この関数の呼び出しに置き換える(挙動は変えない)。

2. ゲート表示コンポーネントを1つ作る

src/components/ListMemberGate.tsx(server component で可)。props は { redirect: string; count?: number | null; label?: string }

内容:

  • 見出し「<対象>の一覧・検索は会員限定です」
  • 件数だけは出す(「該当 1,234 件」)。件数は価値の訴求になり、抜き取りには使えない
  • 「無料で会員登録」→ /auth/sign-up?redirect=<redirect>
  • 「すでに会員の方はログイン」→ /auth/sign-in?redirect=<redirect>
  • 「登録せずに相談したい方はこちら」→ 問い合わせページ(登録を挟まない逃げ道を必ず用意する)

3. 一覧を出しているページを全部洗い出してゲートする

grep -rl "<一覧カードのコンポーネント名>" src/app

典型的な漏れどころ:

場所対応
トップの検索フォーム + facet + 結果 + ページネーション会員のみ表示。件数(総掲載数)は残す
属性別ハブ(地域別・カテゴリ別・月別など)カード一覧のみ差し替え。H1・導入文・内部リンクは残す
締切・新着などの横断ハブ同上
ItemList JSON-LD非会員には出さない(構造化データに名前が並ぶ=一覧の流出)
sitemap.xml個別ページを残すか外すかを明示的に決める

実装パターン(非会員には名前を取るクエリ自体を実行しないのが重要):

const member = await isApprovedMember();

const { data, count } = member
  ? await db.from('items').select('id, name, …', { count: 'exact' })
      .order('start_date').limit(100)
  : await db.from('items').select('id', { count: 'exact', head: true });  // 件数のみ

const items = (data ?? []) as Item[];
  • ORM の型が select(member ? 'a, b, c' : 'id') のような動的文字列を解釈できないことがある (型パーサが union を扱えず ParserError 型になる)。その場合は三項演算子でクエリごと分岐させる。
  • head: true の時 datanullif (!data) notFound() のような既存ガードは count を見る形に直す(直さないと非会員に 404 を返してしまう)。

4. 個別ページは「中身ゼロ・枠は残す」

会員向け表示は一切変えない。非会員に対してだけ落とす。

残すもの: <title> / <h1>(対象の名前)/ 大分類タグ / 問い合わせ CTA / パンくずのトップ階層 落とすもの:

  • 属性フィールド(時期・場所・形式・規模など)を丸ごと会員限定セクションへ
  • ヘッダのチップ類のうち属性由来のもの
  • パンくず・関連リンクのうち属性別ページへのリンク(それ自体が属性の露出になる)
  • generateMetadatadescription から属性を除去し、名前だけの汎用文にする
  • 構造化データ(Event 等)は name / url / status のみ。startDate / location / sameAs / audience出さない
  • FAQ 回答に個別の数値を埋めていたら、一般論+「詳細は会員登録後」に固定

会員限定案内の文言に、何が会員限定なのかを漏れなく列挙する(落とした項目を全部書く)。 登録せずに進める導線(問い合わせ)も必ず併記する。

5. レンダリング方式の変更に伴う副作用を認識する

cookie を読むため、対象ページは ISR / 静的生成が使えず export const dynamic = 'force-dynamic' になる。

  • revalidate / dynamic = 'force-static' の export を削除する
  • dynamicParams = false を消し忘れると、generateStaticParams を外した瞬間に全パラメータが 404 になる。未定義パラメータは本文側の notFound() で弾く
  • コスト影響: 個別ページが N 件あると、クローラのアクセスが毎回サーバレンダリングになる。 middleware で「未ログインなら CDN キャッシュ可」ヘッダを付ける案は、 フレームワークが dynamic ルートに付ける private, no-cache に上書きされて効かない(実測)。 効かせたいなら「会員だけ middleware rewrite で別ルートへ送り、非会員版は静的に戻す」まで踏み込む必要がある。 そこまでやらないなら、効かない対策コードは入れずに残課題として明記する

6. 受け入れテスト(2層で、実際に走らせる)

Layer1: HTML を直接検証(test/list-gating.test.mjs

next build && next start -p <port> の上で fetch し、未ログインの出力に名前が並んでいないことを機械チェックする。

const { status, html } = await get('/');
check('ゲート表示がある', html.includes('会員限定です'));
check('facet が出ていない', !html.includes('<絞り込みボタンの文言>'));
const links = html.match(/href="\/items\/[0-9a-f-]{8}[^"]*"/g) ?? [];
check('個別ページへのリンクが並んでいない', links.length === 0);
check('ItemList JSON-LD が無い', !html.includes('"@type":"ItemList"'));
check('H1 は残っている', /<h1[\s>]/.test(html));

個別ページは sitemap から実在 ID を1件拾って検証する(テストにIDをハードコードしない)。 startDate / location が JSON-LD に出ていないことも見る。

Layer2: 実描画(Playwright)

スマホ幅(390px)でゲート見出し・登録ボタン・問い合わせCTAが isVisible() であること、 a[href^="/items/"] の数が 0 であること、document.documentElement.scrollWidth > innerWidth でないこと(横スクロールが出ていないこと)を確認する。

検証の落とし穴

  • 会員ログイン状態の実描画まで検証しないと「会員側を壊していない」ことは言えない。 時間がないなら「未検証」と明記する(member ? 既存 : ゲート の分岐で既存パス無改変・build 通過、まで)
  • 本番反映後に本番URLでも同じ確認をするsitemap.xml は ISR で古い版が返ることがあるため、 値がおかしい時は数分置いて再取得する

7. 手数料率など「対外配布済みの数字」を触る時

内部DBのデフォルト値と、既に配布した資料(PDF・LP・提案書)の記載は必ず一致させる

  • 現在値は人に聞かず配布物とDBから確認する。人の記憶は古い決定を答えることがある
  • 変更したら read-back verify する: select column_default from information_schema.columns where table_name='…' and column_name='…'
  • 既存行の値は変えない(alter column … set default はデフォルトのみ変更)。 既存行も変えるなら update を別途明示的に流し、影響行数を確認する

完了条件

  • tsc --noEmitnext build が通る
  • Layer1 / Layer2 が全 PASS し、本番URLでも同じ結果
  • 会員側の表示・機能が落ちていない(検証したか、未検証かを明記)
  • レンダリング方式変更のコスト影響を、対策済みか残課題かのどちらかで明記した

よくある質問

「DB型サイトを「一覧だけ会員限定・個別ページは残す」に切り替える指示書(Next.js App Router)」とは何ですか?

自社DBで集客していたサイトが競合のリスト抜き取りに気づいた時の改修手順。名前が並ぶバルクな一覧だけを会員限定にし、個別ページはtitle/H1とCTAを残す。ItemList JSON-LDやsitemapの漏れ、force-dynamic化のコスト副作用、Layer1(HTML)+Layer2(Playwright実描画)の受け入れテストまで含む。

どれくらいトークン(費用)を節約できますか?

ゼロから開発すると約21万トークンかかりますが、この巻物を使えば約3.2万トークンで済みます。差し引き約17.8万トークン(API料金換算で約270円)・85%の節約です。

どうやって使いますか?

無料です。MDファイルを Claude Code などのAIに読み込ませるだけ。ワンライナーをターミナルに貼れば実装が始まります。要件定義や技術調査を省いて実装だけにトークンを使えます。

どのAIツールに対応していますか?

claude-code、cursor、codex-cli に対応しています。

商用利用できますか?

ライセンスは「商用利用可 (再販不可)」です。

🤝 自分でAIを動かすのは、まだ不安…という方へ

この巻物の内容を、AIを使うプロに丸ごと任せることもできます。姉妹サービスAI代行堂なら「LINEで頼むだけで、仕事が完成」。

AI代行堂を見る →

関連する巻物

この巻物、誰かのトークンも救えます

𝕏 で節約レシートをシェア