我从 Vercel 迁移到开源的 Next.js:一年内离线了三个生产应用

我从 Next.js 版本 9 开始在生产环境中运行它。在大部分时间里,Vercel 是显而易见的选择——部署、忘记、继续前进。但在 2024 年左右的某个时刻,发票看起来像汽车贷款。当你的营销网站托管费用超过了你的实际云基础设施成本时,说明有问题。这时我开始深入研究 OpenNext,在过去一年内将三个生产应用从 Vercel 迁移出去后,我有了一些看法。

这不是一篇"Vercel 不好"的文章。Vercel 确实很好。但它不再是唯一的选择,对许多团队来说,它也不是正确的选择。让我带你了解我所学到的关于使用 OpenNext 自托管 Next.js 的一切——好的、不好的和令人惊讶的经济实惠。

目录

OpenNext: Self-Host Next.js on AWS, Cloudflare, or VPS Without Vercel

什么是 OpenNext 及其存在的原因

Next.js 被设计成在 Vercel 上运行。这不是阴谋论——这是架构。ISR(增量静态再生成)、中间件、图像优化和服务器操作等功能都内置了 Vercel 特定的实现。当你尝试在随机服务器上运行 next start 时,你只能获得 Next.js 所能做的事情的子集。

OpenNext 是一个开源适配器,它接收你的 Next.js 构建输出并将其转换为可在其他平台上运行的部署包。它最初作为一个 SST 社区项目,专注于 AWS Lambda,但在 v3 版本(2025 年的当前主要版本),它支持多个部署目标,包括 Cloudflare Workers、传统 Node.js 服务器等。

OpenNext 实际处理的内容:

  • ISR 和重新验证 — Vercel 使用其内部基础设施实现的基于标签的重新验证系统?OpenNext 使用 AWS 上的 DynamoDB + SQS,或 Cloudflare 上的 KV 存储来重新创建它。
  • 图像优化 — Next.js 的 <Image> 组件依赖优化 API。OpenNext 打包了一个基于 Sharp 的优化程序或路由到特定于平台的解决方案。
  • 中间件 — 在 Vercel 上的边缘运行。OpenNext 将其映射到 CloudFront Functions、Cloudflare Workers 或在 VPS 上在进程中运行。
  • 服务器操作 — 完全支持,通过适当的服务器函数路由。
  • 流式传输和部分预渲染 — OpenNext v3.x 中的支持已显著成熟。

OpenNext 不是什么

它不是一个托管平台。它不是 CDN。它是一个构建适配器——Next.js 的输出和你的基础设施之间的转换层。你仍然需要在某个地方实际运行它。

2025-2026 年的自托管生态

自从我第一次开始研究这个问题以来,生态系统已经爆炸。以下是目前的状况:

平台 OpenNext 支持 成熟度 最适合
AWS(通过 SST) 一流 生产就绪 已经在 AWS 上的团队
Cloudflare Workers 官方适配器 稳定(某些边界情况) 以边缘为中心的应用,成本优化
Docker/VPS 社区 + 官方 稳定 简单部署,现有基础设施
Kubernetes 社区 Helm 图表 成熟中 企业,现有 K8s 集群
Netlify 内置(自有适配器) 生产就绪 承诺使用 Netlify 的团队
Google Cloud Run 社区 实验阶段 GCP 商店

我个人经过战斗测试并可以保证的两条路径是通过 SST 的 AWS 和 VPS 上的 Docker。Cloudflare 是令人兴奋的新来者,每月都在改进。

部署目标:使用 SST 的 AWS

这是黄金路径。SST(无服务器栈)内置了由 OpenNext 提供支持的 Next.js 支持,这是大部分工程工作的去向。

架构概览

当你通过 SST 在 AWS 上部署 Next.js 时,以下是创建的内容:

  • CloudFront 分配 — 你的 CDN,处理静态资产和路由
  • Lambda 函数 — 服务器端渲染、API 路由、服务器操作
  • S3 bucket — 静态资产、预渲染页面、ISR 缓存
  • DynamoDB 表 — ISR 标签映射用于重新验证
  • SQS 队列 — 异步重新验证处理
  • CloudFront Function 或 Lambda@Edge — 中间件执行

听起来很复杂。确实如此。但 SST 将所有内容抽象为约 20 行配置。

SST 配置

这是我生产项目中的真实 sst.config.ts

/// <reference path="./.sst/platform/config.d.ts" />

export default $config({
  app(input) {
    return {
      name: "my-nextjs-app",
      removal: input.stage === "production" ? "retain" : "remove",
      home: "aws",
      providers: {
        aws: {
          region: "us-east-1",
        },
      },
    };
  },
  async run() {
    const site = new sst.aws.Nextjs("Site", {
      domain: {
        name: "myapp.com",
        dns: sst.aws.dns(),
      },
      warm: 5, // keep 5 Lambda instances warm
      memory: "1024 MB",
      environment: {
        DATABASE_URL: process.env.DATABASE_URL!,
        NEXT_PUBLIC_API_URL: "https://api.myapp.com",
      },
    });

    return {
      url: site.url,
    };
  },
});

然后部署:

npx sst deploy --stage production

第一次部署需要 8-12 分钟(CloudFront 分配传播)。后续部署需要 2-4 分钟。

Lambda 注意事项

基于 Lambda 的托管最大的陷阱是冷启动。Next.js 服务器函数不是很小——根据你的依赖项,你会看到 20-80MB 的包。冷启动范围从 800ms 到 3 秒。

我使用的缓解方法:

  1. 预配置并发 — SST 的 warm 参数保持实例热化。在每 GB 秒 $0.0000041667 的价格下,保持 5 个 1GB 函数的实例热化成本约为 $15/月。
  2. 较小的包 — 审计你的服务器端依赖项。我发现一个项目服务器端导入 lodash,而我们只需要 lodash/get。包从 68MB 下降到 31MB。
  3. 区域部署 — 除非你绝对需要,否则不要将 Lambda@Edge 用于 SSR。单区域 Lambda 配合 CloudFront 缓存对 95% 的应用都很好。

OpenNext: Self-Host Next.js on AWS, Cloudflare, or VPS Without Vercel - architecture

部署目标:Cloudflare Workers

Cloudflare 在做出重大举措。他们的 Workers 运行时现在支持足够的 Node.js API,使得 Next.js 可以在那里实际运行,而 OpenNext Cloudflare 适配器已变得异常稳定。

使用 OpenNext Cloudflare 设置

npm install @opennext/cloudflare

添加到你的 wrangler.toml

name = "my-nextjs-app"
main = ".open-next/worker.js"
compatibility_date = "2025-01-01"
compatibility_flags = ["nodejs_compat_v2"]

[assets]
directory = ".open-next/assets"
binding = "ASSETS"

[[kv_namespaces]]
binding = "NEXT_CACHE_KV"
id = "your-kv-namespace-id"

构建并部署:

npx @opennext/cloudflare build
npx wrangler deploy

Cloudflare 权衡

优点:

  • 无冷启动 — Workers 在全球 5ms 内启动
  • 全球边缘默认 — 你的 SSR 在 300+ 个位置运行
  • 离谱定价 — 付费计划上 1000 万个请求每月 $5

缺点:

  • 内存限制 — 免费 128MB,付费 256MB。大型 Next.js 应用可能会达到此限制。
  • CPU 时间限制 — 付费计划 30 秒。沉重的 SSR 页面可能是个问题。
  • Node.js 兼容性差距 — 大多数事物都有效,但如果你直接使用本地 Node 模块(如 sharp),你需要解决方法。Cloudflare Images 可以处理优化。
  • 某些 Next.js 功能不受支持 — 到 2025 年初,部分预渲染支持在 Cloudflare 上仍处于实验阶段。

对于内容丰富的网站和营销页面,Cloudflare Workers 非常引人注目。对于具有沉重服务器端逻辑的复杂 Web 应用程序,我仍然会倾向于 AWS 或 Docker。

部署目标:使用 Docker 的 VPS

有时你只想要一台服务器。没有 Lambda 函数,没有边缘运行时,没有 47 项服务的架构图。一个运行你代码的盒子。我尊重这一点。

Dockerfile

这是我使用的生产 Dockerfile。它是多阶段的,经过优化,实际有效:

# Stage 1: Dependencies
FROM node:20-alpine AS deps
RUN apk add --no-cache libc6-compat
WORKDIR /app
COPY package.json pnpm-lock.yaml ./
RUN corepack enable pnpm && pnpm install --frozen-lockfile

# Stage 2: Build
FROM node:20-alpine AS builder
WORKDIR /app
COPY --from=deps /app/node_modules ./node_modules
COPY . .

ENV NEXT_TELEMETRY_DISABLED=1
ENV NODE_ENV=production

RUN corepack enable pnpm && pnpm build

# Stage 3: Production
FROM node:20-alpine AS runner
WORKDIR /app

ENV NODE_ENV=production
ENV NEXT_TELEMETRY_DISABLED=1

RUN addgroup --system --gid 1001 nodejs
RUN adduser --system --uid 1001 nextjs

COPY --from=builder /app/public ./public
COPY --from=builder --chown=nextjs:nodejs /app/.next/standalone ./
COPY --from=builder --chown=nextjs:nodejs /app/.next/static ./.next/static

USER nextjs

EXPOSE 3000
ENV PORT=3000
ENV HOSTNAME="0.0.0.0"

CMD ["node", "server.js"]

关键:你需要在你的 next.config.js 中设置 output: 'standalone'

/** @type {import('next').NextConfig} */
const nextConfig = {
  output: 'standalone',
};

module.exports = nextConfig;

VPS 建议

我已在几个提供商上运行了此设置:

提供商 规格 月成本 说明
Hetzner CAX21 4 vCPU ARM,8GB RAM €7.49(约 $8) 最佳价值,欧盟数据中心
DigitalOcean Droplet 2 vCPU,4GB RAM $24 良好的美国覆盖
Fly.io(机器) 2 vCPU,4GB RAM ~$30 自动扩展,全球区域
Railway 基于使用情况 $5-50 最容易设置,Vercel 风格的开发体验
AWS EC2 t4g.medium 2 vCPU,4GB RAM ~$25 已经在 AWS 上

对于简单的 Docker 部署,Hetzner 的价值极其划算。我在€7.49 的 Hetzner ARM 实例上运行一个 Next.js 应用,每月服务 200 万+ 页面浏览量,放在 Cloudflare 免费 CDN 层后面。服务器几乎不费吹灰之力。

使用 Docker/VPS 你失去了什么

让我们诚实地谈论与 Vercel 或 SST 设置相比,VPS 上的 next start 没有提供的内容:

  • ISR 重新验证很基础 — 仅文件系统缓存。跨多个实例没有分布式缓存。如果你运行单个服务器,这是可以的。多服务器?你需要 Redis 或共享缓存层。
  • 没有边缘中间件 — 中间件在进程中运行,这对大多数用例来说完全没问题。
  • 图像优化 — 通过 Sharp 有效,但你从单个源提供优化的图像。在前面放一个 Cloudflare 或 CDN。
  • 没有原子部署 — 你需要自己处理零停机部署(Docker Compose 带健康检查,或反向代理如 Caddy/Traefik)。

对于大多数应用,尤其是我们通过 headless CMS 开发 工作进行的无头 CMS 构建,单个 VPS 配合 CDN 完全足够。

成本比较:Vercel vs 自托管

让我们谈论钱。这基于一个 Next.js 应用的真实账单数据,该应用每月进行约 500 万个请求,包括 ISR、图像优化和中等服务器端渲染。

成本因素 Vercel Pro Vercel Enterprise AWS/SST Cloudflare Hetzner VPS
基础平台 $20/用户/月 自定义(~$3k+/月) $0 $5/月 €7.49/月
计算/请求 $150-400/月 包含 $40-80/月 $0-15/月 包含
带宽(100GB) 包含 包含 $8.50(CloudFront) 包含 包含
图像优化 $50-200/月 包含 $5-15/月(Lambda) $5/月(CF Images) 包含(Sharp)
ISR/缓存 包含 包含 $2-5/月(DynamoDB) $0-5/月(KV) $0
估计总计 $300-700/月 $3,000+/月 $55-110/月 $10-25/月 $8-15/月

那些 Vercel 的数字不是假设的。我见过发票。Pro 层的按座位定价、函数执行超额费用和带宽费用对于 5 人以上的团队来说加起来很快。

AWS/SST 的数字假设流量适中且具有预配置并发。Cloudflare 的定价确实很疯狂——除非你在做一些特殊的事情,否则很难在那里花真钱。

何时离开 Vercel

不要只是为了能够离开而离开。为了你应该离开而离开。这是我的框架:

继续使用 Vercel 如果:

  • 你的团队很小(1-3 个开发人员),开发人员时间是你最昂贵的资源
  • 你在 Vercel 上每月支出不足 $100
  • 你没有人喜欢基础设施工作
  • 你迭代很快,需要为每个 PR 进行即时预览
  • 你正在使用 Vercel 特定的功能,如 Analytics、Speed Insights 或 Vercel AI SDK 集成

离开 Vercel 如果:

  • 月度账单超过 $500 且在增长
  • 你需要特定区域的基础设施以获得合规性(GDPR、数据驻留)
  • 你已经运行重要的 AWS/GCP/Cloudflare 基础设施
  • 无服务器上的冷启动对你的用例是不可接受的
  • 你需要不符合 Vercel 模型的自定义缓存策略
  • 你已经达到了 Vercel 的函数大小限制或执行时间限制

认真考虑离开如果:

  • 你在 Vercel Enterprise 定价上,合同续约刚刚到来
  • 你的应用大多是静态/ISR,但你支付的是动态 SSR 价格
  • 你想在同一基础设施中与后端一起运行你的前端

迁移剧本

我已经做过三次了。这是我遵循的流程,通过痛苦的经验进行了改进。

第 1 步:审计你的 Next.js 功能

在你改动任何东西之前,列出你实际使用的 Next.js 功能:

# 查找中间件
find . -name "middleware.ts" -o -name "middleware.js"

# 查找 API 路由
find ./app -name "route.ts" -o -name "route.js" | head -20

# 检查 ISR
grep -r "revalidate" ./app --include="*.ts" --include="*.tsx" | head -20

# 检查服务器操作
grep -r "use server" ./app --include="*.ts" --include="*.tsx" | head -20

# 检查 next.config 中的特殊功能
cat next.config.js

第 2 步:选择你的目标

基于审计:

  • 繁重的 ISR + 中间件 + 图像优化 → AWS/SST
  • 简单的 SSR + 内容网站 → Cloudflare 或 VPS
  • 已经有 Docker/K8s 基础设施 → VPS/Docker
  • 需要在周五完成 → Docker on Railway 或 Fly.io

如果你使用 Next.jsAstro 构建,目标平台的选择会显著影响你的架构决策。

第 3 步:设置 CI/CD

Vercel 的 CI/CD 真的很好。你会想念它。用 GitHub Actions 复制它:

# .github/workflows/deploy.yml
name: Deploy
on:
  push:
    branches: [main]

jobs:
  deploy:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v4

      - uses: pnpm/action-setup@v4
        with:
          version: 9

      - uses: actions/setup-node@v4
        with:
          node-version: 20
          cache: 'pnpm'

      - run: pnpm install --frozen-lockfile
      - run: pnpm build
      - run: pnpm test

      # 对于 SST:
      - run: npx sst deploy --stage production
        env:
          AWS_ACCESS_KEY_ID: ${{ secrets.AWS_ACCESS_KEY_ID }}
          AWS_SECRET_ACCESS_KEY: ${{ secrets.AWS_SECRET_ACCESS_KEY }}

      # 对于 Docker/VPS(替代):
      # - run: docker build -t myapp .
      # - run: docker push registry.example.com/myapp:latest
      # - run: ssh deploy@server 'cd /app && docker compose pull && docker compose up -d'

第 4 步:预览部署

这是人们最想从 Vercel 中得到的一件事。对于 SST,使用阶段:

# 在你的 PR CI 工作流中
npx sst deploy --stage pr-${{ github.event.pull_request.number }}

对于 Docker,诸如 Coolify(自托管)或 Railway 之类的工具很好地处理预览部署。

第 5 步:DNS 迁移

实际的迁移时刻。我总是建议:

  1. 与 Vercel 一起在新基础设施中部署
  2. 使用临时域进行彻底测试
  3. 在迁移前一天将 DNS TTL 降低到 60 秒
  4. 在低流量时段进行 DNS 切换
  5. 保持 Vercel 部署运行 48 小时作为备用
  6. 密切监控错误率、TTFB 和核心 Web 指标

第 6 步:拆除 Vercel

一旦你放心了(至少给它一周),取消 Vercel 订阅并删除项目。不要留下僵尸项目堆积费用。

常见陷阱及其避免方法

环境变量消失。 Next.js 有 NEXT_PUBLIC_ 前缀的变量(在构建时捆绑)和仅限服务器的变量(在运行时可用)。在 Vercel 上,这种区别有点模糊。在自托管上,它很严格。确保所有 NEXT_PUBLIC_ 变量在 CI 中的构建时间可用。

ISR 缓存不持久。 在 Docker 上,.next/cache 目录需要在持久卷上。否则,每个容器重启都会丢失你的缓存页面:

# docker-compose.yml
services:
  web:
    build: .
    ports:
      - "3000:3000"
    volumes:
      - next-cache:/app/.next/cache

volumes:
  next-cache:

Sharp 安装失败。 sharp 图像优化库需要特定于平台的二进制文件。在 Docker 中,确保你在与运行时相同的架构内安装依赖项。上面的 Dockerfile 通过使用具有相同基础映像的多阶段构建来处理这个问题。

中间件行为差异。 Vercel 在其边缘网络上运行中间件。在 AWS/SST 上,它作为 CloudFront Function 运行(限制为 10ms 执行,2MB 大小)。复杂的中间件可能需要移动到服务器函数。由于这些限制,我必须重构身份验证中间件。

缺失的标头和重写。 如果你依赖 vercel.json 处理标头、重定向或重写,你需要将这些移到 next.config.js 或你的 CDN/反向代理配置。

如果这一切感到压倒性,那正是我们在 Social Animal 处理的基础设施工作类型。检查我们的 定价联系我们 — 我们已经进行了足够多的迁移,拥有一个精细的流程。

常见问题

OpenNext 在 2025 年是否生产就绪? 是的。OpenNext v3.x 正在为数千家公司运行生产工作负载。SST/AWS 路径是最经过战斗测试的,Cloudflare 支持紧随其后。我不会称 Google Cloud 或裸 Kubernetes 支持为成熟,但 AWS 和 Cloudflare 是稳定的。

OpenNext 是否支持 Next.js App Router 和 Server Components? 完全支持。App Router、Server Components、Server Actions、流式传输和 Suspense 都有效。OpenNext 团队密切跟踪 Next.js 发布,尽管在主要 Next.js 版本之后通常有 1-3 周的滞后,然后 OpenNext 才能跟上。

通过离开 Vercel 我实际上能节省多少? 这取决于你的使用模式。对于运行中等流量应用的 5 人团队,我见过团队从 Vercel Pro 上的 $600-800/月转变为 AWS/SST 上的 $30-80/月或 VPS 上的不足 $20/月。节省是真实的,但还有额外的维护负担。

我可以在没有 Vercel 的情况下使用 ISR(增量静态再生成)吗? 绝对可以。在 AWS/SST 上,ISR 使用 DynamoDB 作为标签缓存,SQS 用于异步重新验证——它是完全功能的,包括通过 revalidateTag()revalidatePath() 的按需重新验证。在 VPS 上,ISR 与文件系统缓存一起工作,这对单服务器部署很好。

关于 Vercel 的预览部署?我能复制这些吗? 你可以获得 80% 的体验。SST 支持基于阶段的部署,所以每个 PR 都可以获得自己的栈。Coolify 和类似工具为基于 Docker 的设置提供预览部署。你不会轻易复制的是 Vercel 的视觉评论系统和紧密的 GitHub 集成用于部署状态。大多数团队发现权衡是可以接受的。

对于无头 CMS 网站,我应该使用 OpenNext with Cloudflare 还是 AWS? 对于内容丰富的无头 CMS 网站(Sanity、Contentful、Storyblok),Cloudflare Workers 是一个很好的选择。这些网站往往是 ISR 密集型的,具有相对轻的服务器端逻辑——非常适合 Cloudflare 的定价模型。我只会在 Cloudflare 还不支持的功能需要或如果你已经深入 AWS 生态系统时才使用 AWS。

自托管 Next.js 比自托管 Astro 或 Remix 更难吗? 说实话?是的。Next.js 有任何框架最复杂的构建输出,因为有 ISR、中间件、图像优化和部分预渲染等功能。Astro 和 Remix 有简单得多的部署故事。如果你开始一个新项目且自托管是优先事项,考虑 Astro — 托管它极其简单。但如果你已经在 Next.js 上,OpenNext 使迁移实用。

如果 OpenNext 停止维护会怎样? OpenNext 由 SST 支持,拥有活跃的社区和主要赞助商。尽管如此,这对任何开源依赖都是合法的关注。缓解是 Docker/独立方法(next start)可以完全不使用 OpenNext — 你只是失去了一些更高级的功能,如 ISR 标签重新验证和边缘中间件。这是优雅的退化,而不是悬崖。