部署与生产环境
本页按“准备交付物 → 启动并检查 → 接入进程管理与反向代理 → 发布和回退”说明生产部署。先完成 CLI 最小项目流程和构建,再选择适合环境的部署方式。
先验证一次生产启动
前置:已有 TypeScript API-only 项目,依赖已安装,脚手架的 src/routes/index.ts 保留 / 与 /health。在项目根目录执行:
另开终端检查(Windows PowerShell 使用 curl.exe):
预期两项均为 HTTP 200,健康响应的 data.status 为 ok。结束后在启动终端按 Ctrl+C,并确认进程退出、端口释放。换成 fullstack 脚手架时,健康路径为 /api/health;应用自定义后以自己的真实路径为准。框架不会给所有项目自动添加统一健康端点。
本页其余配置是按场景合并的片段,不应依次覆盖整个配置文件。容器、PM2、Nginx 和 Kubernetes 示例需要对应平台、权限及实际服务地址;完成本地启动并不代表这些平台已部署验证。
构建生产产物
vext build
使用 vext build 刷新 generated / manifest 工具产物,并将 TypeScript 源码编译为生产级 JavaScript:
未指定输出目录、环境变量或已有构建记录时,编译产物输出到 dist/,保留与 src/ 对应的模块结构。本页使用固定 dist 的部署示例时,构建和启动均显式传入 --outdir dist;自定义目录则成对替换,避免启动另一份旧产物。
使用 --typecheck 会先刷新 .vext/types/、src/types/generated/index.d.ts 与 .vext/manifest/,再执行 tsc --noEmit 和生产编译。以下是可选业务目录的映射示意:
编译选项
本表描述后端编译器。前端产物采用独立的生产默认值:浏览器压缩开启、浏览器 source map 关闭,SSR renderer 压缩关闭,除非显式配置。
编译排除
生产编译自动排除以下文件:
*.d.ts/*.d.mts/*.d.cts— 类型声明*.test.*/*.spec.*— 测试文件__tests__/— 测试目录config/development.*— 开发环境配置config/local.*— 本地覆盖配置config/test.*— 测试环境配置
编译底层
vext build 的后端编译阶段基于 esbuild 实现。实际耗时取决于项目规模、插件转换、source map 配置、文件系统与硬件;制定部署预算时请测量自己的 CI 或发布构建。
编译时会自动注入 process.env.NODE_ENV = "production",因此 build 后用户源码中的环境分支会按 production 语义静态折叠;但运行时实际加载哪个配置 profile,仍按启动配置选择:显式 --config、VEXT_CONFIG、兼容的非标准 NODE_ENV 名,再到构建身份记录的 profile / production 默认值。编译后的文件必须实际存在,不能只在服务器上改一个 profile 名。
完整交付清单
输出目录按显式 --outdir → VEXT_BUILD_OUTDIR → 项目构建记录 → dist 选择。交付到没有本地记录的新环境时明确传入 --outdir。不要只挑选 routes/services 的 JS;外部模板、上传目录、配置文件和动态读取的资源按实际路径另行交付。保留目录权限和持久数据,确保运行用户能写 PID、上传和应用状态。
后端不会打包 npm 依赖,vextjs、适配器及业务运行依赖须列入应用的 dependencies。TypeScript 的 start 不回退源码;JavaScript 项目仍需源码。更多边界见部署清单。
生产交付前端
当 frontend.enabled 为 true 时,vext build 还会将浏览器与 SSR 产物默认写入 dist/client/:index.html、带 hash 的资源、render-manifest.json、deploy-manifest.json 和默认的 server/renderer.cjs。后端产物仍在 dist/ 下保持源码目录映射;不要部署一个并不存在的固定顶层 dist/server/ 目录。
同源部署
先确认前端角色目录和渲染模式已配置、构建通过;同源静态资源不需要配置 CDN 地址:
vext start 会在 listen 前校验前端产物,再按配置服务静态资源和 SSR 页面。CSR 的 SPA fallback 还需配置相应 scopes;不能据此认定所有未知 URL 都自动回落到 index.html。
CDN 部署与上传
只有确定由 CDN 承担 immutable browser assets 时才使用 CDN。设置绝对 frontend.deploy.assetBaseUrl,让 HTML/SSR 继续由 Node 服务提供,然后在真实上传前审阅 upload plan:
deploy-manifest.json 包含可上传的 JS、CSS、import 型媒体与 public/** 文件,并带有 sha256/SRI metadata。它刻意排除 SSR HTML 与 source map。frontend.deploy.upload.stateFile 必须放在输出目录之外,避免普通 build 清理增量上传状态。
内置 adapter 只有 filesystem 与 mock。filesystem 用于生成本地 staging tree;真实云厂商需要显式 custom adapter。自定义 adapter 的依赖和上传凭据由应用显式配置。完整步骤见构建与发布,全部字段见前端配置。
启动生产服务
直接启动
TypeScript 项目要求有效且完整的构建产物;开发用 vext dev。不要通过上传源码或安装 tsx 掩盖生产产物缺失。部署时固定工作目录,PID 文件和相对路径均与它有关。
环境变量
停止与超时预算
正常关闭先停止接收新流量,再等待在途请求和 onClose 清理。单应用 shutdown.timeout 单位为秒,默认 10;Cluster 的 reload.shutdownTimeout 单位为毫秒,默认 10000,外部进程管理器还应留出额外时间。以内部 10 秒、容器/PM2 30 秒为起点,并按自己的请求与连接关闭耗时调整。
Unix 上 CLI 转发 SIGINT / SIGTERM;Windows 的 CLI 信号处理走子进程 IPC,并有 15 秒兜底。Windows 外部强制结束进程不等价于触发应用 onClose。Cluster 停止、PID 定位和 SIGHUP 平台限制见Cluster。
Docker 部署
Dockerfile
以下适用于默认 dist 的 TypeScript API-only 项目,无前端、外部模板或额外构建资源。构建上下文需包含应用 package.json、锁文件、tsconfig.json 和 src;如果有项目 preload 或其他构建输入,也要复制。前端项目按上一节补齐角色目录和输出。
健康路径对应本页 API-only 脚手架;使用 GET 而不是依赖应用支持 HEAD。exec 形式启动本地 CLI,避免 npm/shell 额外包裹影响信号传递。EXPOSE 仅声明端口,实际宿主映射见下方命令。
.dockerignore
Docker Compose
Compose 示例需应用主动读取数据库变量。若前层没有 database,生产配置中提供完整值,例如:
depends_on: service_healthy 处理初始启动依赖;Mongo 后续掉线仍需应用重试、就绪检查和运维处置。Mongo volume 独立于应用镜像保留。容器 healthcheck 本身不会让普通 Docker 自动重启一个仍在运行的 unhealthy 进程。参阅 Dockerfile 健康检查与 Compose 依赖顺序。
构建和运行
独立 docker run 的数据库地址示例适用于提供 host.docker.internal 的 Docker Desktop;Linux 主机需使用实际可达地址或明确配置 host-gateway。Compose 场景使用服务名 mongo。
Nginx 反向代理
基础配置
先准备域名、证书和可达的后端端口。以下是 HTTP API 代理;确认应用真实依赖转发地址时再配置 trustProxy,并限制后端仅由可信代理访问。
普通 API 请求不应统一发送 Connection: upgrade。仅当应用适配器实际支持并提供 WebSocket 时,另按 Nginx WebSocket 文档配置条件 Upgrade;SSE 还需按实际流式接口调整 buffering / timeout。/static/ 的 alias 是独立静态目录示例,不会自动对应 Vext 前端带 hash 的产物。
修改后先执行 nginx -t,通过后按部署系统重载 Nginx,并验证外部 HTTPS、转发头、上传大小及健康路径。
多实例负载均衡
PM2 进程管理
安装
ecosystem 配置文件
以下用于 Unix 主机,先创建可写日志目录,并替换 cwd 为实际发布目录:
PM2 管理的是 Vext CLI 父进程,业务在其子进程或 Cluster Worker 中。不要把 PM2 的 CPU/内存指标直接当作所有 Worker 指标;为业务进程和容器单独监控。
保留 PM2 默认 SIGINT 关闭路径。不要设置 shutdown_with_message: true:它发送字符串 "shutdown",当前 CLI 没有对应处理器。详见 PM2 关闭机制和配置字段。
PM2 常用命令
VextJS 通过配置 cluster.enabled: true 或环境变量 VEXT_CLUSTER=1 启用内置 Cluster,由 cluster.workers 指定 Worker 数量;start 不支持 --cluster 或 --workers 参数。使用内置 Cluster 时保持 PM2 的 instances: 1,由 VextJS 管理 Worker;滚动替换仍受下文平台与就绪条件限制。
详见 Cluster 多进程。
PM2 的 fork 单实例示例不提供多实例滚动保障。使用 Vext Cluster 时仍保持外层单实例;SIGHUP reload 需要指向 Vext Master 的 PID,不能假定 PM2 reload 会自动调用 Vext 的滚动协议。共享 Session、限流和连接池须另外配置。
日志收集
JSON 日志格式
生产应用日志明确配置 pretty: false 后输出逐行 JSON。CLI 启动提示可能与 JSON 同流,采集器需区分;不要再由 PM2 添加行首时间戳破坏 JSON。以下为使用默认 ISO 时间戳的示意字段,实际请求日志格式见日志与访问日志:
配置日志级别
日志收集方案
方案一:文件 + Filebeat → ELK
此片段使用 Filebeat filestream / ndjson。采集混合启动文本时处理解析失败事件,并补充实际认证、索引策略和日志轮转;原 log input 已弃用,不宜作为新配置起点。
方案二:Docker 日志 → Loki
Docker 主机须先按 Loki Docker driver 文档安装并配置驱动;下面片段只声明使用它,不会部署 Loki 或安装驱动。
方案三:stdout → Cloud 原生
在 Kubernetes / AWS ECS / Cloud Run 等平台中,直接输出到 stdout,由平台自动收集:
健康检查
实现健康检查端点
脚手架已有 /health 时,替换原处理器,不要重复注册。在 src/routes/index.ts 中保留业务路由并合并:
index.ts 不增加文件名前缀;若拆成 health.ts,其中应注册 "/",否则 "/health" 会成为 /health/health。默认响应包装使状态位于 data.status。全局认证、中间件或缓存也应为探针设置实际需要的放行规则,关闭限流不能自动绕过它们。
- Liveness:判断进程能否响应;不要让短暂数据库故障触发所有实例反复重启。
- Readiness:判断关键业务依赖是否可用;失败返回 503,恢复返回 200。只判断
app.db !== undefined不能证明数据库仍可用。使用数据库时可通过已初始化的req.app.db.client执行真实 ping,并设置依赖超时;按数据库准备连接。 - Cluster:一次 HTTP 探针只命中一个 Worker。
vext status固定请求/health,也不代替多 Worker、外部依赖和业务探针,其退出码 0 不能作为部署健康门禁。
验证 200 路径后,还应人为使关键依赖不可用,确认 readiness 返回 503、实例退出流量;恢复后复验。
Kubernetes 探针配置
该片段放入 Deployment 的 spec.template;完整资源仍需 metadata、selector、replicas 和镜像拉取配置。依据实际启动时间考虑 startupProbe;配合多副本、就绪状态与终止宽限处理发布摘流,不能仅因存在 readinessProbe 就宣称零中断。探针语义见 Kubernetes 官方说明。
异常崩溃通知(onFatalError)
VextJS 内置了进程级异常捕获机制,当发生 uncaughtException 或 unhandledRejection 时,框架会:
- 记录
fatal级别日志 - 调用用户配置的
onFatalError回调(如有) - 执行优雅关闭(onClose hooks 清理资源)
process.exit(1)退出进程
配置 onFatalError
在 shutdown 配置中添加 onFatalError 回调,接入告警通知:
替换为自己的 Webhook 地址;通知格式和权限由目标服务决定,以下各片段替换同一个 onFatalError 回调。外部请求应设置超时并检查响应,不应把 HTTP 非 2xx 当作发送成功。
企业微信 Webhook 示例
Slack Webhook 示例
通用 HTTP Webhook 示例
注意事项
uncaughtException 和 unhandledRejection 发生在 HTTP 中间件执行链之外(例如定时任务、事件监听器中的异常),中间件无法捕获这类错误。因此必须在框架 bootstrap 层注册 process 级事件监听器。
安全加固
生产环境清单
环境变量管理
VextJS 不会自动解析 .env 文件,也不内置隐式 dotenv loader。代码通过
process.env 读取的值,必须由操作系统、shell、进程管理器、容器平台、CI/CD、
密钥管理服务或应用显式拥有的 loader 注入。脚手架仍会忽略 .env* 文件,以降低
外部工具创建这些文件后被误提交的风险;这项 Git 防护不表示 Vext 会加载它们。
性能优化
Node.js 参数
Node 默认堆上限取决于版本、平台和可用内存,不是固定 1.5GB。该参数限制 V8 old-space,不等于进程 RSS 或整个容器内存。
Source Map
后端 build 默认生成 .js.map,但采用 esbuild external 模式,不写入 sourceMappingURL。因此仅设置 NODE_OPTIONS=--enable-source-maps 不能保证当前产物的堆栈映射回 TypeScript。需由实际诊断平台关联 JS 和 map,并验证异常样本;详见构建的 Source Map 边界。
Cluster 多进程
在已有生产配置中合并以下设置,再构建并启动。固定数量先以实际 CPU、内存和连接池预算验证;如使用 "auto",框架按检测到的可用 CPU 数计算,上限 64。
详见 Cluster 多进程。
连接池优化
每个 Worker 各建连接池,最大连接预算按“池上限 × Worker × 副本”估算,另计监控和其他进程。fetch 重试只适用于实现支持的幂等方法与可重放请求,还应计入每次尝试超时和退避的总耗时;详见HTTP 客户端。
部署流程建议
CI/CD 流水线
灰度发布
- 构建带唯一版本标签的镜像,保留当前版本及与其配套的配置。
- 在新端口或副本部署新版本,直接验证真实 health、ready 和业务请求。
- 校验并重载代理配置,按权重引入流量;权重表示调度比例,不保证每十次请求恰好一次灰度。
- 观察错误、延迟、依赖负载和会话兼容;通过后扩大流量,失败则切回旧实例。
- 摘除旧实例后等待在途请求和长连接,再关闭旧进程。数据库结构变更须有自己的兼容和回退策略。
应用需要数据库时,同步传入上文实际连接配置。下面片段替换 Nginx 的 upstream:
Vext Cluster reload 不更新 Master 自身配置,也不会替你发布镜像、切换数据结构或保证全部长连接无损。sticky: "ip" 按 TCP 源地址在实例内选择稳定 Worker 槽位;代理/NAT 可能造成热点,故障和滚动替换不会迁移内存 Session。外部负载均衡的实例亲和性不等于共享端口内的 Worker 亲和性。全部 Worker 消失且没有恢复工作时,标准 Master 清理 PID 并以 1 退出,供 PM2/容器监督恢复;外部监督需要退避。继续使用业务健康检查和有效 Worker 容量告警。
监控告警
关键监控指标
下表仅是制定告警的起点,按业务 SLO、基线和资源限额调整;不是框架默认指标或性能保证。
Prometheus 指标端点
按 OpenTelemetry 接入示例 初始化真实 Prometheus Exporter,配置采集器访问它实际监听的端口与路径。普通 res.json() 响应不提供 Prometheus 指标,不能作为 exporter 的替代。
多服务的共享资源边界
每个服务使用自己的 cwd、配置 profile、业务端口、生成目录和持久数据目录。不同端口不会隔离同域 Cookie;按实际共享意图选择 cookie 名、path/domain 和 Session store namespace。需要独立会话时显式配置隔离,不能只改端口。
响应缓存、MonSQLize 查询缓存、Session 与限流存储是不同的系统。共享 Redis/数据库前核对 key prefix/namespace、TTL 单位和失效范围;框架不擅自重命名用户配置。连接预算按每进程池上限 × worker 数 × 服务数计算,再加独立 pools/代理;外部真实上限需要部署证据。
同进程多 app 的 Model 注册按 owner 维护:相同定义可共享;不同定义抢同一 key 在注册前失败;关闭只释放本 app 的引用。库/池选择与注册 key 不同,详见数据库。多进程各自有注册表,外部数据库和缓存仍可能共享。
部署定时任务
定时任务随 HTTP 应用就绪后自动调度,使用应用已有服务和关闭流程。内置 Cluster 存在启用任务时必须配置 jobs.redis;其他多副本部署也需要共享 Redis 和一致的调度定义,避免重复触发;namespace 由包名、profile 和运行模式自动生成,通常无需手填。Redis 不可用时跳过执行,不降级;重新启动只调度未来周期。详见定时任务 Jobs。
文档站发布
维护 VextJS 仓库本身时,文档源保存在 devcodex-labs/vextjs,同一提交发布到两个文档站。它们与上文业务应用部署是两套流程。
源码仓库的 Settings → Pages → Source 保持 GitHub Actions。新增组织站需要以下首次配置,之后不需要手动复制构建结果:
- 在
vextjs组织创建公开仓库vextjs.github.io,初始化main分支。该仓库专门保存构建结果;发布时会替换根目录中的站点文件,删除旧产物并保留 Git 提交历史。 - 为该仓库创建专用 SSH Deploy Key。公钥放入站点仓库的
Settings → Deploy keys,勾选 Allow write access;私钥通过源码仓库的Settings → Secrets and variables → Actions保存为VEXT_DOCS_DEPLOY_KEY。不要把私钥放进源码、文档或聊天消息。 - 在站点仓库的
Settings → Pages选择 Deploy from a branch,分支选择 main、目录选择 /(root)。该组织根地址由仓库名称决定,无需配置 CNAME。
.github/workflows/docs.yml 使用 Node.js 22,分别设置 VEXT_DOCS_BASE 与 VEXT_DOCS_SITE_URL,按表中的路径和地址构建两个站点。两份产物分别上传和校验,避免根路径与 /vextjs/ 的资源链接混用。自动发布跟随源码仓库 main 的成功 push CI,检出该次 CI 的精确 SHA。
两站分别调用 .github/workflows/docs-build.yml,各自通过完整文档发布校验后启动对应的发布任务:源码仓库使用 Pages artifact 与内置 GITHUB_TOKEN;组织站使用 VEXT_DOCS_DEPLOY_KEY 推送产物。任一站点构建或发布失败,只阻止该站点本次发布,另一站点仍可在自身校验通过后发布。尚未配置该密钥时,工作流跳过组织站发布并记录提示,源码仓库仍照常发布。发布前再次检查源码 main 的当前提交;已被后续提交替代的构建会跳过发布。版本 tag 的自动发版继续由 release.yml 处理。
需要重新发布时,在源码仓库的 Actions → Deploy Docs → Run workflow 选择 main,同时重建和发布两个站点。手动入口同样执行完整文档校验;其他分支不能通过该入口发布到生产站点。组织站的推送任务成功后,仍需等待目标仓库的 Pages 发布任务完成才能看到更新。
下一步
- 了解 Cluster 多进程 充分利用多核 CPU
- 查看 OpenTelemetry 接入 实现完整的可观测性
- 学习 Nacos 接入 实现微服务注册发现
- 探索 配置 中的环境配置覆盖机制