我對相當多個內容網站進行過試驗—使用過 Contentful、Sanity、Strapi,以及大約六個其他無頭 CMS 平台。它們非常不錯,直到它們不再是。一旦你需要比如說 50,000 個位置頁面或從結構化資料中構建靈敏目錄,標準 CMS 就開始感覺像是用膠帶粘合在一起的。這時我就轉向 Supabase。

這不是「Supabase 是新型 CMS」的宣言。哦不,這遠比這更微妙。在某些特定情況下,PostgreSQL 資料庫配合可靠的 API 層相比 CMS 完全佔上風,特別是在程序化 SEO 的大遊戲中。請跟著我,當我說明何時做出這種轉變、為什麼這很關鍵,以及你如何把一切設置妥當。

目錄

Supabase vs 無頭 CMS:何時為程序化 SEO 使用資料庫

程序化 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 驅動的記錄豐富,甚至排定的更新。方便!

Supabase vs 無頭 CMS:何時為程序化 SEO 使用資料庫 - 架構

有效的架構模式

我建立過一些程序化 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 提取草稿資料(使用像 draftpublished 這樣的 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 查看我們的 定價頁面