Skip to content

Lesson 28:部署上线 — 让世界看到你的作品 ​

🧩 本节信息卡(学习前先看) ​

  • 阶段定位:Phase 3(实战篇)
  • 推荐时长:120~180 分钟(首次学习)
  • 先修要求:完成 L17–27,并准备部署平台、数据库和第三方服务测试账号
  • 学习产出:部署可访问的测试商店,核验数据库迁移、认证回调、支付回调和错误采集
✅ 本节完成标准(自检清单)
  • [ ] 我可以独立复现文中的核心代码片段
  • [ ] 我能解释“为什么这样实现”,而不只是“照着写”
  • [ ] 我记录了至少 1 个踩坑点和修复方法

🧭 本节统一学习流程 ​

  1. 学习目标:先明确本节要解决的业务问题与核心 API。
  2. 主线实战:跟随课程实现可运行功能(先跑通,再优化)。
  3. 原理深挖:理解为什么这样设计,以及常见误区。
  4. 练习挑战:完成 L1/L2(阶段收官课建议加 L3)巩固迁移能力。
  5. 本节小结:回顾“做了什么 / 学到了什么 / 下节前检查项”。

建议节奏:阅读 20% + 编码 60% + 复盘 20%。

🎯 本节目标:将全栈电商应用部署到 Vercel,配置生产级数据库、环境变量分层、错误监控和域名,真正上线可访问。

📦 本节产出:一个公网可访问的课程商店,先使用 Stripe 测试模式验收;正式收款前还需完成 L21 的账户安全能力与支付上线检查。

一、部署架构全景 ​


二、切换到生产级数据库 ​

SQLite 可以用于合适的生产场景,支持并发读,写入需要协调。这里更换数据库的原因是:Vercel Functions 的本地文件系统不能作为多个实例共享、持久保存订单的数据库。不要把 dev.db 上传后当作生产库。Vercel 的 SQLite 说明。

本课以托管 PostgreSQL 服务 Neon 为例。创建独立的开发、预览、生产数据库,按控制台给出的连接方式配置;额度和计费以服务商当前方案为准。

2.1 新课程数据库:重建 PostgreSQL 迁移历史 ​

下述流程仅用于全新的空 PostgreSQL 开发库。先备份 SQLite 文件和原 schema/迁移历史,不要向已有生产库执行 migrate dev 或 reset。修改同一个 datasource 的 provider 并保留所有 L19–24 模型:

prisma
// prisma/schema.prisma
datasource db {
  provider = "postgresql"
  url      = env("DATABASE_URL") // 应用运行时连接,可使用服务商提供的池化地址
}

在不提交 Git 的 .env 中配置 DATABASE_URL(应用连接)与 DIRECT_URL(迁移直连)。这里保留 L19 的 prisma.config.ts、dotenv 和 seed 配置,只把 CLI 的连接改成直连:

ts
// prisma.config.ts:完整配置,仍使用 Prisma 6.19.x
import 'dotenv/config'
import { defineConfig, env } from 'prisma/config'

export default defineConfig({
  schema: 'prisma/schema.prisma',
  migrations: { path: 'prisma/migrations', seed: 'tsx prisma/seed.ts' },
  engine: 'classic',
  datasource: { url: env('DIRECT_URL') },
})

检查 .env.local 中是否存在同名旧配置,避免 Next.js 和 Prisma CLI 连接到不同数据库。确认目标是新建空开发库后,将旧历史移出当前 migrations 路径,再生成 PostgreSQL 的初始迁移:

bash
# 仅在第一次切换、且此归档目录尚不存在时执行;保留旧 SQL 作为历史。
mv prisma/migrations prisma/migrations-sqlite-archive
npx prisma migrate dev --name init-postgres --skip-seed
npx prisma generate
npx prisma db seed

审核新生成的 SQL、提交新历史与 migration_lock.toml。SQLite SQL 不能原样应用于 PostgreSQL;migrate dev 还需要可用的 shadow database 权限,如托管开发库无法创建 shadow database,按 Prisma 6 文档单独配置开发专用 shadow database。不要把生产库作为 shadow database。Prisma Migrate 限制。

2.2 已有数据与测试环境 ​

迁移历史只描述结构,不搬运 SQLite 中的用户、商品和订单。如果要保留数据,应先在隔离环境编写和演练导出/转换/导入过程,保留主键、外键、密码哈希、订单状态、金额(仍为整数分)及支付会话 ID,核对记录数和金额总和,再安排停写窗口切换。目标 PostgreSQL 已有表时还需单独评估基线,不能执行上面的空库流程。备份恢复和应用版本回滚也需一起演练。

PostgreSQL 的大小写搜索、约束和并发行为与 SQLite 不完全相同。切库后重新验证 L22 搜索与 L23 库存/事务重试。L26 的临时 SQLite runner 此时不能继续使用:迁移前的 SQLite 测试结果不等于 PostgreSQL 验收。为每次 CI 运行提供一个空 PostgreSQL 测试库,把运行时 DATABASE_URL、迁移 DIRECT_URL 与测试保护标识设置为该测试库,执行新迁移、e2e/seed.ts、Playwright;运行结束后只销毁这次的测试库。保留 L26 的不复用服务器与种子账号规则,不把测试库地址指向 Preview 或 Production。


三、环境变量分层管理 ​

在真实的团队开发中,你会有三套环境,每套环境使用不同的密钥和配置:

Next.js 按以下优先级查找变量,找到后停止:

  1. 进程环境 process.env
  2. .env.$NODE_ENV.local
  3. .env.local(NODE_ENV=test 时跳过)
  4. .env.$NODE_ENV
  5. .env

next dev 使用 development;生产构建和 next start 使用 production。.env.local 并非只在开发时读取。Prisma CLI 在本课由 dotenv/config 读取 .env,不会自动套用 Next.js 的整套顺序;平台部署和 CI 应显式注入连接配置。

所有实际 .env* 配置文件都忽略提交,只提交无秘密的 .env.example。NEXT_PUBLIC_ 变量会进入浏览器构建产物,其值在构建时固定;修改后要重新构建。数据库密码、Stripe Secret、AUTH_SECRET、SENTRY_AUTH_TOKEN 均为服务端秘密,不能加此前缀。Sentry DSN 是可公开的项目接收地址,不等于其上传授权 token。Next.js 15 环境变量。


四、部署到 Vercel ​

4.1 推送到 GitHub ​

先检查 git status 和 .gitignore,确认未暂存数据库文件、.env*、Playwright 登录状态文件或密钥。

bash
git add .
git commit -m "feat: complete e-commerce application"
git push origin main

4.2 连接 Vercel ​

  1. 访问 vercel.com → "Import Project"
  2. 选择你的 GitHub 仓库
  3. Vercel 自动检测为 Next.js 项目

4.3 配置环境变量 ​

在 Vercel → Settings → Environment Variables 中,分环境添加:

本课先部署测试商店,两个环境都使用 Stripe 测试模式,且数据库、认证秘密与 endpoint signing secret 分开。以下变量名与 L21 Auth.js v5([email protected],预发布版)和 L24 一致:

dotenv
DATABASE_URL=替换为本环境的PostgreSQL应用连接
DIRECT_URL=替换为同一数据库的迁移直连
AUTH_SECRET=替换为本环境的随机秘密
AUTH_GITHUB_ID=替换为本环境的GitHubOAuth应用ID
AUTH_GITHUB_SECRET=替换为该OAuth应用秘密
APP_URL=https://替换为本环境的稳定域名
STRIPE_SECRET_KEY=sk_test_替换为测试密钥
STRIPE_WEBHOOK_SECRET=whsec_替换为该环境endpoint签名密钥
CRON_SECRET=替换为支付对账任务的独立随机秘密
NEXT_PUBLIC_SENTRY_DSN=替换为SentryDSN
NEXT_PUBLIC_APP_ENV=preview
SENTRY_AUTH_TOKEN=替换为构建时上传source_map的token

Production 的 NEXT_PUBLIC_APP_ENV 设为 production。Vercel 上 Auth.js 可推断部署 URL;若需要显式配置稳定源站,使用 AUTH_URL,不要继续使用旧教程的 NEXTAUTH_URL/GITHUB_ID。随机 Preview URL 不适合直接复用固定 OAuth 回调和 Stripe endpoint;为集成测试设置稳定 Preview 域名并注册对应回调。L24 使用托管 Checkout,无需 Stripe publishable key。

正式收款是另一个发布步骤:核验账户、币种、回调、对账和账户安全后,再成套切换 live key 与 live endpoint signing secret。改变 Vercel 环境变量后重新部署才能让新部署生效。

4.4 数据库迁移 ​

next build 不负责数据库迁移。保持构建与迁移为两个明确步骤;在已有 scripts 中合并以下命令,保留测试与检查脚本:

json
"scripts": {
  "build": "prisma generate && next build",
  "db:deploy": "prisma migrate deploy"
}

在已注入本环境 DATABASE_URL 和 DIRECT_URL 的受控发布终端或 CI job 中,先检查目标,再执行 npm run db:deploy。生产使用已经审核并提交的 PostgreSQL 迁移,不能运行 migrate dev。迁移先在 Preview 演练,并采用兼容旧应用的结构变更,避免迁移成功但应用构建失败时旧版本无法运行。生产 seed 要单独审核和执行;不要每次部署都自动写入课程演示商品或测试账号。

若希望生产部署必须等待测试和迁移成功,需配置发布流水线与 Vercel 部署的依赖关系。只连接 Git 仓库并不会自动等待 L26 的 GitHub Actions 检查。不能通过 ignoreBuildErrors 或禁用授权/输入校验让部署通过。

4.5 点击 Deploy ​

在项目中设置 Node.js 24.x,提交 lockfile,并将 Install Command 设为 npm ci、Build Command 设为 npm run build。Vercel 随后会:

  1. 克隆代码
  2. 安装依赖 (npm ci)
  3. 执行 npm run build
  4. 按路由构建服务端运行产物;动态页面、Actions 和 Route Handlers 在服务端执行
  5. 将静态资源分发到全球 CDN

部署成功后,打开平台返回的地址,验证登录、浏览、建单与回调,而不是只看构建成功。


五、配置 Stripe Webhook ​

生产环境的 Stripe Webhook URL 需要更新:

  1. 登录 Stripe Dashboard
  2. 进入 Developers → Webhooks
  3. 添加 Endpoint:https://your-domain.vercel.app/api/webhook/stripe
  4. 与 L24 的处理器一致,订阅 checkout.session.completed、checkout.session.expired、checkout.session.async_payment_succeeded 和 checkout.session.async_payment_failed
  5. 获取新的 Webhook Secret,更新 Vercel 环境变量

endpoint 使用与已安装 Stripe 20.4.1 SDK 对齐的 API 版本;本地 CLI 的 signing secret 不能复用。部署后检查 Stripe 投递结果、验签、重复事件幂等处理及数据库订单状态。为 L24 的 /api/internal/reconcile-payments 配置定时 POST 调用,并发送 Authorization: Bearer <CRON_SECRET>,补偿漏回调和中断的初始化。

GitHub OAuth App 的回调地址设为 https://你的稳定域名/api/auth/callback/github。Preview 使用单独 OAuth 应用与稳定地址。


六、错误监控:Sentry 集成 ​

本课使用 @sentry/nextjs 10 系列与 Next.js 15.5。可以运行向导后检查生成结果,也可以按下面手动配置;两种方式不要重复初始化 SDK。

bash
npm install @sentry/nextjs@10

客户端入口使用 src/instrumentation-client.ts。不用旧的 sentry.client.config.ts:

ts
// src/instrumentation-client.ts
import * as Sentry from '@sentry/nextjs'
Sentry.init({
  dsn: process.env.NEXT_PUBLIC_SENTRY_DSN,
  tracesSampleRate: 0.1,
  environment: process.env.NEXT_PUBLIC_APP_ENV,
  sendDefaultPii: false,
})
export const onRouterTransitionStart = Sentry.captureRouterTransitionStart

本课程认证和数据库运行在 Node.js,因此在同一个 src/ 目录下创建服务端配置和注册入口。以后增加 Edge 路由时,还需提供并按运行时加载单独的 Edge 配置。

ts
// src/sentry.server.config.ts
import * as Sentry from '@sentry/nextjs'
Sentry.init({
  dsn: process.env.NEXT_PUBLIC_SENTRY_DSN,
  tracesSampleRate: 0.1,
  environment: process.env.NEXT_PUBLIC_APP_ENV,
  sendDefaultPii: false,
})
ts
// src/instrumentation.ts
import * as Sentry from '@sentry/nextjs'
export async function register() {
  if (process.env.NEXT_RUNTIME === 'nodejs') {
    await import('./sentry.server.config')
  }
}
export const onRequestError = Sentry.captureRequestError

在全局错误边界中报告捕获的错误。它替换根布局时需自行渲染 html/body:

tsx
// src/app/global-error.tsx
'use client'
import * as Sentry from '@sentry/nextjs'
import { useEffect } from 'react'

export default function GlobalError({ error, reset }: {
  error: Error & { digest?: string }; reset: () => void
}) {
  useEffect(() => { Sentry.captureException(error) }, [error])
  return <html lang="zh-CN"><body>
    <h1>页面暂时无法显示</h1>
    <button onClick={reset}>重试</button>
  </body></html>
}

合并到当前 next.config.ts,不要覆盖 L27 的图片配置或 Analyzer 包装。若已有 Analyzer,最外层再用 withSentryConfig 包住其结果:

ts
import { withSentryConfig } from '@sentry/nextjs'
// nextConfig 与 withBundleAnalyzer 沿用 L27。
export default withSentryConfig(withBundleAnalyzer(nextConfig), {
  org: '替换为组织slug',
  project: '替换为项目slug',
  authToken: process.env.SENTRY_AUTH_TOKEN,
  silent: !process.env.CI,
})

tracesSampleRate: 0.1 指性能追踪的采样,不表示只采集 10% 的错误。NODE_ENV 在 Preview/Production 都可能是 production,因此这里显式区分部署环境。Source map 上传 token 仅配置在构建环境;上线前检查上报字段,避免主动附加密码、支付信息或整个表单。

自动采集也有边界:主动返回一个 500 Response、吞掉异常或业务失败结果,不一定自动形成错误事件;需要在适当位置调用 captureException 并保留用户可读错误处理。邮件/Slack 通知必须在 Sentry 中另行连接并配置告警规则。先在 Preview 触发一个受控异常,核对事件环境、堆栈和告警,再移除测试入口。Sentry Next.js 官方配置。


七、自定义域名 ​

  1. 在 Vercel → Settings → Domains 添加你的域名(如 shop.example.com)
  2. 在域名注册商按 Vercel 当前为该域名展示的记录配置 DNS;根域与子域所需记录不同,不要硬编码通用 CNAME
  3. 等待 DNS 验证和 HTTPS 证书签发成功,再更新 APP_URL、OAuth 与 Stripe 回调配置。见 Vercel 域名配置

八、生产环境部署检查清单 ​

  • [ ] Node.js、Next.js 与依赖 lockfile 的版本符合课程基线,构建和类型检查通过
  • [ ] Development / Preview / Production 的数据库与秘密分开,.env* 和数据库文件未提交
  • [ ] PostgreSQL 迁移与必要的数据导入已演练,备份和恢复方案可执行
  • [ ] 发布流程在测试与迁移通过后才部署;应用回滚与数据库兼容性已验证
  • [ ] APP_URL 与 OAuth 回调对应稳定 HTTPS 源站
  • [ ] Stripe 使用当前环境的模式、API 版本和 endpoint signing secret,重复回调不会重复扣库存
  • [ ] L24 支付对账定时任务已配置并检查执行结果
  • [ ] Sentry 的浏览器、服务端、堆栈和告警已分别验收
  • [ ] 按 L27 记录性能基线;按 L26 原则在 PostgreSQL 隔离测试库验证关键流程
  • [ ] 正式开放注册/收款前,补齐 L21 尚未实现的账户安全能力,并完成对应验收

九、Phase 3 回顾与阶段收官 ​

到这里,课程覆盖了从 React 页面到服务端数据、认证、支付和部署的主流程。各项外部服务仍需以自己的环境验收结果为准。

你现在掌握的完整技术栈 ​

领域技术学习课时
UI 框架React 19 + TypeScriptL01~L06
样式Tailwind CSS v4 + shadcn/uiL07, L13
路由React Router v7 (SPA) / App Router(文件路由)L07, L17
客户端状态Zustand + persistL09, L23
服务端状态TanStack Query / RSCL11~L12, L18
表单React Hook Form + ZodL14
数据库Prisma 6 + SQLite / PostgreSQLL19, L28
认证Auth.js v5(beta.32,预发布)L21
支付Stripe Checkout + WebhookL24
测试Vitest + PlaywrightL25~L26
性能Core Web Vitals + Bundle 分析L27
部署Vercel + Sentry + CI/CDL28

下一步建议 ​

  1. 纵向深入:阅读 React 源码、学习 V8 引擎和编译原理
  2. 横向拓展:React Native (移动端)、Electron (桌面端)、tRPC (类型安全 API)
  3. 持续学习:关注 React Blog、Next.js Blog
  4. 实战检验:把这套技术栈应用到自己的开源项目中

完成部署验收后,可以继续 L29–30,理解渲染、调度与编译优化背后的机制。


📌 本节小结 ​

你做了什么你学到了什么
将数据库切换到 PostgreSQLPrisma 多数据库切换
部署全栈应用到 VercelGitHub → Vercel CI/CD 自动化部署
配置了分层的环境变量Development / Preview / Production 隔离
集成了 Sentry 错误监控线上错误自动捕获与告警
配置了自定义域名和 HTTPSDNS 配置与 SSL 自动签发
—完整的生产部署检查清单
—Phase 1~3 共 28 节课的完整知识图谱回顾 ✅

十、练习 ​

  1. 演练回滚:故意在 Preview 环境注入一个错误配置,验证你能在 10 分钟内回滚到上一版本。
  2. 环境变量审计:输出一份 Development/Preview/Production 变量对照表,标注“是否可公开”“是否必填”。
  3. 告警验证:手动触发一个受控异常(例如在仅限 Preview 的测试入口抛出一个明确的 Error;普通 404 不等于异常),确认 Sentry 能正确采集并通知。

项目驱动 · 边写边学 · React 19