テンプレート型ページ生成:プログラマティック SEO の技術実装ルート
先週、友人からプログラマティック SEO の相談がありました。キーワードマトリクスは Excel に 2000 以上のロングテールを整理済みなのに、「次の一手」で止まっている、と。
「ロジックは分かる。でも実装しようとすると次から次へと疑問が出る。Next.js と Astro どっち? DB は何を使う? URL はどう設計する? そもそも 5000 ページを一気に生成してサーバーは耐えられる?」
正直、2 年前の私も同じところで悩みました。法律サービスディレクトリを作り、3000 都市ページをプログラマティック SEO で出そうとしたときのことです。結果、初版の公開まで 2 か月。今思い出しても胃が痛くなるほどの落とし穴でした。
この記事では、その穴を埋めるための3 つの技術ルート——静的生成、動的レンダリング、ハイブリッド——をまとめます。それぞれにコードの考え方、向くシーン、実例を載せています。読み終えたら手は動かせるはずです。少なくとも、当時の私のようにゼロから試行錯誤する必要はありません。
すでに「プログラマティック SEO とは何か:適用範囲とスパム政策の境界線」「キーワード一括生成:プログラマティック SEO のコンテンツ計画戦略」を読んでいるなら、本稿はシリーズ第 3 弾として、テンプレート型ページの技術実装にフォーカスします。
まず押さえる:自分に合うのはどのルートか
実装は、フレームワークを勢いで選ぶ話ではありません。まずデータの性質を見ます。
静的生成(SSG)は向いているか
更新が週 1 回や月 1 回程度まで遅くてよいなら、静的生成がいちばん安定しやすいです。
例として、以前友人の観光ガイドサイトを手伝ったことがあります。都市ごとのページは、観光地・交通・グルメなどほぼ固定コンテンツで、半年に 1 回更新すれば十分でした。Astro の Content Collections で 2000 都市分を JSON にし、一括で静的ページを生成。ビルドはだいたい 10 分程度でしたが、公開後の TTFB(Time To First Byte)はおおむね 80ms 前後、CDN キャッシュヒット率は 95% 程度でした。
メリットは明確です。表示が速い、SEO と相性がいい、サーバー負荷が小さい。デメリットは、データ更新のたびにサイト全体の再ビルドが必要なこと、ページが 5000 を超えるとビルド時間が伸びやすいことです。
動的レンダリング(SSR)はいつ使うか
レートや在庫のように常に最新が要るなら、静的生成だけでは足りません。
国境越え送金の Wise は典型例です。通貨ペアのページでは為替が分単位で動きます。静的生成だけだと「10 分前のレート」が表示されかねず、送金判断に直結します。そこで Next.js の SSR(サーバーサイドレンダリング)で、リクエストごとに API から最新レートを取っています。
その代わりサーバー負荷は増えます。Wise は 1 日あたり百万回を超える換算クエリをさばいており、コストも馬鹿になりません。TTFB も遅くなりがちで、目安は 200〜500ms です。
ハイブリッドは「中庸」の選択
更新の遅い部分と速い部分が両方あるなら、ハイブリッドがしっくりきます。
Zapier の「アプリ A × アプリ B」連携ページがそうです。5000 以上あり、機能説明やセットアップ手順などベース情報は静的ですが、ユーザーの接続状態や最終同期時刻は動的です。
Next.js の ISR(Incremental Static Regeneration)で、まずは静的に速く返しつつ、バックグラウンドで定期的に更新。速さと正しさのバランスを取っています。
おすすめ:技術ルートを決める前に、次の 3 つに答えてください。
- データはどのくらいの頻度で変わるか(日次? 時間? リアルタイム?)
- ページ規模はどの程度か(5000 未満? 5000〜2 万? それ以上?)
- SEO パフォーマンスの要求は(TTFB は 100ms 未満必須? 300ms でも許容?)
ここがはっきりすれば、選択肢はかなり絞れます。
静的生成:Astro での実装イメージ
静的生成に決めたなら、私は Astro を推します。静的サイト向けに設計されており、Content Collections がプログラマティック SEO と相性が良いからです。
データ構造の設計
まずデータ構造を定義します。例として、都市ごとに 1 ページの法律サービスディレクトリを作るとします。次のように整理できます。
// src/content/config.ts
import { defineCollection, z } from 'astro:content';
const lawyersCollection = defineCollection({
type: 'content',
schema: z.object({
city: z.string(),
citySlug: z.string(),
province: z.string(),
lawyerCount: z.number(),
topFirms: z.array(z.string()),
avgPrice: z.string(),
specialties: z.array(z.string()),
}),
});
export const collections = {
'lawyers': lawyersCollection,
};
src/content/lawyers/ にデータファイルを置き、都市ごとに 1 つの JSON にします。
// src/content/lawyers/beijing.json
{
"city": "Beijing",
"citySlug": "beijing",
"province": "Beijing Municipality",
"lawyerCount": 12500,
"topFirms": ["King & Wood Mallesons", "Zhong Lun Law Firm", "Dacheng Law Offices"],
"avgPrice": "2000-5000 CNY/hour",
"specialties": ["Criminal", "Civil", "Commercial", "Intellectual Property"]
}
2000 都市なら 2000 ファイルに見えますが、スクリプトで一括生成すれば現実的です。私はよく Python か Node で DB からエクスポートし、JSON をまとめて書き出します。
動的ルートのテンプレート
データができたらテンプレートです。Astro の動的ルートは扱いやすいです。
// src/pages/[citySlug].astro
---
import { getCollection } from 'astro:content';
export async function getStaticPaths() {
const lawyers = await getCollection('lawyers');
return lawyers.map(lawyer => ({
params: { citySlug: lawyer.data.citySlug },
props: { lawyer },
}));
}
const { lawyer } = Astro.props;
---
<!DOCTYPE html>
<html>
<head>
<title>{lawyer.data.city} Lawyer Service Guide | Top Law Firms & Pricing</title>
<meta name="description" content={`Complete guide to lawyer services in ${lawyer.data.city}, covering ${lawyer.data.specialties.join(', ')} and more. Recommended firms: ${lawyer.data.topFirms.join(', ')}. Average rates: ${lawyer.data.avgPrice}`} />
</head>
<body>
<h1>{lawyer.data.city} Lawyer Service Guide</h1>
<section>
<h2>Key Data</h2>
<p>Number of Lawyers: {lawyer.data.lawyerCount}</p>
<p>Main Practice Areas: {lawyer.data.specialties.join(', ')}</p>
<p>Average Rates: {lawyer.data.avgPrice}</p>
</section>
<section>
<h2>Recommended Firms</h2>
<ul>
{lawyer.data.topFirms.map(firm => <li>{firm}</li>)}
</ul>
</section>
<!-- Internal links: related cities -->
<section>
<h2>Lawyer Services in Nearby Cities</h2>
<!-- This can be populated based on province or geographic location -->
</section>
</body>
</html>
getStaticPaths が全都市の静的ページを自動生成します。2000 都市なら 2000 枚の HTML です。
ビルドとデプロイ
ビルドコマンドはシンプルです。
npm run build
ビルド後、dist/ に静的 HTML が揃います。Cloudflare Pages や Vercel にそのまま載せれば、CDN キャッシュも効きます。
先ほどの観光ガイドの例では、2000 ページのビルドにだいたい 10 分程度でした。Astro のビルドは速く、Next.js の静的生成より効率的なことが多いです。
最適化のヒント:5000 ページを超えるなら分割ビルドも検討してください。増分ビルドで、新規データ分だけ生成し、サイト全体を毎回まるごと再ビルドしない運用も可能です。
動的レンダリング:Next.js の SSR
データがリアルタイムに変わるなら、静的生成だけでは足りません。そこで Next.js の SSR です。
基本設定
Next.js SSR の核は getServerSideProps です。
// pages/currency/[pair].tsx
import { GetServerSideProps } from 'next';
export const getServerSideProps: GetServerSideProps = async (context) => {
const { pair } = context.params;
const [from, to] = pair.split('-to-');
// Fetch real-time exchange rate
const exchangeRate = await fetchExchangeRate(from, to);
return {
props: {
from,
to,
rate: exchangeRate.rate,
lastUpdate: exchangeRate.timestamp,
},
};
};
export default function CurrencyPage({ from, to, rate, lastUpdate }) {
return (
<div>
<h1>{from} to {to} Currency Converter</h1>
<p>Current Rate: {rate}</p>
<p>Last Updated: {new Date(lastUpdate).toLocaleString()}</p>
{/* Conversion calculator */}
<input type="number" placeholder="Enter amount" />
<button>Convert</button>
{/* Historical chart */}
<div>30-day exchange rate trend</div>
</div>
);
}
ユーザーが /currency/usd-to-eur に来るたび、サーバーが為替 API から最新を取得します。常に「いま」の内容になります。
キャッシュ戦略:サーバーを潰さない
動的レンダリングの難所は負荷です。Wise のように 1 日百万回超のクエリで、毎回フルに外部 API を叩くとコストが爆発します。
解はキャッシュです。ただし戦略が要ります。
stale-while-revalidate が有効です。まずキャッシュ(数分古くてもよい)を返しつつ、裏で静かに更新。次の訪問で新しい値が見えます。
Next.js でも次のような組み立てが可能です。
export const getServerSideProps: GetServerSideProps = async (context) => {
const { pair } = context.params;
// Check cache
const cached = await checkCache(pair);
if (cached && !isExpired(cached)) {
return { props: cached.data };
}
// Cache expired, update in background
fetchExchangeRate(pair).then(data => updateCache(pair, data));
// Return stale data first
return { props: cached?.data || await fetchExchangeRate(pair) };
};
ユーザーには速く見せつつ、毎回外部 API を叩かずに済ませられます。
動的な構造化データ
動的ページでは、構造化データ(JSON-LD)も動的に組み立てます。
// Generate JSON-LD in page component
const jsonLd = {
"@context": "https://schema.org",
"@type": "FinancialService",
"name": `${from} to ${to} Currency Conversion`,
"offers": {
"@type": "Offer",
"price": rate,
"priceCurrency": to,
},
};
// Inject into HTML
<script type="application/ld+json">
{JSON.stringify(jsonLd)}
</script>
ページごとに常に正しい構造化データを出せ、検索結果側でも最新レートに近い情報を示しやすくなります。
ハイブリッド:Next.js の ISR
「半分は静的、半分は動的」なら、ISR(Incremental Static Regeneration)が第一候補です。
ISR の考え方
最初は静的に生成しつつ、「有効期限」を設定します。期限後の最初の訪問でバックグラウンド再生成が走る、というモデルです。
// pages/integrations/[app1]-and-[app2].tsx
export async function getStaticPaths() {
const integrations = await fetchAllIntegrations();
return integrations.map(int => ({
params: { app1: int.app1, app2: int.app2 },
}));
}
export async function getStaticProps({ params }) {
const integration = await fetchIntegration(params.app1, params.app2);
return {
props: integration,
revalidate: 3600, // Expires after 1 hour
};
}
revalidate: 3600 は、生成後 1 時間は静的レスポンスを返し、1 時間経過後の最初の訪問では古いページを見せつつ裏で再生成し、2 回目の訪問から新しいページが返る、という動きです。
オンデマンド再検証
自然失効を待てないケースもあります。例:Zapier の連携が突然壊れ、すぐページを更新したい——1 時間は待たない、というときです。
Next.js ではオンデマンドの再検証も用意されています。
// API route: trigger update
// pages/api/revalidate.ts
export default async function handler(req, res) {
const { app1, app2 } = req.query;
try {
await res.revalidate(`/integrations/${app1}-and-${app2}`);
return res.json({ revalidated: true });
} catch (err) {
return res.status(500).send('Error revalidating');
}
}
監視スクリプトで連携の健全性を定期的に見て、異常時にこの API を叩いて即更新、という運用ができます。
ISR の向き・不向き
ISR は万能ではありません。株価やタイムセールのように毎アクセス最新が必須なら SSR が必要です。
ISR が向くのは、更新はそう頻繁ではない(時間〜日単位)が、変わったときは比較的早く反映したい場面です。Zapier の連携ページはまさにそうで、数か月に 1 回レベルの変更でも、ユーザーはすぐ知りたいはずです。
URL 設計:SEO の土台
技術ルートが決まったら URL です。見落とされがちですが、設計は SEO 効果に直結します。
SEO に効く URL の 3 原則
1. 狙いキーワードをパスに含める
URL も評価要因の一つで、自然にキーワードが入ると有利になりやすいです。
例:「北京 離婚 弁護士」向けなら /beijing/divorce-lawyer のように、地名とサービスがパスに出ます。
2. 階層は深くしすぎない(目安 3 段まで)
深すぎるパスはユーザーにもクローラにも負担です。
/service/legal/lawyer/divorce/beijing のような 6 段は、一見して何のページか分かりにくく、低品質と見なされやすいリスクもあります。
3. ハイフン区切り。クエリパラメータは避ける
/lawyer?type=divorce&city=beijing は、/beijing/divorce-lawyer より SEO 的に不利になりがちです。パラメータ URL は動的ページと誤判定され、インデックス効率が落ちることもあります。
よくある URL パターン 3 つ
現場でよく見るパターンを 3 つにまとめます。
パターン 1:コア語を先に
/lawyer/beijing/divorce
ブランド寄りサイト向き。「lawyer」を先に置き、カテゴリの認知を強めます。
パターン 2:地理を先に
/beijing/divorce-lawyer
ローカルサービス向き。「都市名+サービス」の検索クエリとパスが一致しやすく、有利になりやすいです。
パターン 3:フラット
/beijing-divorce-lawyer
超大量ページ向き。階層が 1 段で運用が単純です。
おすすめ:ユーザーの検索の言い方を見る。「都市+業種」が多ければパターン 2。クエリがばらけるならパターン 3 の柔軟さが活きます。
内部リンクの自動化
URL が決まったら内部リンクです。プログラマティック SEO の強みは、リンク網を機械的に張れることです。
例:3000 都市の弁護士ディレクトリで、各ページから関連都市へどう繋ぐか。
地理の階層:北京ページから「河北省の弁護士」「天津の弁護士」など近隣へ。
サービス種別:北京の離婚弁護士ページから、同じ都市の「刑事」「民事」など別領域へ。
コードは素直に書けます。
---
// In page template
const { lawyer } = Astro.props;
const nearbyCities = await getNearbyCities(lawyer.data.province);
const relatedSpecialties = lawyer.data.specialties;
---
<section>
<h2>Lawyers in Nearby Cities</h2>
{nearbyCities.map(city => (
<a href={`/${city.slug}/${lawyer.data.specialties[0]}-lawyer`}>
{city.name} {lawyer.data.specialties[0]} Lawyer
</a>
))}
</section>
<section>
<h2>Other Legal Services in {lawyer.data.city}</h2>
{relatedSpecialties.map(spec => (
<a href={`/${lawyer.data.citySlug}/${spec}-lawyer`}>
{lawyer.data.city} {spec} Lawyer
</a>
))}
</section>
各ページに数十本の内部リンクが付き、サイト全体がネット状につながります。クロール効率も上がり、ユーザーも関連に辿りやすくなります。
データベース選定:考えすぎない
データ構造が決まったら保存先です。長々と悩む人が多いですが、そこまで難しくありません。
選択肢 4 つと向くシーン
PostgreSQL:構造化データと複雑クエリ。
フィールドが固定で、「北京の離婚案件で 3000 円未満の…」のような検索が要るなら安定の選択です。ACID、トランザクション、全文検索も揃います。
MongoDB:スキーマがまだ流動的。
初期にフィールドがよく変わるなら柔軟です。先に厳密なスキーマを決めずに拡張できます。私も初期案件では構造変更が多く Mongo を使うことがありました。
Airtable / Google スプレッドシート:小規模・共同編集。
数十〜数百行でチームで触るなら十分戦えます。非エンジニアも更新できる。友人の小案件は 200 行程度を Airtable で回していてメンテが軽いです。
CSV / JSON ファイル:純粋な静的生成。
データが完全に静的でページも 1000 未満なら、ファイル直読みで十分です。DB を持たず、ビルド時に読み込む。Astro の Content Collections はまさにこの思想です。
まず規模で決める
1000 件未満:CSV/JSON か Airtable。
1000〜1 万件:PostgreSQL か MongoDB。
1 万件超:PostgreSQL + Redis キャッシュなども検討。
完璧な一枚を最初から狙わず、動くものを選びましょう。後からスキーマが変わっても、移行は想定より小さく済むことが多いです。
テンプレート開発の要:コンテンツの差別化
基盤ができてからが本番で、多くのプログラマティック SEO がテンプレの中身が似すぎて失敗します。
落とし穴:キーワード差し替えだけのテンプレ
失敗例を見ました。「都市名+ホテル」で 2 万ページ。都市名だけ変えて本文は同一。「北京のホテルおすすめ」「上海のホテルおすすめ」……全部同じ型。
結果、アルゴリズム的に評価を落とし、トラフィックが 7 割減。回復に 8 か月かかった、という話です。
本質:ページごとに独自価値がない。北京で検索したユーザーと上海で検索したユーザーが、ほぼ同じ文章を見せられる——明らかな量産スパムに見えます。
差別化の 3 手法
1. 動的データの注入
各ページに固有データを載せる。弁護士ディレクトリなら、都市ごとの人数、事務所リスト、平均料金など。DB から出すだけでページ間の差が付きます。
2. UGC の統合
ユーザー生成コンテンツが最強の差別化材料です。TripAdvisor のホテルページは、レビューがページごとに違い、テンプレ量産ではありません。
UGC があるなら必ず載せましょう。弁護士サイトなら「口コミ」モジュールなど。
<section>
<h2>User Reviews</h2>
{lawyer.data.reviews.map(review => (
<div>
<p>{review.content}</p>
<span>Rating: {review.rating}/5</span>
</div>
))}
</section>
3. AI による補助的な拡張
動的データも UGC もないとき、AI で説明文を厚くする手もあります。ただし人間によるレビュー必須。コア情報の代替にはせず、補助の文章に留めるべきです。
試しに、コアデータ(人数・事務所リスト)は DB、各都市の「法務の特徴」一文だけ AI 補助、という切り分けをしたことがあります。
ポイント:AI は足し算。各ページに本物のユニークデータがない限り、AI だけでは救えません。
構造化データの自動生成
各ページに JSON-LD が必要ですが、これは自動生成に向きます。
const jsonLd = {
"@context": "https://schema.org",
"@type": "LegalService",
"name": `${lawyer.data.city} Lawyer Services`,
"areaServed": {
"@type": "City",
"name": lawyer.data.city,
},
"provider": lawyer.data.topFirms.map(firm => ({
"@type": "Organization",
"name": firm,
})),
};
実データから JSON-LD を組み立てれば、検索結果でも事務所名や地域情報がリッチに出やすくなり、CTR の改善にもつながります。
パフォーマンス:待たせすぎない
ページ数が多いほど、速度最適化は無視できません。
押さえる 3 指標
TTFB(Time To First Byte):サーバー応答の速さ。
静的生成はだいたい <100ms、動的は 200〜500ms が目安。500ms を超えると、ユーザーは本文を見る前に離脱しかねません。
LCP(Largest Contentful Paint):主コンテンツが表示されるまで。
目安は <2.5 秒。画像や重いコンポーネントが多いと LCP が伸びます。
FID(First Input Delay):操作への応答。
目安 <100ms。JavaScript が多すぎるとボタンが効かない感じになります。
CDN:静的ページの加速器
静的生成なら CDN キャッシュが鍵です。Cloudflare Pages も Vercel も CDN 付きで、設定は比較的簡単です。
// astro.config.mjs
export default defineConfig({
output: 'static',
build: {
assets: 'assets/',
},
vite: {
build: {
rollupOptions: {
output: {
assetFileNames: 'assets/[hash][extname]',
},
},
},
},
});
ビルド成果物にユニークなハッシュが付くため、CDN と相性が良くなります。
画像最適化:表示を遅くしない
各ページのヒーローをそのままの JPG で載せると数 MB になりがちで、読み込みが重くなります。
Astro には画像最適化が組み込まれています。
---
import { Image } from 'astro:assets';
import heroImage from '../images/lawyer-hero.jpg';
---
<Image src={heroImage} alt="Lawyer Services" width={1200} height={675} />
WebP への変換や圧縮が自動で、例えば 2MB の画像が 200KB 程度になることも珍しくありません。
クロールバジェット:大量ページの必修
5000 ページを超えると、Google のクローラが全部を回りきれないことがあります。いわゆるクロールバジェットの浪費です。
対策
まず sitemap の分割。5000 ページを 1 ファイルに詰め込まず、複数ファイルに分けます。
// sitemap-index.xml
<sitemapindex>
<sitemap><loc>https://example.com/sitemap-1.xml</loc></sitemap>
<sitemap><loc>https://example.com/sitemap-2.xml</loc></sitemap>
<sitemap><loc>https://example.com/sitemap-3.xml</loc></sitemap>
</sitemapindex>
1 ファイルあたり多くても 500 URL 程度に抑えると、インデックス効率が上がりやすいです。
次に 内部リンクの優先度。検索ボリュームの大きい都市ページを、トップやナビから多く指す。重要度の低いページへのリンクは減らす。クローラは自然と重要ページを優先します。
実例:大手はどう組んでいるか
理論のあとは事例です。アーキテクチャのヒントになります。
TripAdvisor:ハイブリッドの型
数百万のホテルページをどう回しているか。
アーキテクチャ:静的生成 + 動的更新のハイブリッド。
名称・住所・設備など基本情報は静的。レビューと評価は動的読み込み。静的は DB エクスポート、動的はレビュー API、の二系統です。
URL:/hotel/[city]/[hotel-name]
例:/hotel/beijing/grand-hyatt。3 段で SEO に無理が少ない構成です。
技術の要点
- レビューのリアルタイム更新(UGC)
- 価格比較の動的読み込み(API)
- Review スキーマなどの構造化データ自動化
成功の鍵は UGC です。ページごとに何百もの実レビューがあり、テンプレだけでは出せない差が付きます。
Zapier:ISR の教科書
「アプリ A × アプリ B」の連携ページが 5000 以上。例:Slack と Gmail、Notion と Google カレンダー。
アーキテクチャ:Next.js ISR。
機能説明・手順などベースは静的、接続状態・最終同期は動的。ISR で速さと正しさを両立。
URL:/integrations/[app1]/[app2]
例:/integrations/slack/gmail。2 段で明快です。
技術の要点
- オンデマンド再検証(連携トラブル時の即更新)
- Playwright などでの自動テスト
- アプリハブと関連連携への内部リンク網
エンジニアリングブログで ISR と数千ページ運用について語られており、読む価値があります。
Wise:動的レンダリングとキャッシュ
為替は分単位で動くため、純粋な静的生成では足りません。
アーキテクチャ:Next.js SSR + stale-while-revalidate。
/currency/usd-to-eur に来たら、まずキャッシュを確認。未失効(例:5 分以内)ならそのまま返しつつ、裏でレート更新。次の訪問で新しい値が見えます。
URL:/currency/[from]-to-[to]
例:/currency/usd-to-eur。キーワードを自然に含められます。
技術の要点
- stale-while-revalidate
- CDN エッジキャッシュ
- リアルタイムレートを JSON-LD で表現
コストを抑えつつ鮮度を確保する好例です。リアルタイム寄りの画面を作るなら参考になります。
落とし穴集:私が踏んだミス
成功例のあとは失敗談です。以下は実際に踏んだ穴です。
落とし穴 1:URL が混沌
初期の法律ディレクトリで /service?id=lawyer&city=beijing&type=divorce のような URL にしていました。
柔軟に見えますが、クローラはクエリ URL を好みません。インデックス効率が落ち、ユーザーにも「何のページか」が伝わりにくいです。
教訓:着手前に URL を固める。公開後の全面変更は、内部リンク・外部リンク・サイトマップの修正コストが巨大です。
落とし穴 2:テンプレの重複
2 万ページで都市名だけ差し替え、本文は完全コピー。「北京」を「上海」に換えただけ、というサイトを見ました。
Google 側の評価が落ち、トラフィック 7 割減。
教訓:ユニークデータがないページは作らない。1 万の薄いページより、1000 の質の高いページ。
落とし穴 3:性能の壁
5000 ページを動的生成だけで回すとサーバーが悲鳴を上げます。昔 PHP で回していたときは落ちまくりでした。
教訓:5000 超なら静的生成か ISR を主役に。純ダイナミックだけでは持ちません。
落とし穴 4:監視なき公開
公開後に監視がなく、気づいたら 3 か月後——半分のページがインデックスされていなかった、原因はサイトマップ設定ミス、という案件がありました。
教訓:公開直後の 1 週間は必ずモニタリング。Search Console でインデックス、Screaming Frog で技術面、Ahrefs 等で順位の変化を見る。
次の一歩:考えすぎず小さく始める
ここまで読んでまだ圧倒されるなら、小さな実験からでよいです。
Step 1:小さなシナリオを選ぶ
いきなり 5000 ページにしない。まず 50 都市など、扱える規模で。
Step 2:スタックを決める
更新が遅いなら Astro。リアルタイムなら Next.js SSR。
Step 3:データと URL を設計する
半日でもいいので、フィールドとパスを紙に書く。飛ばさない。
Step 4:テンプレを作って検証
まず 3 ページ分でテンプレを作り、差別化を目視確認。問題なければバッチ生成。
Step 5:小ロットで公開
50 ページで出して 1 週間様子を見る。インデックス率、直帰率、滞在時間。問題なければ 500 へ拡大。
Step 6:反復で磨く
データを見ながらテンプレ・内部リンク・URL を調整する。プログラマティック SEO は一度きりのプロジェクトではなく、継続的な改善です。
FAQ
静的生成・動的レンダリング・ハイブリッドはどう選べばよいですか?
プログラマティック SEO のページ数に上限はありますか?
テンプレートページは Google にペナルティを食らいますか?
DB は PostgreSQL と MongoDB どちらがよいですか?
URL の階層は最大どれくらいがよいですか?
内部リンクの自動化はどう実装しますか?
プログラマティック SEO には Astro と Next.js どちらが向いていますか?
9 min read · 公開日: 2026年4月4日 · 更新日: 2026年4月5日
関連記事
キーワード一括生成:プログラマティック SEO のコンテンツ計画戦略
キーワード一括生成:プログラマティック SEO のコンテンツ計画戦略
シリーズ更新とホットトピックパッチ:技術ブログを活性化させる実践戦略
シリーズ更新とホットトピックパッチ:技術ブログを活性化させる実践戦略
プログラマティックSEOとは:適用範囲とスパムポリシーの境界線

コメント
GitHubアカウントでログインしてコメントできます