Supabase vs 無頭CMS:何時使用資料庫進行程式化SEO
我對相當多個內容網站進行過試驗—使用過 Contentful、Sanity、Strapi,以及大約六個其他無頭 CMS 平台。它們非常不錯,直到它們不再是。一旦你需要比如說 50,000 個位置頁面或從結構化資料中構建靈敏目錄,標準 CMS 就開始感覺像是用膠帶粘合在一起的。這時我就轉向 Supabase。
這不是「Supabase 是新型 CMS」的宣言。哦不,這遠比這更微妙。在某些特定情況下,PostgreSQL 資料庫配合可靠的 API 層相比 CMS 完全佔上風,特別是在程序化 SEO 的大遊戲中。請跟著我,當我說明何時做出這種轉變、為什麼這很關鍵,以及你如何把一切設置妥當。
目錄
- 程序化 SEO 實際需要什麼
- 無頭 CMS 的天花板
- 為什麼 Supabase 適合程序化 SEO
- 有效的架構模式
- 你何時仍應使用無頭 CMS
- 混合方法:CMS + Supabase 搭配使用
- 為程序化 SEO 設置 Supabase
- 效能和成本比較
- 常見問題

程序化 SEO 實際需要什麼
程序化 SEO 就像創造一個網頁工廠。你在生成大量網頁,每個都針對非常具體的長尾關鍵詞。想想 Zapier 的應用頁面、Nomadlist 的無盡城市比較,或 Wise 的幫助型匯率頁面。這些頁面?它們由範本構建,充滿獨特資料,每個都針對自己的搜尋查詢。
殺手級程序化 SEO 需要什麼?
- 量級:我們在談論數百、數千、甚至可能數萬個頁面。
- 結構化資料:內容需要遵循可預測的模式,但具有可變資料點。
- 關係:你有相互關連的資料—比如城市與社區的關係或產品分類。
- 頻繁更新:價格變化、統計資料更新、新東西不斷出現。
- 查詢靈活性:你需要以你過去沒有預測的方式篩選和切割資料。
無頭 CMS?對於像部落格文章或登陸頁面這樣的編輯內容很好。它提供漂亮的用戶介面、富文本編輯等等。問題出現在你的「內容」實際上是插入範本的資料時。這樣,你就在與 CMS 的限制作鬥爭。
無頭 CMS 的天花板
去年在一個項目中使用 Contentful 時撞到了牆。想像一下:一個 SaaS 比較網站,說「工具 A 對比工具 B」大約有 2,000 個軟體項目。算一下,你在看大約兩百萬個潛在頁面。
無頭 CMS 系統在哪裡開始動搖?
API 速率限制
Contentful 的免費限制是每秒 200 個 API 請求。Team 計畫?相同限制。嘗試構建數千個頁面,限制就會直接擊中你。Sanity 也沒有好多少—上限是每月 500K API 請求。達到規模—這些數字會狠狠咬你。
條目限制和定價
大多數平台根據條目或記錄數量收費。所以當你在處理比如說 50,000 條記錄時,突然地,這個定價變得...讓我們說吧,令人不適:
| 平台 | 免費層記錄 | 50K 記錄成本 | 100K 記錄成本 |
|---|---|---|---|
| Contentful | 25,000 個條目 | ~$489/月(Premium) | 自訂定價 |
| Sanity | 100K 文件(免費) | 免費(但有 API 限制) | 免費(但有 API 限制) |
| Strapi Cloud | 無限制(自託管) | ~$99/月 + 託管 | ~$99/月 + 託管 |
| Supabase | 500MB(無限行) | $25/月(Pro) | $25/月(Pro) |
Sanity 對文件數量相當大方,但偷偷靠近 API 使用率就不太友善。Supabase 呢?根據資料庫大小收費,不是行數。當你在處理大量資料時,這是個遊戲改變者。
查詢限制
這可能是決勝局。無頭 CMS 的查詢語言— Contentful 的 API 或 Sanity 的 GROQ—是為更簡單的請求而構建的。但複雜的 JOIN、聚合、全文搜尋帶排名等等?它不夠。進入 Supabase。完整的 Postgres。所有 SQL 魔術都在你的指尖。
-- 在 CMS 查詢語言中很難做到這個
SELECT
t1.name AS tool_a,
t2.name AS tool_b,
t1.pricing - t2.pricing AS price_difference,
array_agg(DISTINCT f.name) FILTER (WHERE ft1.tool_id IS NOT NULL AND ft2.tool_id IS NULL) AS unique_to_a,
array_agg(DISTINCT f.name) FILTER (WHERE ft2.tool_id IS NOT NULL AND ft1.tool_id IS NULL) AS unique_to_b
FROM tools t1
CROSS JOIN tools t2
LEFT JOIN features_tools ft1 ON ft1.tool_id = t1.id
LEFT JOIN features_tools ft2 ON ft2.tool_id = t2.id AND ft2.feature_id = ft1.feature_id
LEFT JOIN features f ON f.id = COALESCE(ft1.feature_id, ft2.feature_id)
WHERE t1.id < t2.id
GROUP BY t1.id, t2.id;
試著用 GROQ 或在 Contentful 的 API 中拉出那個。你會被埋在 API 呼叫中,並在代碼中手動重新組裝資料。
為什麼 Supabase 適合程序化 SEO
Supabase 就像帶著一些花俏功能的託管 Postgres。它從你的資料庫自動生成一個 RESTful API,並包含即時訂閱、身份驗證、邊緣函數和儀表板—本質上將你的所有任務包裝在一個整潔的包裡。
PostgREST API
使用 Supabase,你直接從你的資料庫表獲得一個 RESTful API。每個表的 CRUD。你可以排序、篩選、分頁—你想要的一切。完美用於在 Next.js 或 Astro 中提取構建時資料。
// 在 Next.js 中為程序化 SEO 頁面提取資料
import { createClient } from '@supabase/supabase-js'
const supabase = createClient(process.env.SUPABASE_URL!, process.env.SUPABASE_ANON_KEY!)
export async function generateStaticParams() {
const { data: cities } = await supabase
.from('cities')
.select('slug')
return cities?.map(city => ({ slug: city.slug })) ?? []
}
export default async function CityPage({ params }: { params: { slug: string } }) {
const { data: city } = await supabase
.from('cities')
.select(`
*,
neighborhoods (*),
cost_of_living (*),
coworking_spaces (count)
`)
.eq('slug', params.slug)
.single()
// 用真實資料渲染你的範本
}
複雜邏輯的資料庫函數
當 REST API 不夠時,Postgres 函數是你的新摯友。你可以建立函數透過 RPC 呼叫,用於所有那些複雜的計算、生成資料和聚合細節。
CREATE OR REPLACE FUNCTION get_city_comparison(city_a_slug TEXT, city_b_slug TEXT)
RETURNS JSON AS $$
SELECT json_build_object(
'city_a', (SELECT row_to_json(c) FROM cities c WHERE c.slug = city_a_slug),
'city_b', (SELECT row_to_json(c) FROM cities c WHERE c.slug = city_b_slug),
'cost_difference', (
SELECT a.cost_index - b.cost_index
FROM cities a, cities b
WHERE a.slug = city_a_slug AND b.slug = city_b_slug
)
)
$$ LANGUAGE sql;
公開資料的行級安全性
你的大多數資料將被公開,尤其是對於 SEO 項目。Supabase 有這個行級安全性功能,讓你的資料保持安全但可訪問—讓你分享表和欄位而不會失眠擔心資料洩露。
邊緣函數用於資料豐富
你可能需要來自外部 API 的資料,或者也許你在篩選 CSV。Supabase 的邊緣函數就在你的資料庫旁邊無伺服器執行。我用過這些用於資料導入、AI 驅動的記錄豐富,甚至排定的更新。方便!

有效的架構模式
我建立過一些程序化 SEO 網站有一段時間,幾個模式運作得非常好。讓我分享它們:
模式 1:使用 ISR 的靜態生成
對於有 1,000 到 100,000 個頁面之間、經常更新的網站來說,這是黃金。
- 框架:Next.js 使用
generateStaticParams或 Astro 帶靜態輸出 - 資料來源:Supabase Postgres
- 構建策略:靜態生成前 1,000 個頁面,並對其餘部分使用 ISR(增量靜態重新生成)。
- 更新機制:Supabase Webhook 觸發 Vercel 部署鉤子以進行完全重建或按需頁面重新驗證。
我們經常在我們的 Next.js 項目中使用這個。縮放得很好!
模式 2:混合靜態 + 伺服器
對於擁有 100K+ 頁面或資料經常變化的大型網站來說完美。
- 框架:Next.js App Router 帶伺服器組件,或 Astro 帶伺服器端渲染
- 資料來源:Supabase(使用連線池化,如 Supavisor)
- 構建策略:在構建時建立網站地圖,並在需要時按需呈現頁面,具有積極的快取。
- 快取:使用 Vercel 的資料快取或 Cloudflare 的快取帶 stale-while-revalidate 標頭。
模式 3:資料庫驅動的網站地圖
在程序化 SEO 中,你不想忘記你的網站地圖。直接從資料庫生成它:
// app/sitemap.ts (Next.js)
import { createClient } from '@supabase/supabase-js'
export default async function sitemap() {
const supabase = createClient(
process.env.SUPABASE_URL!,
process.env.SUPABASE_SERVICE_ROLE_KEY!
)
const { data: cities } = await supabase
.from('cities')
.select('slug, updated_at')
.order('updated_at', { ascending: false })
return cities?.map(city => ({
url: `https://example.com/cities/${city.slug}`,
lastModified: city.updated_at,
changeFrequency: 'weekly' as const,
priority: 0.8,
})) ?? []
}
你何時仍應使用無頭 CMS
讓我們解決房間裡的大象:Supabase 不會在每個用例中擊敗無頭 CMS。以下是你何時想要堅持 CMS:
- 編輯內容:部落格、案例研究或需要豐富格式化的長文章?CMS 拜託—作者會感謝你。
- 營銷頁面:那些需要無開發人員調整的?帶視覺編輯器的 CMS 是你需要的。
- 小規模內容:主要是文字、少於 500 頁?CMS 設置簡單得多。
- 非技術團隊:如果 SQL 聽起來像是對你的團隊進行刑訊逼供,CMS 更友善。
- 內容工作流:審批鏈、版本控制、發佈排程—堅持 CMS。
在這些情況下,我們通常在我們的 無頭開發解決方案中推薦像 Sanity、Contentful 或 Storyblok 這樣的平台。
混合方法:CMS + Supabase 搭配使用
老實說,這是我對大多數項目的首選:混合兩者。讓 CMS 對編輯內容做它的事,而 Supabase 處理程序化資料。
一個真實世界的例子:我們構建了一個房地產平台,其中:
- Sanity 管理部落格內容、代理商資料和關於頁面
- Supabase 處理了 80,000+ 房產清單、社區資料、價格歷史和學校評分。
- Next.js 在構建和運行時從兩個來源提取。
結果?編輯團隊不需要擔心資料庫,資料管道從未與 CMS 糾纏。每個工具在自己的角色中閃閃發光。
// 從兩個來源提取的頁面
import { sanityClient } from '@/lib/sanity'
import { supabase } from '@/lib/supabase'
export default async function NeighborhoodPage({ params }) {
// 來自 Sanity 的編輯內容
const editorial = await sanityClient.fetch(
`*[_type == "neighborhoodGuide" && slug.current == $slug][0]`,
{ slug: params.slug }
)
// 來自 Supabase 的結構化資料
const { data: stats } = await supabase
.from('neighborhood_stats')
.select('*, schools(*), listings(count)')
.eq('slug', params.slug)
.single()
return <NeighborhoodTemplate editorial={editorial} stats={stats} />
}
這個設置允許你兩個世界最好的東西而無需妥協。
為程序化 SEO 設置 Supabase
讓我們卷起袖子。以下是為程序化 SEO 項目設置 Supabase 的硬碰硬。我們將使用一個假設的「城市指南」網站。
步驟 1:設計你的架構
考慮實體及其關係,不只是內容類型:
CREATE TABLE countries (
id SERIAL PRIMARY KEY,
name TEXT NOT NULL,
slug TEXT UNIQUE NOT NULL,
continent TEXT,
currency_code TEXT
);
CREATE TABLE cities (
id SERIAL PRIMARY KEY,
country_id INTEGER REFERENCES countries(id),
name TEXT NOT NULL,
slug TEXT UNIQUE NOT NULL,
population INTEGER,
latitude DECIMAL(10, 8),
longitude DECIMAL(11, 8),
cost_index DECIMAL(5, 2),
safety_score DECIMAL(3, 2),
internet_speed_mbps INTEGER,
meta_title TEXT,
meta_description TEXT,
updated_at TIMESTAMPTZ DEFAULT NOW()
);
CREATE TABLE city_monthly_weather (
id SERIAL PRIMARY KEY,
city_id INTEGER REFERENCES cities(id),
month INTEGER CHECK (month BETWEEN 1 AND 12),
avg_temp_celsius DECIMAL(4, 1),
avg_rainfall_mm DECIMAL(5, 1),
sunshine_hours INTEGER,
UNIQUE(city_id, month)
);
-- 常見查詢模式的索引
CREATE INDEX idx_cities_country ON cities(country_id);
CREATE INDEX idx_cities_slug ON cities(slug);
CREATE INDEX idx_cities_cost ON cities(cost_index);
步驟 2:設置 RLS 策略
-- 啟用 RLS
ALTER TABLE cities ENABLE ROW LEVEL SECURITY;
ALTER TABLE countries ENABLE ROW LEVEL SECURITY;
-- 允許公開讀取存取
CREATE POLICY "Public read access" ON cities
FOR SELECT USING (true);
CREATE POLICY "Public read access" ON countries
FOR SELECT USING (true);
步驟 3:為 SEO 資料建立資料庫函數
CREATE OR REPLACE FUNCTION get_similar_cities(target_slug TEXT, match_count INTEGER DEFAULT 5)
RETURNS SETOF cities AS $$
SELECT c2.*
FROM cities c1, cities c2
WHERE c1.slug = target_slug
AND c2.id != c1.id
ORDER BY
ABS(c2.cost_index - c1.cost_index) +
ABS(c2.safety_score - c1.safety_score) * 10
LIMIT match_count
$$ LANGUAGE sql;
步驟 4:批量導入你的資料
雖然 Supabase 儀表板讓你導入 CSV,對於更大的資料集,透過用戶端庫或直接透過 Postgres:
import { createClient } from '@supabase/supabase-js'
import { parse } from 'csv-parse/sync'
import { readFileSync } from 'fs'
const supabase = createClient(SUPABASE_URL, SUPABASE_SERVICE_ROLE_KEY)
const cities = parse(readFileSync('./data/cities.csv', 'utf-8'), {
columns: true,
cast: true,
})
// 以 500 個為一組批量插入
for (let i = 0; i < cities.length; i += 500) {
const chunk = cities.slice(i, i + 500)
const { error } = await supabase.from('cities').upsert(chunk, {
onConflict: 'slug',
})
if (error) console.error(`Batch ${i / 500} failed:`, error)
}
效能和成本比較
現在,讓我們談談成本和速度。以下是在 2025 年運行項目後的實際情況:
| 指標 | 無頭 CMS(Contentful Team) | Supabase Pro | 自託管 Strapi |
|---|---|---|---|
| 月度成本(50K 記錄) | $489/月 | $25/月 | ~$20-50/月(託管) |
| API 響應時間(平均) | 80-150ms(CDN) | 30-80ms(直接) | 50-120ms |
| 構建時間(10K 頁面) | 15-25 分鐘(速率受限) | 3-8 分鐘 | 5-12 分鐘 |
| 查詢靈活性 | 有限篩選 | 完整 SQL | 有限(REST/GraphQL) |
| 最大記錄(實際) | ~100K | 數百萬 | 取決於託管 |
| 內建全文搜尋 | 基本 | Postgres FTS | 需要外掛 |
| 即時更新 | 僅 Webhook | 原生 WebSocket | 僅 Webhook |
| 非開發人員的管理 UI | 優秀 | 基本(儀表板) | 良好 |
成本節省?引人注目。對於有 50K+ 資料記錄的大型 SEO 項目,通過選擇 Supabase 而不是高級 CMS,你看著節省 $400+/月。超過 12 個月,那將近 $5,000。
速度?從 20 分鐘的構建減少到 5 分鐘?是的,它從根本上改變了你迭代和開發的方式。
常見問題
Supabase 能為程序化 SEO 處理數百萬行嗎? 當然!Supabase 建立在 Postgres 的堅實肩膀上。只要你的索引遊戲一流,它可以輕鬆處理數千萬行。我在 Pro 計畫上管理過擁有超過兩百萬行的程序化 SEO 項目,一路順風。只要避免在頁面生成期間陷入 N+1 查詢陷阱。
如果頁面是伺服器渲染的,Supabase 對 SEO 有好處嗎? Supabase 本身不會干擾 SEO。它只是你的資料層,別的什麼都不是。真正重要的是你如何輸出這些頁面—靜態(SSG)或伺服器端(SSR)才是讓它們可爬行的。Supabase 只是比 CMS API 更快、更靈活地提供該資料。Google 不在乎你的資料來自哪裡。
非技術團隊成員如何在 Supabase 中編輯資料? 這是痛點—這是 Supabase 相對 CMS 陷入困境的一個地方。儀表板就像電子表格編輯器一樣工作,對簡單更改很好。但對於更友善的體驗,使用 Retool、Appsmith 構建一個輕量級管理面板,或甚至一個基本的 Next.js 管理路由是聰明的。某些團隊使用無伺服器函數同步 Google Sheets 與 Supabase。對於資料調整,令人驚訝地有效。
我應該為程序化 SEO 使用 Supabase 還是 Firebase? Supabase,無可爭議。Firebase 的 Firestore 是一個 NoSQL 文件資料庫,使關係查詢成為煩人的事。程序化 SEO 通常處理關係資料—考慮實體和層級。Postgres 透過 Supabase?自然處理它。另外,Firestore 根據讀取操作收費,當你在構建時生成數千個頁面時,你的錢包感到熱量。
我可以將 Supabase 與 Astro 用於程序化 SEO 嗎?
絕對可以,這是一個相當甜蜜的組合。Astro 的靜態網站生成速度極快,其內容集合與從 Supabase 提取的資料配對得很好。在構建時,你將在 getStaticPaths 函數中查詢 Supabase 以生成無盡的靜態頁面。我們在我們的 Astro 項目中有過超級結果。
我如何在沒有 CMS 的情況下處理內容預覽?
你需要里程數來構建這個,但這是前提:製作一個預覽 API 路由,從 Supabase 提取草稿資料(使用像 draft 或 published 這樣的 status 欄位)並呈現頁面。簡單的身份驗證檢查可以確保只有你的團隊可以存取這些預覽。不如 CMS 預覽那麼圓滑,但嘿,它在 50 行 Next.js 代碼中完成工作。
在規模上生成元標題和描述的最佳方式是什麼?
將範本字串植入你的代碼,用資料餵食它們。也許:${city.name} Cost of Living Guide ${new Date().getFullYear()} | Rent, Food & Transport Costs。對於獨特的描述,試著透過 Supabase Edge Function 使用 GPT-4o-mini 來自動生成和儲存每個頁面的元描述。以 $0.15 每百萬輸入標記(那些聰明的 2025 年價格!),製作 100K 元描述低於 $5。
對於大型程序化 SEO 項目,Supabase 成本多少? Pro 計畫在 $25/月將滿足大多數需求。有 8GB 儲存空間、250GB 頻寬和 500MB 邊緣函數呼叫空間。如果你的資料集超過 8GB,它只是 $0.125/GB 月度。一個 50GB 資料庫?大約 $30.25/月。相比大狗 CMS 定價?不甚相同。想要更多細節?如果你想知道完整構建看起來像什麼,pop 查看我們的 定價頁面。