Lesson 28:部署上线 — 让世界看到你的作品
🧩 本节信息卡(学习前先看)
- 阶段定位:Phase 3(实战篇)
- 推荐时长:120~180 分钟(首次学习)
- 先修要求:完成 L17–27,并准备部署平台、数据库和第三方服务测试账号
- 学习产出:部署可访问的测试商店,核验数据库迁移、认证回调、支付回调和错误采集
✅ 本节完成标准(自检清单)
- [ ] 我可以独立复现文中的核心代码片段
- [ ] 我能解释“为什么这样实现”,而不只是“照着写”
- [ ] 我记录了至少 1 个踩坑点和修复方法
🧭 本节统一学习流程
- 学习目标:先明确本节要解决的业务问题与核心 API。
- 主线实战:跟随课程实现可运行功能(先跑通,再优化)。
- 原理深挖:理解为什么这样设计,以及常见误区。
- 练习挑战:完成 L1/L2(阶段收官课建议加 L3)巩固迁移能力。
- 本节小结:回顾“做了什么 / 学到了什么 / 下节前检查项”。
建议节奏:阅读 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/schema.prisma
datasource db {
provider = "postgresql"
url = env("DATABASE_URL") // 应用运行时连接,可使用服务商提供的池化地址
}在不提交 Git 的 .env 中配置 DATABASE_URL(应用连接)与 DIRECT_URL(迁移直连)。这里保留 L19 的 prisma.config.ts、dotenv 和 seed 配置,只把 CLI 的连接改成直连:
// 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 的初始迁移:
# 仅在第一次切换、且此归档目录尚不存在时执行;保留旧 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 按以下优先级查找变量,找到后停止:
- 进程环境
process.env .env.$NODE_ENV.local.env.local(NODE_ENV=test时跳过).env.$NODE_ENV.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 登录状态文件或密钥。
git add .
git commit -m "feat: complete e-commerce application"
git push origin main4.2 连接 Vercel
- 访问 vercel.com → "Import Project"
- 选择你的 GitHub 仓库
- Vercel 自动检测为 Next.js 项目
4.3 配置环境变量
在 Vercel → Settings → Environment Variables 中,分环境添加:
本课先部署测试商店,两个环境都使用 Stripe 测试模式,且数据库、认证秘密与 endpoint signing secret 分开。以下变量名与 L21 Auth.js v5([email protected],预发布版)和 L24 一致:
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的tokenProduction 的 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 中合并以下命令,保留测试与检查脚本:
"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 随后会:
- 克隆代码
- 安装依赖 (
npm ci) - 执行
npm run build - 按路由构建服务端运行产物;动态页面、Actions 和 Route Handlers 在服务端执行
- 将静态资源分发到全球 CDN
部署成功后,打开平台返回的地址,验证登录、浏览、建单与回调,而不是只看构建成功。
五、配置 Stripe Webhook
生产环境的 Stripe Webhook URL 需要更新:
- 登录 Stripe Dashboard
- 进入 Developers → Webhooks
- 添加 Endpoint:
https://your-domain.vercel.app/api/webhook/stripe - 与 L24 的处理器一致,订阅
checkout.session.completed、checkout.session.expired、checkout.session.async_payment_succeeded和checkout.session.async_payment_failed - 获取新的 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。
npm install @sentry/nextjs@10客户端入口使用 src/instrumentation-client.ts。不用旧的 sentry.client.config.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 配置。
// 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,
})// 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:
// 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 包住其结果:
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 官方配置。
七、自定义域名
- 在 Vercel → Settings → Domains 添加你的域名(如
shop.example.com) - 在域名注册商按 Vercel 当前为该域名展示的记录配置 DNS;根域与子域所需记录不同,不要硬编码通用 CNAME
- 等待 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 + TypeScript | L01~L06 |
| 样式 | Tailwind CSS v4 + shadcn/ui | L07, L13 |
| 路由 | React Router v7 (SPA) / App Router(文件路由) | L07, L17 |
| 客户端状态 | Zustand + persist | L09, L23 |
| 服务端状态 | TanStack Query / RSC | L11~L12, L18 |
| 表单 | React Hook Form + Zod | L14 |
| 数据库 | Prisma 6 + SQLite / PostgreSQL | L19, L28 |
| 认证 | Auth.js v5(beta.32,预发布) | L21 |
| 支付 | Stripe Checkout + Webhook | L24 |
| 测试 | Vitest + Playwright | L25~L26 |
| 性能 | Core Web Vitals + Bundle 分析 | L27 |
| 部署 | Vercel + Sentry + CI/CD | L28 |
下一步建议
- 纵向深入:阅读 React 源码、学习 V8 引擎和编译原理
- 横向拓展:React Native (移动端)、Electron (桌面端)、tRPC (类型安全 API)
- 持续学习:关注 React Blog、Next.js Blog
- 实战检验:把这套技术栈应用到自己的开源项目中
完成部署验收后,可以继续 L29–30,理解渲染、调度与编译优化背后的机制。
📌 本节小结
| 你做了什么 | 你学到了什么 |
|---|---|
| 将数据库切换到 PostgreSQL | Prisma 多数据库切换 |
| 部署全栈应用到 Vercel | GitHub → Vercel CI/CD 自动化部署 |
| 配置了分层的环境变量 | Development / Preview / Production 隔离 |
| 集成了 Sentry 错误监控 | 线上错误自动捕获与告警 |
| 配置了自定义域名和 HTTPS | DNS 配置与 SSL 自动签发 |
| — | 完整的生产部署检查清单 |
| — | Phase 1~3 共 28 节课的完整知识图谱回顾 ✅ |
十、练习
- 演练回滚:故意在 Preview 环境注入一个错误配置,验证你能在 10 分钟内回滚到上一版本。
- 环境变量审计:输出一份
Development/Preview/Production变量对照表,标注“是否可公开”“是否必填”。 - 告警验证:手动触发一个受控异常(例如在仅限 Preview 的测试入口抛出一个明确的 Error;普通 404 不等于异常),确认 Sentry 能正确采集并通知。