在 AWS 上交付全栈应用时,最好不要把 AWS Console 当成真相来源。Frontend、secrets、domains 与 stage rules 都应该用 TypeScript 写在应用旁边,经 pull request 审视,并在每个环境以同样方式套用。
这正是 SST 提供的模型。整个应用以代码定义——通常从单一 sst.config.ts 开始——而 SST 会自动化底层 AWS resources。这篇笔记说明本站的 infrastructure 与 DevOps 模型:Bun + Turborepo monorepo、OpenNext 上的 Next.js,以及 local、shared preview 与 production 的 stages。
三种 infra 故事很容易混在一起,所以这里分开处理:
- 目前已部署 —
sst.config.ts只 import Next.js / OpenNext stack。 - 已在 repo 里定义,但尚未 import — container API infra(
Vpc→Cluster→Service+Router)放在packages/infra,并在run()里保持注释,直到该 stack 需要部署。 - 其他地方的相关模式 — 用 Lambda + API Gateway 做 Hono API 见 用 Hono、Drizzle、Zod OpenAPI 与 SST 打造 Backend APIs。那篇笔记并不是本站
sst.config.ts正在跑的内容。
这篇笔记负责这些边界周围的 infra 布局与 DevOps loop。
1. 为什么这套 stack 选 SST
SST 的设计让开发者可以用 Components 定义应用功能,而不必亲手组装每一个低阶 resource。对这套 stack 来说,最重要的有三点——也是 SST 在跟 Pulumi 或 CDK for Terraform 这类通用 IaC 工具比较时强调的三点(SST FAQ):
- 较高阶的 components,这里用到:
sst.aws.Nextjs、sst.Secret,以及(当 API module 接上时)sst.aws.Service、sst.aws.Router、sst.aws.Vpc与sst.aws.Cluster。 - Resource linking,让 runtime 代码通过 typed
ResourceSDK 读取基础设施,而不是 hardcoded names 与 ARNs。 sst dev作为统一的本地环境:infra watcher、来自同一 CLI 的 frontend(以及之后的 container)devprocesses,再加上当 stage 里有 Lambda handlers 时的 Live。
在本站,这对应到选定 AWS region 里经 OpenNext 部署的 Next.js、以 stage 为基础的 domains(example.com、dev.example.com、{stage}.example.com)、非 prod 的 edge Basic Auth secrets,以及应用 packages 与 packages/infra 分开的 monorepo。
2. SST 与其他 IaC 的比较
这些工具的目标相同:infrastructure as code。差别在主要受众与日常摩擦。SST 自己的定位在这里很有用:SST 面向 developers,而 CDKTF 与 Pulumi 主要面向 DevOps engineers。SST 底层仍使用 open-source Pulumi 与 bridged Terraform providers;它不需要 Pulumi account,应用 state 也留在团队自己的 cloud account(SST docs)。
Terraform 与 Vercel 在其他项目仍然有用。对这个全栈 AWS 应用,SST 是默认选择。这是 workflow 选择,不是主张每个组织都该放弃 Terraform。
| Tool | Mental model | Strength | 为什么 SST 适合这里的 app stacks |
|---|---|---|---|
| Terraform / OpenTofu | Declarative HCL,以 provider 为中心 | 通用、成熟的 state 与 workflow;适合组织级 platform teams | App 与 infra 仍然分开;对这个 monorepo 没有 first-class Live、linking,或 Nextjs DX |
| AWS CDK | TypeScript constructs → CloudFormation | 深度 AWS 覆盖与 Constructs 生态 | AWS-native modeling 很强;对这个 workflow 的统一 app loop(linking、sst dev、frontend + backend multiplexer)较弱 |
| Pulumi / CDKTF | 用真实语言写的通用 IaC | Providers 灵活;适合 platform team 拥有整支 program 的情况 | SST 建在 Pulumi 之上,但加上 app components、linking 与 sst dev,不必为每个 feature 写完整 Pulumi program |
| Serverless Framework | 聚焦 serverless apps 的 framework | 熟悉的 Lambda packaging | 比这里用到的全栈 Next/OpenNext、containers 与 stage workflows 更窄 |
| Vercel / PaaS | Git push → hosted platform | 快速 frontend DX,几乎不用碰 AWS 表面 | 只需要 managed frontend 时很强;当需要以 TypeScript 拥有 AWS resources、domains、secrets 与可选的 ECS/API 时选 SST |
表格之外:SST components 编码常见 app patterns,仍允许在需要更低阶控制时使用 transform 加上 150+ providers。Linking 补上 Terraform 或 CDK 通常靠手写 env vars 与 IAM 填补的 runtime 缺口。sst dev 是产品功能,而不是自己组装的本地 stack。SST 仍然有 state——本地加上 backup bucket——所以 managed resources 不应在 Console 人手编辑(Basics)。
当 platform team 已经用 Terraform 或 OpenTofu 拥有 shared networking、当 Pulumi 或 Terraform 是团队多云 control planes 的语言,或当 CloudFormation-native CDK construct libraries 是招聘与 review 表面时,SST 并不适合当唯一答案。那些情况下,SST 仍可很好地做 app delivery,而组织级 IaC 留在团队已有杠杆的地方。
3. 项目形状:sst.config.ts 与 packages/infra
SST 支持 drop-in mode——在 Next.js app 旁边放单一 sst.config.ts——以及 monorepo,config 留在 root,基础设施拆成 modules。这个项目用 monorepo 形状。
sst.config.ts app name, region, protect / removal, imports
packages/infra/ Next.js, secrets, domain, edge, optional API/VPC
apps/web Next.js site
apps/api Hono / Mastra API
packages/* shared auth, db, content, design systemConfig 文件定义 app identity 与安全规则。run function 加载 infra modules。今天只 import Next.js;API module 保持注释,直到该 stack 需要部署:
export default $config({
app(input) {
return {
name: "my-app",
removal: input?.stage === "production" ? "retain" : "remove",
protect: ["production"].includes(input?.stage),
home: "aws",
providers: {
aws: {
region: "us-east-1",
},
},
}
},
async run() {
// await import("./infra/api")
await import("./infra/nextjs")
},
})packages/infra 放着会被组合起来的零件:
nextjs— OpenNext deployment 与 domains(今天已 import)secrets— stage secrets,例如 edge Basic Auth credentials,以及 API 路径用的 database 与 auth secretsdomain/edge— apex domain 与非 prod 在 edge 的 Basic Authapi/router/vpc/cluster— container API 路径,已定义但尚未 import
Business logic 不放进这些文件。Infra modules 创建并 link resources;application packages 消费它们。
4. 这套 Stack 里的 Components
搭配 OpenNext 的 Next.js
网站以 sst.aws.Nextjs 部署。Production 用 apex domain;其他 stages 用 {stage}.example.com。Production 保留一个 warm server instance;非 prod 用 warm: 0。edge prop 挂上非 prod Basic Auth。dev block 告诉 sst dev 如何启动本地 Next process。
const isProd = $app.stage === "production"
const domain = "example.com"
export const nextjs = new sst.aws.Nextjs("Web", {
path: "apps/web",
domain: {
name: isProd ? domain : `${$app.stage}.${domain}`,
redirects: isProd ? [`www.${domain}`] : [],
},
warm: isProd ? 1 : 0,
environment: {
NEXT_PUBLIC_SITE_URL: `https://${domain}`,
SST_STAGE: $app.stage,
},
openNextVersion: "4.0.3",
// buildCommand: sync assets, then OpenNext build
edge,
dev: {
command: "bun run dev",
directory: "apps/web",
url: "http://localhost:3000",
},
})Secrets
Secrets 是 first-class resources,不是从未进入 deployment model 的松散 .env 文件。Edge Basic Auth 在非 prod 使用 username/password secrets。Database 与 auth secrets 为 API 路径定义:
export const username = new sst.Secret("Username")
export const password = new sst.Secret("Password")
export const databaseUrl = new sst.Secret("DatabaseUrl")
export const betterAuthSecret = new sst.Secret("BetterAuthSecret")
export const aiGatewayApiKey = new sst.Secret("AiGatewayApiKey")Container API(已定义,未 import)
当 API module 被 import 时,路径是 Vpc → Cluster → Service,并以 Router 处理 api.example.com 或 {stage}.api.example.com。Database、Better Auth 与 AI Gateway secrets 会 link 进该 service。在 sst.config.ts 启用 await import("./infra/api") 之前,那个 stack 不会部署。
另一种形状——API Gateway 后面的 Lambda,加上 Lambda authorizer——是 用 Hono、Drizzle、Zod OpenAPI 与 SST 打造 Backend APIs 里的模式。那里用 sst.aws.Function 与 sst.aws.ApiGatewayV2。这是 Hono APIs 的相关 SST 做法,不是本站 config 目前加载的基础设施。
5. 用 Linking 取代 Hardcoded Env
Resource linking 是基础设施与 runtime 之间的桥。创建 resource、放进 link,再用 SDK 读取。Linking 会注入 values、生成 types(sst-env.d.ts),并在 component 支持时授予 permissions。
本站今天,Next.js stack 通过 edge configuration 取得 edge Basic Auth credentials(username/password secrets)。Web app 本身并不为 database 读取 Resource.*。
当 API module 被 import 时,secrets 会 link 进 container service,应用代码可以这样读:
import { Resource } from "sst"
const client = postgres(Resource.DatabaseUrl.value, {
ssl: "require",
})对 frontends 来说,linked values 只在 server 端可用。Client components 只看得到明确传进去的内容。
对需要 stage secrets 的 CLIs——Drizzle migrations、Better Auth schema generation——sst shell 会加载 linked stage environment,不必再发明第二条 secrets 路径。
6. Stages 作为 DevOps 单位
在 SST 里,stage 就是一个环境:应用的 namespaced 副本。本站实务上的对应大致如下:
| Stage | Purpose | Domain pattern |
|---|---|---|
local / personal | 日常 sst dev | local Next URL;该 stage 的 cloud resources |
dev | Shared preview | dev.example.com |
production | Live site | example.com |
Production 受保护。意外的 sst remove 不应抹掉关键数据,所以 config 在 production 使用 protect 与 removal: "retain",符合 SST 建议的 template pattern(removal policies)。
非 production stages 还有一项实务 ops 细节:通过 edge viewerRequest injection 做 CloudFront Basic Auth,让 preview URLs 默认不公开。
日常命令保持简短:
aws sso login # refresh AWS credentials
sst dev --stage local # personal / local stage
sst deploy --stage dev # shared preview
sst deploy --stage productionCredentials 留在本地 AWS credential chain(profile 或 SSO session)。SST 部署进该 account 与 region。Next.js 与 secret resources 不是靠人手在 Console 点出来的。
7. 本地 Workflow:sst dev、Live 与 Mode Discipline
sst dev 是本地 control plane。对本站已接上的 Next.js stack,日常最有价值的部分是:
- 部署 infrastructure 变更的 watcher。
- Next.js
devblock——本地 Next,链接到该 stage 已部署的 resources。 - 之后当 API module 被 import 时,container
devcommand,以及可选的 tunnel 连到 VPC resources。
Live——AWS 里的 stub Lambdas 代理回本机——对 Lambda-heavy stages 最重要,包括 backend APIs 笔记里的 Function / API Gateway 模式。当本站只 import OpenNext 时它较不核心,不过 OpenNext server paths 依部署形状仍可能在底层用到 Lambda。
sst dev 应放在 personal 或 local stage。Shared dev 与 production 用 sst deploy。把同一个 stage 在 Live stubs 与真实 deploys 之间切换既慢又混乱:若 CLI 被杀掉,stubs 可能留下,而远端 invokes 会一直等本机超时(Live quirks)。
需要 shared URL 就部署真实 stage。要用 breakpoints 调试 Lambda handler,就在 personal stage 上用 Live——调试 Node handlers 时可用 VS Code Auto Attach。
8. Production 安全与 CI 边界
IaC 只有在 state 与流程保持诚实时才有用。
- 不要在 Console 编辑 SST 管理的 resources。SST 会依 config 对 state 套用 diffs;人手编辑会让 state drift(Basics)。
- 保护 production:用
protect与removal: "retain",避免意外 production remove 抹掉 SST-managed stateful resources。API 路径用的 Postgres 是经sst.Secret的外部 URL,不是这个 config 创建的sst.aws.Postgresdatabase。 - 先验证,再在 CI 部署。 GitHub Actions 先跑 install、lint、format check、typecheck、tests 与 build。只有这些 checks 成功后,workflow 才对目标 stage(
dev或production)跑sst deploy。本地deploy:dev/deployscripts 仍适合刻意的一次性 deploys,但默认路径是 CI:checks 全绿,再 deploy。
SST Console 是另一种取得 git-push autodeploy、preview environments 与 monitoring 的方式。本站用 GitHub Actions 做 verify-then-sst deploy 序列,而不是依赖 Console 跑这个 loop。
Tenant isolation 与 SaaS 边界设计是另一个问题——当问题是那个时,见 用 Hono、Better Auth、Drizzle 与 Postgres RLS 打造 Multi-Tenant 后端。
9. Mental Model
sst.config.ts
→ packages/infra/nextjs (imported today)
→ Nextjs / OpenNext + edge Basic Auth secrets
→ packages/infra/api (defined, not imported)
→ Vpc / Cluster / Service / Router + linked secrets
→ link (when a consumer is linked)
→ Resource.* in app runtime
→ sst shell for CLIs
stages: local | dev | production
local: sst dev
ci: checks → tests → build → sst deploy --stage …运营 loop 保持精简:
- 改 infra TypeScript 或 application code。
- 在 personal stage 用
sst dev迭代。 - 开 pull request;GitHub Actions 跑 checks、tests 与 build。
- 成功后,CI 对目标 stage(
dev或production)跑sst deploy。
结语
SST 让 AWS infrastructure 成为可 review 的 TypeScript,并放在应用旁边。Components 编码常见的交付功能。Linking 拿掉一类 env-var 与 IAM glue。Stages 把环境变成 first-class DevOps 单位。sst dev 把本地 stack 收成一条命令。
当问题是组织级 platform ownership,或团队已在那里标准化时,Terraform、CDK 或 Pulumi 仍然更合适。对一个跑在 OpenNext 上的 Next.js site——有 stage-based domains、secrets,以及需要时可用的 container 或 Lambda API 路径——SST 是很强的默认,因为它优化的是 developer loop,而不只是 resource graph。
关于 Lambda 或 Fargate 上的 Hono API——包括 authorizer wiring、WAF 与 alarms——继续看 用 Hono、Drizzle、Zod OpenAPI 与 SST 打造 Backend APIs。目前的 component 与 CLI 细节,从 SST docs 开始。