跳到主要内容

在 AWS 上交付全栈应用时,最好不要把 AWS Console 当成真相来源。Frontend、secrets、domains 与 stage rules 都应该用 TypeScript 写在应用旁边,经 pull request 审视,并在每个环境以同样方式套用。

SST 用代码定义应用——通常从单一 sst.config.ts 开始——并自动化底层 AWS resources。本站是 Bun + Turborepo monorepo,Next.js 跑在 OpenNext 上,并有 local、shared preview 与 production 的 stages。

三种 infra 故事分开处理:

  1. 目前已部署 — sst.config.ts 只 import Next.js / OpenNext stack。
  2. 已在 repo 里定义,但尚未 import — container API infra(Vpc → Cluster → Service + Router)放在 packages/infra,并在 run() 里保持注释,直到该 stack 需要部署。
  3. 其他地方的相关模式 — 用 Lambda + API Gateway 做 Hono API 见 用 Hono、Drizzle、Zod OpenAPI 与 SST 打造 Backend APIs。那篇笔记并不是本站 sst.config.ts 正在跑的内容。


1. 为什么这套 stack 选 SST

SST 让开发者可以用 Components 定义应用功能,而不必亲手组装每一个低阶 resource。

  • 较高阶的 components,这里用到:sst.aws.Nextjs、sst.Secret,以及(当 API module 接上时)sst.aws.Service、sst.aws.Router、sst.aws.Vpc 与 sst.aws.Cluster。
  • Resource linking,让 runtime 代码通过 typed Resource SDK 读取基础设施,而不是 hardcoded names 与 ARNs。
  • sst dev 作为统一的本地环境:infra watcher、来自同一 CLI 的 frontend(以及之后的 container)dev processes,再加上当 stage 里有 Lambda handlers 时的 Live。

本站:经 OpenNext 的 Next.js、以 stage 为基础的 domains、非 prod 的 edge Basic Auth,以及与 packages/infra 分开的应用 packages。SST 与 Pulumi 或 CDK for Terraform 的比较见 SST FAQ。



2. SST 与其他 IaC 的比较

这些工具都共享 infrastructure as code。SST 面向 developers;CDKTF 与 Pulumi 主要面向 DevOps engineers。SST 底层仍使用 open-source Pulumi 与 bridged Terraform providers;它不需要 Pulumi account,应用 state 也留在团队自己的 cloud account(SST docs)。

ToolMental modelStrength为什么 SST 适合这里的 app stacks
Terraform / OpenTofuDeclarative HCL,以 provider 为中心通用、成熟的 state 与 workflow;适合组织级 platform teamsApp 与 infra 仍然分开;对这个 monorepo 没有 first-class Live、linking,或 Nextjs DX
AWS CDKTypeScript constructs → CloudFormation深度 AWS 覆盖与 Constructs 生态AWS-native modeling 很强;对这个 workflow 的统一 app loop(linking、sst dev、frontend + backend multiplexer)较弱
Pulumi / CDKTF用真实语言写的通用 IaCProviders 灵活;适合 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 / PaaSGit 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)。那份 state 的 backup,以及何时 snapshot 不是 failover,见 System Design 里的 Disaster Recovery。

当 platform team 已经用 Terraform 或 OpenTofu 拥有 shared networking、当 Pulumi 或 Terraform 是团队多云 control planes 的语言,或当 CloudFormation-native CDK construct libraries 是招聘与 review 表面时,SST 并不适合。



3. 项目形状:sst.config.ts 与 packages/infra

SST 支持 drop-in mode——在 Next.js app 旁边放单一 sst.config.ts——以及 monorepo,config 留在 root,基础设施拆成 modules。这个项目用 monorepo 形状。

text
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 system

Config 文件定义 app identity 与安全规则。run function 加载 infra modules。今天只 import Next.js;API module 保持注释,直到该 stack 需要部署:


sst.config.ts
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")
  },
})

  • nextjs — OpenNext 部署与 domains(今天已 import)
  • secrets — stage secrets,例如 edge Basic Auth credentials,以及 API 路径的 database 与 auth secrets
  • domain / edge — apex domain 与非 prod 的 edge Basic Auth
  • api / router / vpc / cluster — container API 路径,已定义但尚未 import

业务逻辑不放在这些文件里。Infra modules 创建并 link resources;应用 packages 消费它们。



4. 这套 Stack 里的 Components

本站用 sst.aws.Nextjs 部署。Production 用 apex domain;其他 stages 用 {stage}.example.com。


packages/infra/nextjs.ts
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",
  edge,
  dev: {
    command: "bun run dev",
    directory: "apps/web",
    url: "http://localhost:3000",
  },
})

  • Production 保留一个 warm server instance;非 prod 用 warm: 0。edge prop 接上非 prod Basic Auth。dev block 告诉 sst dev 如何启动本地 Next process。
  • Secrets 是 first-class resources,不是松散的 .env 文件:sst.Secret("Username")、Password、DatabaseUrl、BetterAuthSecret、AiGatewayApiKey。
  • 当 API module 被 import 时,路径是 Vpc → Cluster → Service,再用 Router 做 api.example.com 或 {stage}.api.example.com。在启用 await import("./infra/api") 之前,那个 stack 不会部署。
  • 另一种形状——Lambda 在 API Gateway 后面,加上 Lambda authorizer——是 用 Hono、Drizzle、Zod OpenAPI 与 SST 打造 Backend APIs 里的模式。那用 sst.aws.Function 与 sst.aws.ApiGatewayV2。它不是本站 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。Web app 本身并不为 database 读取 Resource.*。
  • 当 API module 被 import 时,secrets 会被 link 进 container service。

ts
import { Resource } from "sst"

const client = postgres(Resource.DatabaseUrl.value, {
  ssl: "require",
})

  • 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 copy。

StagePurposeDomain pattern
local / personal日常 sst dev本地 Next URL;该 stage 的 cloud resources
devShared previewdev.example.com
productionLive siteexample.com

  • Production 受保护。意外的 sst remove 不应抹掉关键数据,所以 config 在 production 上用 protect 与 removal: "retain"(removal policies)。
  • 非 production stages 通过 edge viewerRequest injection 拿到 CloudFront Basic Auth,所以 preview URLs 默认不公开。
  • Credentials 留在本地 AWS credential chain(profile 或 SSO session)。SST 部署进那个 account 与 region。Next.js 与 secret resources 不是靠在 Console 点出来的。

bash
aws sso login               # refresh AWS credentials
sst dev --stage local       # personal / local stage
sst deploy --stage dev      # shared preview
sst deploy --stage production


7. 本地 Workflow:sst dev、Live 与 Mode Discipline

sst dev 是本地 control plane。

  • 一个会部署基础设施变更的 watcher。
  • Next.js dev block——本地 Next,link 到该 stage 已部署的 resources。
  • 之后,当 API module 被 import 时,container dev command,以及可选的 tunnel 到 VPC resources。
  • Live——AWS 里把请求 proxy 到本机的 stub Lambdas——对 Lambda-heavy stages 最重要,包括 backend APIs 笔记里的 Function / API Gateway 模式。在本站只 import OpenNext 时,它没那么核心。

sst dev 属于 personal 或 local stage。Shared dev 与 production 用 sst deploy。Shared URL 意味着部署一个真正的 stage。用 breakpoints 调试 Lambda handler 意味着在 personal stage 上用 Live——调试 Node handlers 时搭配 VS Code Auto Attach。

Failure: 在同一个 stage 上在 Live stubs 与真正 deploys 之间翻转。若 CLI 被杀掉,stubs 可能留下,remote invokes 会超时等待本机(Live quirks)。



8. Production 安全与 CI 边界

IaC 只有在 state 与流程保持诚实时才有用。

  • 不要在 Console 编辑 SST 管理的 resources。SST 会依 config 对 state 套用 diffs;人手编辑会让 state drift(Basics)。
  • 保护 production:用 protect 与 removal: "retain"。API 路径用的 Postgres 是经 sst.Secret 的外部 URL,不是这个 config 创建的 sst.aws.Postgres。
  • 先验证,再在 CI 部署。 GitHub Actions 先跑 install、lint、format check、typecheck、tests 与 build。只有这些 checks 成功后,workflow 才对 dev 或 production 跑 sst deploy。
  • 本地 deploy:dev / deploy scripts 仍适合刻意的一次性 deploys。默认路径是 CI:checks 全绿,再 deploy。
  • SST Console 是另一种取得 git-push autodeploy 的方式。本站用 GitHub Actions 做 verify-then-sst deploy 序列。

Tenant isolation 与 SaaS 边界设计是另一个问题——见 用 Hono、Better Auth、Drizzle 与 Postgres RLS 打造 Multi-Tenant 后端。



9. Mental Model

text
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 …

SST 让 AWS infrastructure 成为可 review 的 TypeScript,并放在应用旁边。当问题是组织级 platform ownership 时,Terraform、CDK 或 Pulumi 仍然更合适。目前的 component 与 CLI 细节,从 SST docs 开始。关于 Lambda 或 Fargate 上的 Hono API,继续看 用 Hono、Drizzle、Zod OpenAPI 与 SST 打造 Backend APIs。


Recap Q&A

阅读下一篇笔记
SOLID 作为变更隔离