跳至主要內容

在 AWS 上交付全端應用時,最好不要把 AWS Console 當成真相來源。Frontendsecretsdomainsstage 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 故事很容易混在一起,所以這裡分開處理:

  1. 目前已部署sst.config.ts 只 import Next.js / OpenNext stack。
  2. 已在 repo 裡定義,但尚未 import — container API infra(VpcClusterService + Router)放在 packages/infra,並在 run() 裡保持註解,直到該 stack 需要部署。
  3. 其他地方的相關模式 — 用 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):

  1. 較高階的 components,這裡用到:sst.aws.Nextjssst.Secret,以及(當 API module 接上時)sst.aws.Servicesst.aws.Routersst.aws.Vpcsst.aws.Cluster
  2. Resource linking,讓 runtime 程式碼透過 typed Resource SDK 讀取基礎設施,而不是 hardcoded names 與 ARNs。
  3. sst dev 作為統一的本地環境:infra watcher、來自同一 CLI 的 frontend(以及之後的 container)dev processes,再加上當 stage 裡有 Lambda handlers 時的 Live

在本站,這對應到選定 AWS region 裡經 OpenNext 部署的 Next.js、以 stage 為基礎的 domains(example.comdev.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。

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)。

當 platform team 已經用 Terraform 或 OpenTofu 擁有 shared networking、當 Pulumi 或 Terraform 是團隊多雲 control planes 的語言,或當 CloudFormation-native CDK construct libraries 是招聘與 review 表面時,SST 並不適合當唯一答案。那些情況下,SST 仍可很好地做 app delivery,而組織級 IaC 留在團隊已有槓桿的地方。



3. 專案形狀:sst.config.tspackages/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 system

Config 檔案定義 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 secrets
  • domain / edge — apex domain 與非 prod 在 edge 的 Basic Auth
  • api / 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: 0edge 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 時,路徑是 VpcClusterService,並以 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.Functionsst.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 副本。本站實務上的對應大致如下:

StagePurposeDomain pattern
local / personal日常 sst devlocal Next URL;該 stage 的 cloud resources
devShared previewdev.example.com
productionLive siteexample.com

Production 受保護。意外的 sst remove 不應抹掉關鍵資料,所以 config 在 production 使用 protectremoval: "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 production

Credentials 留在本地 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,日常最有價值的部分是:

  1. 部署 infrastructure 變更的 watcher。
  2. Next.js dev block——本地 Next,連結到該 stage 已部署的 resources。
  3. 之後當 API module 被 import 時,container dev command,以及可選的 tunnel 連到 VPC resources。

Live——AWS 裡的 stub Lambdas 代理回本機——對 Lambda-heavy stages 最重要,包括 backend APIs 筆記裡的 Function / API Gateway 模式。當本站只 import OpenNext 時它較不核心,不過 OpenNext server paths 依部署形狀仍可能在底層用到 Lambda。

sst dev 應放在 personallocal stage。Shared devproductionsst 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:用 protectremoval: "retain",避免意外 production remove 抹掉 SST-managed stateful resources。API 路徑用的 Postgres 是經 sst.Secret 的外部 URL,不是這個 config 建立的 sst.aws.Postgres database。
  • 先驗證,再在 CI 部署。 GitHub Actions 先跑 install、lint、format check、typecheck、tests 與 build。只有這些 checks 成功後,workflow 才對目標 stage(devproduction)跑 sst deploy。本地 deploy:dev / deploy scripts 仍適合刻意的一次性 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 保持精簡:

  1. 改 infra TypeScript 或 application code。
  2. 在 personal stage 用 sst dev 迭代。
  3. 開 pull request;GitHub Actions 跑 checks、tests 與 build。
  4. 成功後,CI 對目標 stage(devproduction)跑 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 開始。