大多数"最佳科技栈"文章读起来就像某人下午浏览了一下 Product Hunt 然后写了他们的发现。他们会告诉你使用 React 和 Postgres,扔进 Stripe,就到此为止了。当你试图构建一个需要快速渲染 137,000 个列表页面、支持 50 英里范围内地理搜索、让用户使用自然语言查询找到结果的目录网站时,这并不会有帮助。

在过去两年里,我一直在构建这些确切的网站。不是玩具项目 -- 是处理数十万条记录、多国支付处理(包括有趣的边界情况,如零小数货币)的生产目录和市场平台,以及结合全文搜索、语义 AI 和地理查询的搜索系统。本文介绍了我们在 Social Animal 使用的堆栈的每一层、我们为什么选择每个部分,以及支持这些决定的生产数据。

目录

2026 年目录和市场网站的最佳科技栈

为什么目录和市场网站在架构上是独特的

目录和市场网站在表面上看起来很简单。列出一些东西,让人们搜索,也许处理一个支付。但是一旦你开始用真实数据构建一个,你就会遇到标准 SaaS 架构没有为之准备的问题。

首先,有页面数量问题。一个有 100K+ 列表的目录需要 100K+ 页面。你不能在每个请求上服务端渲染所有页面,也不能在构建时静态生成所有页面(你的构建将花费数小时)。你需要更聪明的东西 -- 增量静态再生(ISR)或按需重新验证。

其次,搜索是多维的。用户想按文本搜索("家庭治疗师")、按意义搜索("帮助处理关系焦虑的人")以及按位置搜索("奥斯汀方圆 20 英里内")。大多数堆栈处理其中之一。同时处理所有三个需要特定的数据库架构。

第三,市场具有远超简单结账的支付复杂性。你需要处理平台佣金、订阅等级、多货币定价和各国监管差异。任何出错,你要么亏钱,要么违反法律。

这些约束影响了我们堆栈的每个决定。让我们逐层讲解。

10 层堆栈概览

在深入之前,这是完整的图景:

工具 原因
前端 Next.js 15 (App Router) ISR 用于 100K+ 页面,服务器组件
数据库 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 构建了目录网站。两者都非常好。但是对于目录和市场网站来说,Next.js 15 with App Router 因为一个功能而获胜:增量静态再生。

这是真实的场景。我们的一个目录项目渲染 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();
    
  // 服务器组件 -- 不会为此发送客户端 JavaScript
  return <ListingDetail listing={listing} />;
}

服务器组件是另一个大赢家。列表详细页面大多是静态内容 -- 名称、描述、照片、评论。没有理由为此向客户端发送 React 的运行时。服务器组件在服务器上渲染并发送纯 HTML。你的列表页面加载速度快,你的 JavaScript 包保持小。

我们很少使用客户端组件:搜索栏、交互式地图、预订表格,以及任何需要用户交互的东西。其他一切都留在服务器上。

为什么不是 Astro?

Astro 对于交互性最少的内容驱动目录来说是很好的。我们已经将其用于文档网站和以内容为重点的项目。但是市场网站需要经过身份验证的状态、实时功能和复杂的表单。Next.js 更自然地处理这些。如果你的目录大多是只读的(想象:一个静态业务目录),Astro 值得考虑 -- 查看我们的 Astro 开发功能

2026 年目录和市场网站的最佳科技栈 - 架构

第 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 中进行。尝试在三个单独的服务中执行此操作,并保持结果的一致性。

有关目录数据库选项的更深入信息,请查看我们的 headless 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 代码中有一个 bug 意外暴露了查询,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 Price 对象。

零小数货币 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、服务器组件、边缘中间件、预览部署 -- 一切都无需配置即可工作。

对于目录网站特别是,边缘网络很重要。在东京搜索目录的用户应该从附近的边缘节点获得缓存的列表页面,而不是来自弗吉尼亚的服务器。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 为你提供 Web Vitals(LCP、CLS、INP)、真实用户监控和流量数据。它内置于 Vercel 仪表板中,无需零配置。对于目录网站,我们密切关注列表页面上的 LCP -- 如果它超过 2.5 秒,我们知道我们的 ISR 配置或图像优化出了问题。

PostHog 处理产品分析:哪些搜索查询返回零结果(所以我们知道要填补什么内容空白),哪些列表类别获得最多浏览量,用户在预订或注册流程中的什么地方放弃。PostHog 的免费层覆盖每月最多 100 万个事件,这处理大多数早期市场。

这种组合为你提供两个"网站快吗?"和"用户找到他们需要的东西吗?"-- 两个非常不同但同样重要的问题。

完整堆栈比较表

我们的选择 替代方案 我们为什么选择我们的
前端 Next.js 15 Astro、Remix ISR 用于 100K+ 页面
数据库 Supabase PostgreSQL PlanetScale、Neon pgvector + PostGIS 在一个数据库中
身份验证 Supabase Auth Clerk、Auth.js 原生 RLS 集成
支付 Stripe Connect Paddle、LemonSqueezy 市场分割、多货币
搜索 三重模式(在数据库中) 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 而不是在数据库中搜索吗? 对于大多数目录网站,没有。将数据在你的主数据库和单独的搜索服务之间保持同步创建 bug,增加延迟并增加成本。Algolia 根据搜索操作收费 -- 在规模上,这变得快速昂贵(他们的定价从 Build 计划的每 1,000 个搜索请求 $1 开始)。PostgreSQL 的内置搜索功能,特别是与 pgvector 结合使用,很好地处理目录规模搜索。例外:如果你需要在数百万条记录中进行 10ms 以下响应时间的拼写容差和分面过滤,Algolia 值得复杂性。

构建目录与市场之间的区别是什么? 目录列出东西并让用户找到它们。市场添加交易 -- 支付、预订、佣金以及通常提供商和消费者之间的双向交互。技术堆栈大多相同,但市场添加 Stripe Connect(或同等的)、更复杂的身份验证角色和事务性电子邮件流。数据库模式也变得更加复杂,具有订单、发票和支付跟踪表。

我能在现有目录网站中添加 AI 功能吗? 绝对地。我们堆栈中的 AI 层是附加的,不是基础的。你可以通过为你现有的列表生成嵌入(一个一次性批处理作业)、将它们存储在 pgvector 列中并添加语义搜索端点来添加语义搜索,同时现有文本搜索。内容丰富和聊天机器人功能可以作为独立 API 路由添加。最难的部分通常是为大型现有数据集生成嵌入 -- 预算几个小时的处理时间和 $50-100 的 API 成本用于 100K 列表。