Charlotte, NC
BlogAugust 4, 2026

What SST v4 Gets Right for Full-Stack AWS Applications

Blake McCarn
What SST v4 Gets Right for Full-Stack AWS Applications
Full-stack AWS applications have an awkward boundary. The application code wants a fast local feedback loop. The infrastructure wants deliberate changes, durable state, and enough guardrails that a frontend edit cannot accidentally remove a database. It is easy to optimize for one side and make the other miserable. I have now used SST across two very different applications: this portfolio and blog, and the production Charlotte Wire & Cable site. The first is a focused Next.js deployment. The second adds DynamoDB, S3, Cognito, SES, a customer-facing catalog, and an admin application. That is enough time with the framework to have a more useful opinion than “SST makes AWS easy.” It does make some parts of full-stack AWS development much better. It also has a clear boundary where I would still reach for Terraform or CDK. SST is a full-stack application framework that defines infrastructure in TypeScript. Its higher-level components cover common application resources, while the underlying Pulumi and Terraform provider ecosystem exposes lower-level resources across AWS, Cloudflare, and more than 150 other providers. The version history matters because it is easy to repeat an outdated description. SST moved away from CloudFormation and CDK in v3. Version 4 continues the Pulumi-based architecture and updates the underlying AWS provider. It is not a CDK wrapper. That distinction changes the shape of an SST project. There are no CDK stacks in the current model. The application starts with one sst.config.ts, and larger projects can split resource definitions into an infra/ directory without creating a second infrastructure codebase. I like that model for applications where the same small team owns the frontend, backend, and AWS resources. The infrastructure stays close to the code that consumes it, and TypeScript carries values between components without a separate output-file choreography. This site currently runs Next.js 16.2.9 with SST 4.17.0. The deployment uses the Nextjs component, which builds the application through OpenNext and provisions the AWS resources needed to run it behind CloudFront. The core infrastructure really is compact: That file captures several decisions that would otherwise be spread across a hosting dashboard, DNS provider, and deployment notes. Production resources are retained and protected. Only the production stage receives the real domain. Cloudflare provider behavior is pinned. The OpenNext adapter is also pinned because Next.js and the deployment adapter need to move together. The application secrets are declared once and linked to the site. The provider credentials have a separate lifecycle. The deployment wrapper reads the Cloudflare API token and account ID from encrypted SSM parameters and exposes them only to the SST process. They are deployment credentials, not application secrets. Keeping that distinction explicit is more accurate than saying “everything is in SSM” or “SST handles every secret.” Charlotte Wire is where SST stopped being a convenient hosting wrapper and became the infrastructure layer for a complete application. The current project uses Next.js 15.5.12 and pins SST 4.1.0. Its infrastructure code defines:
  • Two DynamoDB tables for product and team data
  • An S3 bucket for product documents and team images
  • A Cognito user pool and application client for administration
  • An SES identity on email-capable stages
  • A Next.js application with DynamoDB, S3, Cognito, and SES permissions
  • A custom domain and certificate on the stages designated for live traffic
The configuration is still TypeScript, but it is no longer one giant file. sst.config.ts owns deployment policy and the application component. Files under infra/ define the database, storage, authentication, and email resources. That separation has held up well. The application and infrastructure remain in one repository and one deployment graph, while each resource group stays small enough to review. SST does not force everything into one file. It gives the project one entry point. The stage policy is also more explicit than the simple portfolio setup. Long-lived stages retain data. Disposable stages use removal behavior intended for short-lived resources. Only email-capable stages create SES resources and receive the custom domain. The stage name itself has no magic; it is just a string that the application turns into policy. That last point is important. SST makes stages easy to create. It does not decide which data is safe to delete. The developer loop is the part of SST I notice most often. According to the current sst dev documentation, dev mode deploys most infrastructure resources, runs Function components through SST Live, starts frontend processes, loads linked resources into their environment, and watches sst.config.ts for infrastructure changes. For a Nextjs component, the application itself is not deployed during sst dev. SST starts the normal local development command, which defaults to npm run dev. That means the accurate mental model is: This is better than the old description that said every request traveled through CloudFront to a local Lambda stub. SST Live still matters for explicit Function components, but the Next.js development path is a local frontend process connected to deployed dependencies. When I need a real deployed preview, I use a named stage: The CLI uses the stage to isolate resource names, and the deployed Nextjs component returns an AWS URL when no custom domain is configured. That is the URL I can hand to someone else for review. It incurs the normal cost of whatever AWS resources the stage creates, even if a low-traffic serverless stage is inexpensive. SST's secret model is good, but the implementation is not “put values in Parameter Store.” The current Secret component documentation says secret values are encrypted and stored in an S3 bucket in the AWS account. Values used by the infrastructure configuration are encrypted in state. Linked runtime secrets are encrypted in the function bundle and decrypted by the SST SDK when the function starts. Secrets are scoped by app and stage: That has worked well for the two application values on this site and for the Turnstile secret used by Charlotte Wire. The failure mode is also clear: a required secret that has not been set should stop the deployment instead of quietly producing a broken application. SST state also deserves a more exact description. The state file is generated locally, then backed up to the configured home provider. With home: "aws", SST stores state in an S3 bucket and keeps the encryption passphrase in SSM Parameter Store. Completed state snapshots form the recovery history. That is a real remote backup model, but it is not a reason to treat the cloud console as a second source of truth. Manual resource changes can leave state out of sync, and SST documents sst refresh as the way to reconcile that drift. SST removes a lot of glue code by composing several projects: SST components, Pulumi providers, OpenNext, the application framework, and AWS itself. The trade-off is that compatibility between those layers becomes part of the deployment surface. This portfolio is a good example. It pins SST 4.17.0, the Cloudflare provider at 6.17.0, and OpenNext at 4.0.3. The OpenNext pin is there because an older adapter path broke image optimization after a Next.js update. The application code was fine. The integration between framework versions was not. Charlotte Wire makes a different choice and pins an older SST release alongside Next.js 15. That is not automatically a problem. It means upgrades need to be treated as infrastructure changes: read the release notes, run the test suite, build the application, inspect the diff, deploy a non-live stage, and only then touch the live stage. The same caution applies to provider changes. SST pins installed provider versions, and its docs require an explicit config change plus sst install to move them. That predictability is helpful. It still leaves the testing responsibility with the application owner. I would choose SST again when these conditions are true:
  • The product is a full-stack application running primarily on AWS.
  • The same team owns the application and its supporting infrastructure.
  • TypeScript is already the working language.
  • The architecture maps reasonably well to SST components or ordinary provider resources.
  • Fast local application work and repeatable stage deployments both matter.
I would not use it as the default answer for every infrastructure problem. My multi-account AWS foundation, network topology, and homelab lifecycle boundaries belong in CDK or Terraform because those systems outlive any one application and have different review and state requirements. I wrote about that separation in Terraforming My Homelab: Rebuild the Shells, Protect the Data. Vercel is also still the simpler choice when the entire requirement is “host this Next.js site.” SST earns its place when the application needs AWS resources, IAM relationships, stage-aware infrastructure, or a deployment model that stays in the AWS account.
  • SST v4 for application infrastructure and stage-aware deployments
  • Pulumi and Terraform providers for the underlying cloud resources
  • OpenNext for packaging Next.js applications for AWS
  • AWS Lambda, S3, CloudFront, DynamoDB, Cognito, and SES across the two projects
  • Cloudflare for portfolio DNS management
  • TypeScript and Next.js for the application and infrastructure code
SST v4 is not the infrastructure framework I would recommend to every full-stack engineer. It is the one I would put on the shortlist for a full-stack AWS application owned by a small team. That narrower recommendation is more useful. Across two real deployments, SST has made the application and infrastructure feel like one system without pretending every AWS problem is an application problem.
Share this post: