大多數「最佳科技棧」文章讀起來像有人在 Product Hunt 上瀏覽了一個下午,然後寫下他們的發現。他們會告訴你使用 React 和可能的 Postgres,加入 Stripe,就這樣結束了。當你試圖構建一個需要快速渲染 137,000 個列表頁面、支援 50 英里範圍內地理搜尋,以及讓使用者使用自然語言查詢尋找結果的目錄網站時,這並沒有幫助。

在過去的兩年裡,我一直在構建這些確切的網站。不是玩具項目 -- 生產目錄和市場平台,處理數十萬筆記錄、多國支付處理(包括零小數位貨幣等有趣的邊界情況),以及同時結合全文、語義 AI 和地理查詢的搜尋系統。本文介紹了我們在 Social Animal 使用的堆棧的每一層、我們選擇每一塊的原因,以及支持這些決策的生產數據。

目錄

Best Tech Stack for Directory & Marketplace Websites in 2026

為什麼目錄和市場網站在架構上是獨特的

目錄和市場網站看起來很簡單。列出一些東西,讓人們搜尋,也許進行支付。但是當你開始用真實數據構建一個時,你會遇到標準 SaaS 架構未準備的問題。

首先,有頁面數量問題。包含 100K+ 列表的目錄需要 100K+ 頁面。你不能在每次請求時伺服器渲染所有頁面,也不能在構建時靜態生成所有頁面(你的構建將花費數小時)。你需要更聰明的方式 -- 增量靜態再生成(ISR)或按需重新驗證。

其次,搜尋是多維度的。使用者想按文本搜尋(「家庭治療師」)、按意義搜尋(「幫助關係焦慮的人」)、以及按位置搜尋(「奧斯汀 20 英里範圍內」)。大多數堆棧可以處理其中的一個。同時處理這三個需要特定的數據庫架構。

第三,市場有超越簡單結帳的支付複雜性。你需要處理平台佣金、訂閱層級、多貨幣定價和跨國監管差異。如果你在任何方面出錯,你要麼在虧錢,要麼在違法。

這些限制塑造了我們堆棧中的每一個決策。讓我們逐層介紹。

10 層堆棧概覽

在我們深入之前,這是完整的圖景:

工具 原因
前端 Next.js 15(App Router) ISR 用於 100K+ 頁面,Server Components
數據庫 Supabase PostgreSQL 一個數據庫中的 pgvector + PostGIS + 全文搜尋
認證 Supabase Auth 行級別安全,基於角色的存取
支付 Stripe Connect 市場佣金,多貨幣
搜尋 三重模式(tsvector + pgvector + PostGIS) 文本 + 語義 + 地理同時進行
媒體 Supabase Storage + Next.js Image 最佳化交付,簡單上傳
託管 Vercel 邊界部署,ISR 支援,預覽 URL
電子郵件 Brevo API 來自 API 路由的交易 + 行銷
AI Claude API 語義搜尋,內容豐富,聊天機器人
監控 Vercel Analytics + PostHog 流量 + 使用者行為追蹤

這裡的每一層都在多個項目中的生產環境中運行。讓我向你展示它實際上看起來像什麼。

第 1 層:前端 -- Next.js 15

我們已經用 Next.jsAstro 構建了目錄網站。兩者都非常出色。但對於目錄和市場特別來說,使用 App Router 的 Next.js 15 因為一個功能而獲勝:增量靜態再生成。

這是真實的情況。我們的一個目錄項目呈現 137,000 個列表頁面。另一個處理 91,000 個。你不能在構建時靜態生成所有這些頁面 -- 構建將花費永遠,你會超過 Vercel 的函數執行限制。而且你不能在每次請求時伺服器渲染所有這些頁面,因為你的伺服器成本將是天文數字,你的首字節時間會受到影響。

ISR 給你兩個世界最好的。首位訪問者訪問一個頁面時觸發伺服器渲染,該頁面在邊界被緩存。後續訪問者獲得靜態版本。你設置一個重新驗證間隔(我們通常對列表頁面使用 3600 秒),並且緩存在後台刷新。

// app/listings/[slug]/page.tsx
export const revalidate = 3600; // 每小時重新驗證一次

export async function generateStaticParams() {
  // 僅預生成前 1000 個最訪問的列表
  const { data } = await supabase
    .from('listings')
    .select('slug')
    .order('view_count', { ascending: false })
    .limit(1000);
  
  return data?.map((listing) => ({ slug: listing.slug })) ?? [];
}

export default async function ListingPage({ params }: { params: { slug: string } }) {
  const { data: listing } = await supabase
    .from('listings')
    .select('*, categories(*), reviews(*)')
    .eq('slug', params.slug)
    .single();
    
  // Server Component -- 沒有為此發送客戶端 JavaScript
  return <ListingDetail listing={listing} />;
}

Server Components 是另一個大的勝利。列表詳細頁面主要是靜態內容 -- 名稱、描述、照片、評論。沒有理由為此向客戶端發送 React 的運行時。Server Components 在伺服器上渲染並發送純 HTML。你的列表頁面加載速度快,你的 JavaScript 包保持小。

我們很少使用客戶端組件:搜尋欄、互動式地圖、預訂表單,以及任何需要使用者互動的東西。其他所有內容都保持在伺服器上。

為什麼不用 Astro?

Astro 對於互動性最少的內容繁多的目錄非常棒。我們已經將其用於文檔網站和內容為焦點的項目。但市場網站需要經過身份驗證的狀態、即時功能和複雜的表單。Next.js 更自然地處理這些。如果你的目錄主要是唯讀的(想想:靜態商業目錄),Astro 值得考慮 -- 查看我們的 Astro 開發功能

Best Tech Stack for Directory & Marketplace Websites in 2026 - architecture

第 2 層:數據庫 -- Supabase PostgreSQL

這是堆棧中最主觀的選擇,也是我最有信心的。Supabase 提供 PostgreSQL 和它的所有擴展 -- 對於目錄/市場網站,三個擴展極其重要:pgvectorPostGIS 和 PostgreSQL 的內建全文搜尋。

跨越我們的目錄項目,我們在 Supabase 中管理 253,000+ 筆記錄。這包括列表、使用者資料、評論、預訂和訂閱數據。PostgreSQL 毫無困難地處理這些 -- 它被設計用來處理數量級更大的數據集。

真正的見解是這樣的:通過將全文搜尋、向量嵌入和地理數據保存在同一個數據庫中,你避免了跨多個服務同步數據的架構複雜性。你不需要 Elasticsearch 來進行文本搜尋。你不需要 Pinecone 來進行向量搜尋。你不需要單獨的地理服務。一個數據庫。一個事實來源。

-- 單個查詢,結合文本搜尋、向量相似性和地理近距離
SELECT 
  l.id,
  l.name,
  l.description,
  ts_rank(l.search_vector, plainto_tsquery('english', 'family therapist')) AS text_rank,
  1 - (l.embedding <=> $1::vector) AS semantic_similarity,
  ST_Distance(
    l.location::geography, 
    ST_MakePoint(-97.7431, 30.2672)::geography
  ) / 1609.34 AS distance_miles
FROM listings l
WHERE 
  l.search_vector @@ plainto_tsquery('english', 'family therapist')
  AND ST_DWithin(
    l.location::geography, 
    ST_MakePoint(-97.7431, 30.2672)::geography, 
    80467  -- 50 英里(米)
  )
ORDER BY 
  (text_rank * 0.3) + (semantic_similarity * 0.5) + ((1 - distance_miles/50) * 0.2) DESC
LIMIT 20;

那是一個查詢。全文排名、語義相似性評分和地理距離篩選 -- 全部在 PostgreSQL 中進行。試圖跨三個獨立的服務執行此操作,並保持結果一致。

要深入瞭解目錄的數據庫選項,請查看我們的 無頭 CMS 和數據庫對比

Supabase 行級別安全

RLS 值得單獨提及,因為它解決了困擾市場後端的問題:數據庫級別的數據存取控制。你不是在每個 API 端點中寫入授權檢查,而是在數據庫本身上定義策略。

-- 治療師只能看到他們自己的客戶記錄
CREATE POLICY "therapists_own_clients" ON client_records
  FOR SELECT USING (
    auth.uid() = therapist_id
    OR auth.jwt() ->> 'role' = 'admin'
  );

即使你的 API 代碼中有一個錯誤,意外地暴露了一個查詢,RLS 也會防止未授權的數據存取。對於處理敏感使用者數據的市場網站,這是不可協商的。

第 3 層:認證 -- Supabase Auth

由於我們已經在數據庫的 Supabase 生態系統中,使用 Supabase Auth 是自然的選擇。但我們為市場使用它的真正原因是直接與 RLS 集成的基於角色的存取。

我們的一個市場項目跨越三種不同的使用者類型運行基於角色的認證:管理員、服務提供者和客戶。每個角色看到不同的數據,擁有不同的權限,並存取不同的功能。另一個項目運行一個 4 層級的成員系統,其中每個層級逐漸解鎖更多的功能。

該實現將使用者的角色存儲在其 JWT 元數據中,這意味著 RLS 策略可以引用它而不需要額外的數據庫查詢:

// 在註冊期間分配角色
const { data, error } = await supabase.auth.signUp({
  email,
  password,
  options: {
    data: {
      role: 'therapist',
      tier: 'professional'
    }
  }
});

Supabase Auth 支援 OAuth 提供者(Google、Apple 等)、魔法連結和電子郵件/密碼 -- 全部開箱即用。對於 B2C 市場,社交登錄實際上是必需的。我們看到當 Google OAuth 與電子郵件/密碼一起可用時,註冊轉換率增加了 30-40%。

第 4 層:支付 -- Stripe Connect

支付處理是市場項目變得真正複雜的地方。「接受支付」和「接受支付、獲取平台佣金、處理退款、在 30 個國家/地區管理訂閱,以及處理零小數位貨幣」之間有很大的區別。

Stripe Connect 處理市場支付流 -- 平台和服務提供者之間的分割。我們的一個項目在每次交易中處理佣金,自動路由平台費用和提供者的分成。

但訂閱方面是有趣的地方。我們在 30+ 個國家/地區運行一個 4 層級訂閱系統,並實施區域定價。這意味著為不同的貨幣區域維護單獨的 Stripe 價格對象。

零小數位貨幣 Bug

這是一個我分享的故事,因為它為我們(和我們的客戶)節省了真正的金錢。Stripe 處理大多數貨幣,使用其最小單位 -- 所以 $10.00 USD 是 1000(美分)。但某些貨幣(如日本日圓(JPY)和韓國韓元(KRW))沒有小數單位。¥1000 就是 1000,而不是 100000

如果你的代碼盲目地乘以 100 轉換為最小單位,你將對日本使用者收費 100 倍的預期金額。我們在測試中捕捉了這個,但我見過未這樣做的生產市場。

const ZERO_DECIMAL_CURRENCIES = [
  'BIF', 'CLP', 'DJF', 'GNF', 'JPY', 'KMF', 'KRW', 
  'MGA', 'PYG', 'RWF', 'UGX', 'VND', 'VUV', 'XAF', 'XOF', 'XPF'
];

function formatAmountForStripe(amount: number, currency: string): number {
  if (ZERO_DECIMAL_CURRENCIES.includes(currency.toUpperCase())) {
    return Math.round(amount);
  }
  return Math.round(amount * 100);
}

區域試用排除

另一個陷阱:我們必須將某些東南亞國家排除在免費試用優惠之外,因為詐欺率使試用在這些地區在經濟上不可行。Stripe 的 API 讓你使用客戶稅務位置檢查進行設置,但你必須首先知道這是一個問題。這是你只有通過在生產中運行多國市場才學到的那種東西。

第 5 層:搜尋 -- 三重搜尋模式

這可能是本文中最有價值的架構模式。大多數目錄網站提供基本的文本搜尋。好的提供位置篩選。我們同時運行所有三種搜尋類型並混合結果。

全文搜尋(PostgreSQL tsvector):處理精確和詞幹化的關鍵字匹配。當有人搜尋「管道工」時,它也匹配「管道」。速度快,理解充分,內建於 Postgres。

語義搜尋(pgvector + Claude 嵌入):處理基於意義的查詢。「有人可以幫助我減少對我關係的焦慮」不包含「治療師」這個詞,但語義搜尋理解意圖。我們使用 Claude 的 API 為每個列表生成嵌入,並將它們存儲為 pgvector 中的向量。

地理搜尋(PostGIS):處理近距離查詢。「芝加哥市中心 25 英里範圍內」成為一個被索引和快速的空間查詢。

混合是有趣的地方。我們根據查詢對每種搜尋類型的權重不同:

interface SearchWeights {
  textWeight: number;
  semanticWeight: number;
  geoWeight: number;
}

function calculateWeights(query: string, hasLocation: boolean): SearchWeights {
  const isNaturalLanguage = query.split(' ').length > 4;
  
  if (hasLocation && isNaturalLanguage) {
    return { textWeight: 0.2, semanticWeight: 0.5, geoWeight: 0.3 };
  } else if (hasLocation) {
    return { textWeight: 0.4, semanticWeight: 0.2, geoWeight: 0.4 };
  } else if (isNaturalLanguage) {
    return { textWeight: 0.2, semanticWeight: 0.7, geoWeight: 0.1 };
  }
  return { textWeight: 0.7, semanticWeight: 0.2, geoWeight: 0.1 };
}

短關鍵字查詢依靠全文搜尋。較長的自然語言查詢依靠語義搜尋。帶有位置組件的查詢增加地理權重。我們的一個目錄網站在 137K+ 列表中運行這個三重模式,搜尋結果明顯好於使用基本文本匹配的競爭對手。

第 6 層:媒體 -- Supabase Storage + Next.js Image

目錄網站是影像密集的。列表照片、個人資料圖片、標誌 -- 加起來。我們使用 Supabase Storage 來上傳,使用 Next.js <Image> 組件來最佳化交付。

關鍵配置是設置 Supabase Storage 儲存區(帶有適當的存取策略 -- 列表照片為公開,使用者文件為私有),然後使用 Next.js Image 最佳化來以正確的尺寸提供 WebP/AVIF 格式:

<Image
  src={`${process.env.NEXT_PUBLIC_SUPABASE_URL}/storage/v1/object/public/listings/${listing.image_path}`}
  alt={listing.name}
  width={800}
  height={600}
  sizes="(max-width: 768px) 100vw, (max-width: 1200px) 50vw, 33vw"
  loading="lazy"
/>

當部署在 Vercel 上時,Next.js 自動處理格式轉換、調整大小和緩存。與直接提供原始上傳相比,我們看到影像有效載荷減少了 60-70%。

第 7 層:託管 -- Vercel

所有我們的生產目錄和市場網站都在 Vercel 上運行。原因很簡單:Vercel 是 Next.js 最好的運行地點。ISR、Server Components、邊際中間件、預覽部署 -- 一切都無需配置即可工作。

對於目錄網站特別來說,邊際網絡很重要。東京的使用者搜尋目錄應該從附近的邊際節點獲取快取的列表頁面,而不是從維吉尼亞州的伺服器。Vercel 的邊際緩存對 ISR 頁面自動執行此操作。

預覽部署對於具有多個利益相關者的市場項目也很龐大。每個拉取請求都獲得自己的 URL。客戶可以在真實 URL 上使用真實數據查看新搜尋 UI,然後才能進入生產環境。

Vercel 的 Pro 計畫每位團隊成員每月 $20 涵蓋了大多數目錄項目。較大的網站(100K+ 頁面)可能需要企業計畫來獲得更高的 ISR 限制和專屬支援。

第 8 層:電子郵件 -- Brevo API

市場項目中的電子郵件分為兩個類別:交易性(預訂確認、密碼重置、支付收據)和行銷性(新聞通訊、功能公告、重新參與)。

我們使用 Brevo(前身為 Sendinblue)用於兩者,從 Next.js API 路由調用:

// app/api/send-booking-confirmation/route.ts
import { NextResponse } from 'next/server';

export async function POST(request: Request) {
  const { to, bookingDetails } = await request.json();
  
  const response = await fetch('https://api.brevo.com/v3/smtp/email', {
    method: 'POST',
    headers: {
      'api-key': process.env.BREVO_API_KEY!,
      'content-type': 'application/json',
    },
    body: JSON.stringify({
      to: [{ email: to }],
      templateId: 12, // 預訂確認模板
      params: bookingDetails,
    }),
  });
  
  return NextResponse.json({ success: response.ok });
}

Brevo 的免費層每天處理 300 封電子郵件,足以用於早期市場。他們的付費計畫起價 $9/月,用於 5,000 封電子郵件。與 SendGrid 或 Mailgun 相比,我們發現 Brevo 的可交付性率相當,定價對於成長中的項目更可預測。

第 9 層:AI -- Claude API

AI 不是我們目錄堆棧的噱頭 -- 它是一個核心基礎設施組件,處理三項不同的工作。

語義搜尋嵌入:每個列表都獲得由 Claude 生成的嵌入,該嵌入捕捉其意義。這為上述語義搜尋層供電。

內容豐富:對於具有使用者提交列表的目錄,品質差異很大。我們使用 Claude 來規範化描述、提取結構化數據(小時、特性、服務區域),並生成 SEO 友好的摘要。

互動功能:我們的一個項目運行我們稱之為「Oracle Council」的東西 -- 五個不同的 AI 角色,使用者可以在不同類型的指導中諮詢。每個角色都有自己的系統提示、個性和專業領域。這聽起來很輕率,但它驅動了顯著的參與度,是網站最受歡迎的功能之一。

Claude API 定價(截至 2025-2026):Claude 3.5 Sonnet 的成本為每百萬輸入令牌 $3 和每百萬輸出令牌 $15。對於 100K 列表目錄中的嵌入生成,一次性成本大約是 $50-80。根據流量,搜尋查詢和聊天機器人互動的持續成本通常運行 $100-300/月。

第 10 層:監控 -- Vercel Analytics + PostHog

你需要兩種類型的目錄網站監控:性能指標和使用者行為分析。

Vercel Analytics 提供網絡生命週期信號(LCP、CLS、INP)、真實使用者監控和流量數據。它內建於 Vercel 儀表板中,無需零配置。對於目錄網站,我們在列表頁面上密切監控 LCP -- 如果它爬升到 2.5 秒以上,我們知道 ISR 配置或影像最佳化有問題。

PostHog 處理產品分析:哪些搜尋查詢返回零結果(所以我們知道要填補哪些內容空白)、哪些列表類別獲得最多查看、使用者在預訂或註冊流中下降的位置。PostHog 的免費層涵蓋每月最多 100 萬個事件,處理大多數早期市場。

該組合為你提供「網站速度快嗎?」和「使用者在找到他們需要的東西嗎?」-- 兩個非常不同但同樣重要的問題。

完整堆棧對比表

我們的選擇 替代方案 我們選擇的原因
前端 Next.js 15 Astro、Remix 用於 100K+ 頁面的 ISR
數據庫 Supabase PostgreSQL PlanetScale、Neon 一個 DB 中的 pgvector + PostGIS
認證 Supabase Auth Clerk、Auth.js 本機 RLS 集成
支付 Stripe Connect Paddle、LemonSqueezy 市場分割、多貨幣
搜尋 三重模式(在 DB 中) Algolia、Elasticsearch 無外部同步、成本較低
媒體 Supabase Storage Cloudinary、S3 同一生態系統、更簡單的計費
託管 Vercel Netlify、AWS Amplify 最佳 Next.js ISR 支援
電子郵件 Brevo API SendGrid、Resend 價格/可交付性比率
AI Claude API OpenAI、Gemini 對內容任務最佳的推理
監控 Vercel + PostHog Datadog、Mixpanel 免費層涵蓋早期增長

這個堆棧在生產環境中的成本

讓我們談論一個具有大約 50K 列表和中等流量(每月 50K 訪客)的目錄網站的真實數字:

服務 計畫 月費
Vercel Pro $20
Supabase Pro $25
Stripe 按使用付費 每次交易 2.9% + 30¢
Brevo 初始版 $9
Claude API 基於使用 ~$150
PostHog 免費層 $0
固定成本總計 ~$204/月

對於一個生產市場平台,這是非常實惠的。Supabase Pro 計畫為你提供 8GB 的數據庫空間、250GB 的頻寬和 100GB 的存儲 -- 足以用於具有 50K 列表的目錄。

當你超過 100K 列表並進入更高的流量時,預期成本增加到大約 $500-800/月。對於運行專用伺服器、管理的 Elasticsearch 叢集和單獨的向量數據庫的舊方法,仍然便宜得多。

如果你計畫一個目錄或市場項目並想更詳細地瞭解定價,請查看我們的 定價頁面聯繫我們 以獲取特定於項目的估計。

常見問題

2026 年目錄網站的最佳數據庫是什麼? 通過 Supabase 的 PostgreSQL 是我們的首選推薦。pgvector 用於語義搜尋、PostGIS 用於地理查詢和內建全文搜尋的組合意味著你可以處理所有三個搜尋維度,而無需外部服務。跨我們的生產項目有 253K+ 記錄,它無需問題處理目錄規模的數據。PlanetScale(基於 MySQL)等替代品缺乏 PostGIS 支援,使地理搜尋明顯更難。

Next.js 能為目錄網站處理 100,000+ 頁面嗎? 是的,但你需要 ISR(增量靜態再生成)。你不是在構建時生成所有 100K 頁面。相反,你預生成最高流量的頁面(也許是前 1,000-5,000),並讓 ISR 按需生成其餘頁面。我們在生產中進行了 137K 頁面的此操作。關鍵是設置適當的重新驗證間隔 -- 我們對列表頁面使用 3600 秒(1 小時),對類別/搜尋頁面使用更短的間隔。

語義搜尋如何在目錄網站中工作? 每個列表都被轉換為捕捉其意義的數值向量(「嵌入」),使用像 Claude 這樣的 AI 模型。當使用者使用自然語言進行搜尋時 -- 「有人可以幫助患有 ADHD 的孩子」-- 該查詢也被轉換為向量。數據庫使用 pgvector 的余弦相似性運算子找到向量在數學上接近查詢向量的列表。即使列表不包含搜尋查詢中的確切詞,這也有效。

Stripe Connect 對於市場來說是必要的,或者我可以使用常規 Stripe? 如果你的市場涉及買方和賣方之間的支付(或客戶和服務提供者),你需要 Stripe Connect。常規 Stripe 只讓你接受對你自己帳戶的支付。Connect 處理分割 -- 獲取平台佣金並將剩餘部分路由給服務提供者。它還為美國的賣方處理 1099 報告,這是你不想自己構建的合規性要求。

從頭開始構建目錄網站的成本是多少? 使用本文概述的堆棧,你的持續基礎設施成本開始約 $200/月,用於中等規模的目錄。開發成本根據功能大幅變化,但具有搜尋、使用者帳戶和列表管理的生產就緒目錄通常需要 8-16 週構建。具有支付、預訂和訂閱的完整市場再增加 4-8 週。你可以探索我們的 目錄和市場開發功能 以獲取更多細節。

我應該使用 Algolia 或 Elasticsearch 而不是數據庫中搜尋嗎? 對於大多數目錄網站,沒有。在主數據庫和單獨搜尋服務之間同步數據的複雜性造成了錯誤、增加了延遲、並增加了成本。Algolia 根據搜尋操作收費 -- 按比例,這會變得昂貴(他們的定價開始於 Build 計畫上每 1,000 次搜尋請求 $1)。PostgreSQL 的內建搜尋功能,特別是與 pgvector 結合時,很好地處理目錄規模的搜尋。異常:如果你需要拼寫容限和分面篩選,在數百萬筆記錄上使用低於 10ms 的響應時間,Algolia 值得複雜性。

構建目錄與市場之間的區別是什麼? 目錄列出東西並讓使用者找到它們。市場增加交易 -- 支付、預訂、佣金,通常是提供者和消費者之間的雙向互動。科技堆棧大致相同,但市場增加 Stripe Connect(或等效物)、更複雜的認證角色和交易電子郵件流。數據庫架構也因訂單、發票和支出追蹤表而變得更複雜。

我可以將 AI 功能添加到現有目錄網站嗎? 絕對地。我們堆棧中的 AI 層是附加的,而不是基礎的。你可以通過為現有列表生成嵌入(一次性批量工作)、將它們存儲在 pgvector 列中並在現有文本搜尋旁邊添加語義搜尋端點來添加語義搜尋。內容豐富和聊天機器人功能可以添加為獨立 API 路由。最難的部分通常是大型現有數據集的嵌入生成 -- 為 100K 列表分配幾小時的處理時間和 $50-100 的 API 成本。