16 · 部署与上线交付
为课程终态补上 `api/health` 和上线验收基线,把“本地能跑”收口成“可部署、可排查、可交付”。
- 源码目录:phase-07-deployment/lesson-16-deployment-and-delivery
- 建议先读:
app/api/health/route.ts、app/database/supabase.ts、app/components/DeploymentStatusCard.tsx
一、学习目标
本课所在阶段:第七阶段 · 交付上线。
学完这一课后,你应该能够:
- 解释为什么“本地能跑”和“可上线交付”是两种不同的能力
- 看懂
app/api/health/route.ts只检查环境变量存在性,而不是替你完成完整运维诊断 - 明确这节课真正关心的四个关键环境变量分别服务于哪些链路
- 说清
app/database/supabase.ts、app/contexts/AuthContext.tsx和app/api/health/route.ts之间的关系 - 知道
app/agent/chatbot.ts和app/services/chat.service.ts与第 15 课保持一致,部署课没有继续改主聊天逻辑 - 理解
DeploymentStatusCard.tsx当前是一个已准备好的展示组件,但主页入口还没有直接消费/api/health
二、问题背景
到了第 15 课,课程项目已经拥有比较完整的产品能力:
- 登录和会话边界
- Supabase 持久化
- 工具调用和图片生成
- Canvas 工作区、保存和分享
但这些能力几乎都默认了一个前提:你的本地环境已经配好了。
一旦把项目放进部署语境,问题就完全变样了:
- 现在到底缺的是哪一个环境变量?
- 是模型 key 没配,还是 Supabase 地址根本没进环境?
- 登录失败、聊天失败、图片失败,到底是业务代码问题,还是部署配置问题?
如果没有最后这一课,你很容易把“部署配置没齐”误判成“业务逻辑写坏了”,排查顺序也会越来越乱。
所以第 16 课的重点不是再做一个新功能,而是给整套课程加上一个更像交付版本的收尾:
- 提供最小可用的健康检查入口
- 明确当前项目依赖的关键环境变量
- 给部署后的验收顺序建立一个正确起点
三、核心概念
1. 交付课不再新增产品协议,而是补“排查入口”
和前两课相比,这一课最重要的变化不是 Canvas、工具或 Agent 再升级,而是开始回答:
- 现在这套部署环境有没有把必需配置填全?
- 如果线上有问题,我应该先从哪里查?
本课新增的 app/api/health/route.ts 就是为了这个目标存在。它让“关键环境变量是否存在”这件事,第一次有了一个可直接访问的服务端入口。
2. /api/health 是部署就绪性检查,不是全链路诊断器
当前实现只做一件事:检查四个关键环境变量有没有值。
它不会帮你确认:
- key 的值是不是填对了
- Supabase 是否真的能连通
- Google 或 OpenAI 的配额、权限是否正常
- RLS、Storage、OAuth 回调等更细的线上问题
所以这条接口回答的问题很明确:
“至少最关键的配置项有没有缺失?”
3. 服务端环境变量是部署真相来源
第 16 课最值得你建立的工程直觉是:
- 运行时配置最终要以服务端为准
- 前端页面看到的失败现象,很多只是后果,不是根因
比如当前代码里:
app/database/supabase.ts会在缺少NEXT_PUBLIC_SUPABASE_URL或NEXT_PUBLIC_SUPABASE_PUBLISHABLE_DEFAULT_KEY时直接抛错app/contexts/AuthContext.tsx里 OAuth 登录也依赖同样的两项环境变量GOOGLE_API_KEY和OPENAI_API_KEY则分别影响图片生成和主聊天模型
所以把这些项集中放进 api/health,本质上是在给部署问题建立一个服务端统一入口。
4. 这一课刻意不改聊天主链路
对比第 15 课可以看到:
app/agent/chatbot.ts没有差异app/services/chat.service.ts没有差异
这件事很重要,因为它说明:
- 部署课不是继续调 Agent
- 也不是继续调 Canvas 协议
- 它解决的是“如何让已经完成的能力更可交付”
5. 当前代码里,部署状态卡已经准备好,但还没接上主页
这一点必须特别说清楚。
第 16 课的代码里已经新增了:
app/components/DeploymentStatusCard.tsx- 对应的
.deployment-*样式
但当前 app/page.tsx 并没有主动:
fetch('/api/health')- import 并渲染
DeploymentStatusCard
所以从真实代码角度看,本课已经把“健康检查接口”和“可复用展示组件”都准备好了,但主页消费层还没有接上。这种边界也很值得你注意,因为它直接影响你该把哪部分当成已上线主链路,哪部分当成下一步扩展位。
6. 相对第 15 课的真实增量
| 维度 | 第 15 课 | 第 16 课 |
|---|---|---|
| 核心目标 | 升级 Canvas 工作区 | 建立部署检查和交付验收起点 |
| 新增 API | /api/artifacts | /api/health |
| 页面语义 | Custom Rendering | Deploy & Ship |
| 主页主链路 | 聊天 + Canvas 工作区 | 聊天 + Canvas 工作区保持不变 |
| 部署辅助 UI | 没有 | 新增 DeploymentStatusCard.tsx,但未接进主页 |
四、关键文件
这是本课真正新增的部署检查入口。它返回 ok、checks 和 timestamp,是服务端部署状态的最小观察窗口。
这里直接消费 NEXT_PUBLIC_SUPABASE_URL 和 NEXT_PUBLIC_SUPABASE_PUBLISHABLE_DEFAULT_KEY。这也是为什么 /api/health 必须先检查这两项。
OAuth 登录链路同样依赖 Supabase 公开环境变量。部署后如果这两项不对,登录会最先出问题。
app/components/DeploymentStatusCard.tsx
这是部署检查的展示组件。它能把 health JSON 映射成“Ready / Check”和各配置项状态,但当前主页还没有直接挂载它。
这里最值得注意的反而是“没改什么”:聊天主界面和 Canvas 工作区都基本沿用上一课,只是页面语义切到了部署阶段,并把画布宽度缓存 key 改成了 lesson16-canvas-width。
这个文件把课程标题改成 Lesson 16 / Deploy & Ship,提醒你本课重心已经从功能扩展切换成上线交付。
这里保持标准的 dev / build / start / lint 脚本,没有引入额外部署脚本。换句话说,这套课程最终态就是标准 Next.js 项目部署路径。
五、整体流程
这张图最关键的信息是:当前代码里真正落地并可直接使用的是 /api/health,而 DeploymentStatusCard 更像一个已经准备好的消费层组件。
六、运行过程
1. /api/health 用最小逻辑暴露部署状态
app/api/health/route.ts 只有一个很克制的目标:判断这四项是否存在。
NEXT_PUBLIC_SUPABASE_URLNEXT_PUBLIC_SUPABASE_PUBLISHABLE_DEFAULT_KEYGOOGLE_API_KEYOPENAI_API_KEY
最后返回的对象很简单:
okcheckstimestamp
这让你在部署环境里有了一个可直接访问的“起始诊断面板”,哪怕它目前还是 JSON 形式。
2. 这四个环境变量之所以被检查,是因为它们正好覆盖当前主链路
当前代码里,这四项分别对应:
-
NEXT_PUBLIC_SUPABASE_URL决定前后端往哪个 Supabase 项目连。 -
NEXT_PUBLIC_SUPABASE_PUBLISHABLE_DEFAULT_KEY影响前端登录、会话恢复以及数据库客户端初始化。 -
GOOGLE_API_KEY影响图片生成工具。 -
OPENAI_API_KEY影响主聊天模型。
也就是说,health route 没有乱选字段,而是优先覆盖“课程终态最容易坏掉的几条主线”。
3. 缺少环境变量时,真实故障往往会先在别的模块爆出来
这是为什么第 16 课必须单独讲部署。
例如:
app/database/supabase.ts缺少公开 Supabase 环境变量时会直接抛错。AuthContext.tsx的 OAuth 登录创建客户端也依赖同一组变量。- 模型 key 不正确时,往往要等你真的去发聊天或生成图片才会暴露。
如果没有 /api/health,你看到的第一个现象可能是“登录失败”或“聊天失败”,但根因其实只是环境变量缺失。
4. 聊天和 Canvas 主链路在这一课刻意保持不变
第 16 课最值得你记住的一点不是“改了什么”,而是“没改什么”:
app/agent/chatbot.ts与上一课一致app/services/chat.service.ts与上一课一致app/page.tsx仍然保留聊天页和 Canvas 工作区
这意味着部署课不会再给你增加新的产品心智负担。它做的是收口,不是加戏。
5. DeploymentStatusCard.tsx 已准备好,但当前主页还没有挂载
这是本课和现有文档最容易对不上的地方。
当前代码里:
DeploymentStatusCard.tsx已经能把 health 结果映射成Ready / Check- 对应的样式也已经写进
globals.css - 但
app/page.tsx没有去请求/api/health - 也没有 import 这个组件
所以更准确的理解应该是:
- 服务端检查入口已经落地
- 展示组件也已经准备好
- 主页整合这一步仍然留作下一步可扩展工作
6. 这节课推荐的最小上线验收顺序
如果你要验证这节课到底学会了什么,建议按下面顺序走:
- 本地执行
pnpm build - 把四个关键环境变量填到部署平台
- 部署后先访问
api/health - 再验证登录
- 再验证普通聊天
- 再验证图片工具
- 最后验证 Canvas 工作区和 artifact 分享页
这个顺序的价值在于:先排部署基础问题,再看业务层问题,避免排查路径倒着走。
7. 当前健康检查仍然保留了明确边界
你必须清楚,这不是“一键运维系统”。
它现在不会主动帮你发现:
- 环境变量值虽然存在,但填错了
- OAuth 回调地址不对
- Supabase RLS 或 schema 没跑全
- 第三方 API 配额或权限不足
所以这节课真正给你的不是“所有问题都帮你搞定”,而是“先从最可能的部署缺项开始排查”。
七、关键代码解析
关键代码 1:app/api/health/route.ts 如何给部署建立最小排查入口
function hasEnv(name: string) {
return typeof process.env[name] === 'string' && process.env[name]!.length > 0;
}
export async function GET() {
const checks = {
supabaseUrl: hasEnv('NEXT_PUBLIC_SUPABASE_URL'),
supabaseAnonKey: hasEnv('NEXT_PUBLIC_SUPABASE_PUBLISHABLE_DEFAULT_KEY'),
googleApiKey: hasEnv('GOOGLE_API_KEY'),
openaiApiKey: hasEnv('OPENAI_API_KEY'),
};
const ready = Object.values(checks).every(Boolean);
return NextResponse.json({
ok: ready,
checks,
timestamp: new Date().toISOString(),
});
}代码解析:
- 这段实现非常克制,但它正好适合作为课程最后一课的“部署起点”。
- 它不和具体业务逻辑耦合,也不依赖用户先走登录或聊天流程,因此很适合用来先判断“部署基础条件是否齐全”。
- 如果没有这个入口,很多部署问题只能等业务链路报错后再倒查,排查成本会高很多。
关键代码 2:app/database/supabase.ts 如何体现环境变量是运行时前提
const supabaseUrl = process.env.NEXT_PUBLIC_SUPABASE_URL;
const supabaseAnonKey = process.env.NEXT_PUBLIC_SUPABASE_PUBLISHABLE_DEFAULT_KEY;
if (!supabaseUrl || !supabaseAnonKey) {
throw new Error(
'缺少 Supabase 环境变量,请在 .env 中设置 NEXT_PUBLIC_SUPABASE_URL 和 NEXT_PUBLIC_SUPABASE_PUBLISHABLE_DEFAULT_KEY。'
);
}代码解析:
- 这段代码说明 Supabase 相关环境变量不是“可选增强项”,而是应用启动前提。
- 它也解释了为什么 health route 要优先检查这两项,因为缺它们时,数据库和认证链路都可能直接坏掉。
- 把这个文件和
/api/health放在一起看,你会更容易理解第 16 课的核心:先识别部署前提,再谈业务功能。
关键代码 3:app/components/DeploymentStatusCard.tsx 为什么值得读,但不能误判为主链路
const entries: Array<[string, boolean]> = [
['Supabase URL', health.checks.supabaseUrl],
['Supabase Key', health.checks.supabaseAnonKey],
['Google API', health.checks.googleApiKey],
['OpenAI API', health.checks.openaiApiKey],
];
<div className={`deployment-badge ${health.ok ? 'is-ready' : 'is-warning'}`}>
{health.ok ? 'Ready' : 'Check'}
</div>代码解析:
- 这个组件已经把 health 结果翻译成了对人更友好的 UI,说明作者已经把“下一步如何展示部署状态”想清楚了。
- 但你也必须同时看到它目前还没有被
app/page.tsx使用,所以它更像“已准备好的展示层”,而不是当前线上主入口。 - 这正是文档更新时最需要忠于代码的地方:可以讲清它的作用,但不能把它写成已经接入完成的事实。
八、常见问题
1. 为什么主页里没有直接显示部署状态卡?
因为当前代码还没有在 app/page.tsx 里请求 /api/health 并渲染 DeploymentStatusCard。现在真正落地的是健康检查接口和配套展示组件,而不是已经整合好的主页部署面板。
2. 为什么 /api/health 要检查 NEXT_PUBLIC_* 变量?
因为这些公开变量虽然名字里带 NEXT_PUBLIC,但它们同样是当前运行时前提。Supabase 客户端初始化和 OAuth 登录都会依赖它们,缺了照样会影响上线可用性。
3. /api/health 返回 ok: true 就代表一定上线成功了吗?
不是。它只表示最关键的环境变量都存在。数据库权限、第三方 API 配额、OAuth 回调、Storage 配置这些问题,依然要靠后续验收流程继续确认。
九、练习题
- 在
app/page.tsx中真正拉取/api/health,把DeploymentStatusCard接进主页,并补上加载与失败状态。 - 给
/api/health增加一个可选的数据库连通性检查,但要明确区分“变量存在性”与“连接成功”这两种结果。 - 为这节课补一份你自己的部署清单,按“构建、环境变量、健康检查、登录、聊天、工具、Canvas”顺序列出验收步骤。
十、总结
第 16 课的价值不在于再做一个更复杂的功能,而在于给整套课程建立了一个正确的交付起点。
你应该把这一课记成三件事:
- 核心聊天和 Canvas 主链路已经稳定,本课不再继续改协议。
/api/health让部署检查第一次有了明确入口。DeploymentStatusCard说明展示层已经准备好,但当前真正落地的主链路仍然是“先请求 health 接口,再按顺序验收业务能力”。
登录以继续阅读
解锁完整文档、代码示例及更多高级功能。