第七阶段 · 交付上线

16 · 部署与上线交付

为课程终态补上 `api/health` 和上线验收基线,把“本地能跑”收口成“可部署、可排查、可交付”。

一、学习目标

本课所在阶段:第七阶段 · 交付上线

学完这一课后,你应该能够:

  • 解释为什么“本地能跑”和“可上线交付”是两种不同的能力
  • 看懂 app/api/health/route.ts 只检查环境变量存在性,而不是替你完成完整运维诊断
  • 明确这节课真正关心的四个关键环境变量分别服务于哪些链路
  • 说清 app/database/supabase.tsapp/contexts/AuthContext.tsxapp/api/health/route.ts 之间的关系
  • 知道 app/agent/chatbot.tsapp/services/chat.service.ts 与第 15 课保持一致,部署课没有继续改主聊天逻辑
  • 理解 DeploymentStatusCard.tsx 当前是一个已准备好的展示组件,但主页入口还没有直接消费 /api/health

二、问题背景

到了第 15 课,课程项目已经拥有比较完整的产品能力:

  1. 登录和会话边界
  2. Supabase 持久化
  3. 工具调用和图片生成
  4. Canvas 工作区、保存和分享

但这些能力几乎都默认了一个前提:你的本地环境已经配好了。

一旦把项目放进部署语境,问题就完全变样了:

  1. 现在到底缺的是哪一个环境变量?
  2. 是模型 key 没配,还是 Supabase 地址根本没进环境?
  3. 登录失败、聊天失败、图片失败,到底是业务代码问题,还是部署配置问题?

如果没有最后这一课,你很容易把“部署配置没齐”误判成“业务逻辑写坏了”,排查顺序也会越来越乱。

所以第 16 课的重点不是再做一个新功能,而是给整套课程加上一个更像交付版本的收尾:

  1. 提供最小可用的健康检查入口
  2. 明确当前项目依赖的关键环境变量
  3. 给部署后的验收顺序建立一个正确起点

三、核心概念

1. 交付课不再新增产品协议,而是补“排查入口”

和前两课相比,这一课最重要的变化不是 Canvas、工具或 Agent 再升级,而是开始回答:

  1. 现在这套部署环境有没有把必需配置填全?
  2. 如果线上有问题,我应该先从哪里查?

本课新增的 app/api/health/route.ts 就是为了这个目标存在。它让“关键环境变量是否存在”这件事,第一次有了一个可直接访问的服务端入口。

2. /api/health 是部署就绪性检查,不是全链路诊断器

当前实现只做一件事:检查四个关键环境变量有没有值。

它不会帮你确认:

  1. key 的值是不是填对了
  2. Supabase 是否真的能连通
  3. Google 或 OpenAI 的配额、权限是否正常
  4. RLS、Storage、OAuth 回调等更细的线上问题

所以这条接口回答的问题很明确:

“至少最关键的配置项有没有缺失?”

3. 服务端环境变量是部署真相来源

第 16 课最值得你建立的工程直觉是:

  1. 运行时配置最终要以服务端为准
  2. 前端页面看到的失败现象,很多只是后果,不是根因

比如当前代码里:

  1. app/database/supabase.ts 会在缺少 NEXT_PUBLIC_SUPABASE_URLNEXT_PUBLIC_SUPABASE_PUBLISHABLE_DEFAULT_KEY 时直接抛错
  2. app/contexts/AuthContext.tsx 里 OAuth 登录也依赖同样的两项环境变量
  3. GOOGLE_API_KEYOPENAI_API_KEY 则分别影响图片生成和主聊天模型

所以把这些项集中放进 api/health,本质上是在给部署问题建立一个服务端统一入口。

4. 这一课刻意不改聊天主链路

对比第 15 课可以看到:

  1. app/agent/chatbot.ts 没有差异
  2. app/services/chat.service.ts 没有差异

这件事很重要,因为它说明:

  1. 部署课不是继续调 Agent
  2. 也不是继续调 Canvas 协议
  3. 它解决的是“如何让已经完成的能力更可交付”

5. 当前代码里,部署状态卡已经准备好,但还没接上主页

这一点必须特别说清楚。

第 16 课的代码里已经新增了:

  1. app/components/DeploymentStatusCard.tsx
  2. 对应的 .deployment-* 样式

但当前 app/page.tsx 并没有主动:

  1. fetch('/api/health')
  2. import 并渲染 DeploymentStatusCard

所以从真实代码角度看,本课已经把“健康检查接口”和“可复用展示组件”都准备好了,但主页消费层还没有接上。这种边界也很值得你注意,因为它直接影响你该把哪部分当成已上线主链路,哪部分当成下一步扩展位。

6. 相对第 15 课的真实增量

维度第 15 课第 16 课
核心目标升级 Canvas 工作区建立部署检查和交付验收起点
新增 API/api/artifacts/api/health
页面语义Custom RenderingDeploy & Ship
主页主链路聊天 + Canvas 工作区聊天 + Canvas 工作区保持不变
部署辅助 UI没有新增 DeploymentStatusCard.tsx,但未接进主页

四、关键文件

app/api/health/route.ts

这是本课真正新增的部署检查入口。它返回 okcheckstimestamp,是服务端部署状态的最小观察窗口。

app/database/supabase.ts

这里直接消费 NEXT_PUBLIC_SUPABASE_URLNEXT_PUBLIC_SUPABASE_PUBLISHABLE_DEFAULT_KEY。这也是为什么 /api/health 必须先检查这两项。

app/contexts/AuthContext.tsx

OAuth 登录链路同样依赖 Supabase 公开环境变量。部署后如果这两项不对,登录会最先出问题。

app/components/DeploymentStatusCard.tsx

这是部署检查的展示组件。它能把 health JSON 映射成“Ready / Check”和各配置项状态,但当前主页还没有直接挂载它。

app/page.tsx

这里最值得注意的反而是“没改什么”:聊天主界面和 Canvas 工作区都基本沿用上一课,只是页面语义切到了部署阶段,并把画布宽度缓存 key 改成了 lesson16-canvas-width

app/components/ChatHeader.tsx

这个文件把课程标题改成 Lesson 16 / Deploy & Ship,提醒你本课重心已经从功能扩展切换成上线交付。

package.json

这里保持标准的 dev / build / start / lint 脚本,没有引入额外部署脚本。换句话说,这套课程最终态就是标准 Next.js 项目部署路径。

五、整体流程

这张图最关键的信息是:当前代码里真正落地并可直接使用的是 /api/health,而 DeploymentStatusCard 更像一个已经准备好的消费层组件。

六、运行过程

1. /api/health 用最小逻辑暴露部署状态

app/api/health/route.ts 只有一个很克制的目标:判断这四项是否存在。

  1. NEXT_PUBLIC_SUPABASE_URL
  2. NEXT_PUBLIC_SUPABASE_PUBLISHABLE_DEFAULT_KEY
  3. GOOGLE_API_KEY
  4. OPENAI_API_KEY

最后返回的对象很简单:

  1. ok
  2. checks
  3. timestamp

这让你在部署环境里有了一个可直接访问的“起始诊断面板”,哪怕它目前还是 JSON 形式。

2. 这四个环境变量之所以被检查,是因为它们正好覆盖当前主链路

当前代码里,这四项分别对应:

  1. NEXT_PUBLIC_SUPABASE_URL 决定前后端往哪个 Supabase 项目连。

  2. NEXT_PUBLIC_SUPABASE_PUBLISHABLE_DEFAULT_KEY 影响前端登录、会话恢复以及数据库客户端初始化。

  3. GOOGLE_API_KEY 影响图片生成工具。

  4. OPENAI_API_KEY 影响主聊天模型。

也就是说,health route 没有乱选字段,而是优先覆盖“课程终态最容易坏掉的几条主线”。

3. 缺少环境变量时,真实故障往往会先在别的模块爆出来

这是为什么第 16 课必须单独讲部署。

例如:

  1. app/database/supabase.ts 缺少公开 Supabase 环境变量时会直接抛错。
  2. AuthContext.tsx 的 OAuth 登录创建客户端也依赖同一组变量。
  3. 模型 key 不正确时,往往要等你真的去发聊天或生成图片才会暴露。

如果没有 /api/health,你看到的第一个现象可能是“登录失败”或“聊天失败”,但根因其实只是环境变量缺失。

4. 聊天和 Canvas 主链路在这一课刻意保持不变

第 16 课最值得你记住的一点不是“改了什么”,而是“没改什么”:

  1. app/agent/chatbot.ts 与上一课一致
  2. app/services/chat.service.ts 与上一课一致
  3. app/page.tsx 仍然保留聊天页和 Canvas 工作区

这意味着部署课不会再给你增加新的产品心智负担。它做的是收口,不是加戏。

5. DeploymentStatusCard.tsx 已准备好,但当前主页还没有挂载

这是本课和现有文档最容易对不上的地方。

当前代码里:

  1. DeploymentStatusCard.tsx 已经能把 health 结果映射成 Ready / Check
  2. 对应的样式也已经写进 globals.css
  3. app/page.tsx 没有去请求 /api/health
  4. 也没有 import 这个组件

所以更准确的理解应该是:

  1. 服务端检查入口已经落地
  2. 展示组件也已经准备好
  3. 主页整合这一步仍然留作下一步可扩展工作

6. 这节课推荐的最小上线验收顺序

如果你要验证这节课到底学会了什么,建议按下面顺序走:

  1. 本地执行 pnpm build
  2. 把四个关键环境变量填到部署平台
  3. 部署后先访问 api/health
  4. 再验证登录
  5. 再验证普通聊天
  6. 再验证图片工具
  7. 最后验证 Canvas 工作区和 artifact 分享页

这个顺序的价值在于:先排部署基础问题,再看业务层问题,避免排查路径倒着走。

7. 当前健康检查仍然保留了明确边界

你必须清楚,这不是“一键运维系统”。

它现在不会主动帮你发现:

  1. 环境变量值虽然存在,但填错了
  2. OAuth 回调地址不对
  3. Supabase RLS 或 schema 没跑全
  4. 第三方 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(),
  });
}

代码解析:

  1. 这段实现非常克制,但它正好适合作为课程最后一课的“部署起点”。
  2. 它不和具体业务逻辑耦合,也不依赖用户先走登录或聊天流程,因此很适合用来先判断“部署基础条件是否齐全”。
  3. 如果没有这个入口,很多部署问题只能等业务链路报错后再倒查,排查成本会高很多。

关键代码 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。'
  );
}

代码解析:

  1. 这段代码说明 Supabase 相关环境变量不是“可选增强项”,而是应用启动前提。
  2. 它也解释了为什么 health route 要优先检查这两项,因为缺它们时,数据库和认证链路都可能直接坏掉。
  3. 把这个文件和 /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>

代码解析:

  1. 这个组件已经把 health 结果翻译成了对人更友好的 UI,说明作者已经把“下一步如何展示部署状态”想清楚了。
  2. 但你也必须同时看到它目前还没有被 app/page.tsx 使用,所以它更像“已准备好的展示层”,而不是当前线上主入口。
  3. 这正是文档更新时最需要忠于代码的地方:可以讲清它的作用,但不能把它写成已经接入完成的事实。

八、常见问题

1. 为什么主页里没有直接显示部署状态卡?

因为当前代码还没有在 app/page.tsx 里请求 /api/health 并渲染 DeploymentStatusCard。现在真正落地的是健康检查接口和配套展示组件,而不是已经整合好的主页部署面板。

2. 为什么 /api/health 要检查 NEXT_PUBLIC_* 变量?

因为这些公开变量虽然名字里带 NEXT_PUBLIC,但它们同样是当前运行时前提。Supabase 客户端初始化和 OAuth 登录都会依赖它们,缺了照样会影响上线可用性。

3. /api/health 返回 ok: true 就代表一定上线成功了吗?

不是。它只表示最关键的环境变量都存在。数据库权限、第三方 API 配额、OAuth 回调、Storage 配置这些问题,依然要靠后续验收流程继续确认。

九、练习题

  1. app/page.tsx 中真正拉取 /api/health,把 DeploymentStatusCard 接进主页,并补上加载与失败状态。
  2. /api/health 增加一个可选的数据库连通性检查,但要明确区分“变量存在性”与“连接成功”这两种结果。
  3. 为这节课补一份你自己的部署清单,按“构建、环境变量、健康检查、登录、聊天、工具、Canvas”顺序列出验收步骤。

十、总结

第 16 课的价值不在于再做一个更复杂的功能,而在于给整套课程建立了一个正确的交付起点。

你应该把这一课记成三件事:

  1. 核心聊天和 Canvas 主链路已经稳定,本课不再继续改协议。
  2. /api/health 让部署检查第一次有了明确入口。
  3. DeploymentStatusCard 说明展示层已经准备好,但当前真正落地的主链路仍然是“先请求 health 接口,再按顺序验收业务能力”。

登录以继续阅读

解锁完整文档、代码示例及更多高级功能。

立即登录

On this page