Skip to main content

Self-Hosting Next.js SaaS with SST and OpenNext 2026

·StarterPick Team
nextjssstopennextawsself-hosting

Self-Hosting Next.js SaaS with SST and OpenNext in 2026

TL;DR

Vercel is excellent for Next.js — but at scale, it's expensive. A SaaS app doing $20K MRR easily spends $1,500–$3,000/month on Vercel Pro features (bandwidth, function invocations, edge middleware executions). SST v3 + OpenNext is the most mature Vercel alternative for self-hosting Next.js on AWS, giving you 80-95% cost savings at scale while keeping the full Next.js feature set: App Router, Server Actions, Server Components, Edge Middleware, Incremental Static Regeneration. In 2026, SST v3 is production-ready, and a growing number of SaaS boilerplates ship with SST configurations out of the box.

Key Takeaways

  • OpenNext is the open-source adapter that makes Next.js deployable to any infrastructure — it handles SSR, ISR, image optimization, and middleware correctly outside of Vercel
  • SST v3 uses Pulumi under the hood instead of CDK, is significantly faster, and has dramatically better developer experience than v2
  • Cost comparison: Vercel Pro at 10M function invocations/month costs ~$1,700; the equivalent AWS Lambda workload costs ~$20
  • sst dev provides a local development environment that mirrors AWS — Lambda functions run locally with live reload
  • Not a drop-in replacement — SST requires AWS account setup, IAM configuration, and ops knowledge; Vercel is still simpler for early-stage apps
  • Which boilerplates support SST: Supastarter, T3 Turbo, and Bedrock all have community SST configurations; ShipFast users have written migration guides

The Vercel Lock-In Problem

Vercel has made Next.js development excellent — their CI/CD, preview deployments, analytics, and edge network are genuinely best-in-class. The problem is pricing at scale.

Vercel Pricing Reality

PlanCostWhat You Get
HobbyFree100GB bandwidth, no commercial use
Pro$20/user/month1TB bandwidth, then $0.40/GB
EnterpriseCustom ($$$$)Custom limits, SLA

The hidden costs at scale:

  • Bandwidth: $0.40/GB over 1TB. A SaaS serving 100K users can easily hit 5TB/month = $1,600/month just in bandwidth
  • Function invocations: Serverless functions cost per invocation above included limits
  • Edge Middleware: Each middleware execution on Vercel's edge network is billed separately
  • Image optimization: On-demand image optimization is billable per transformation

At $50K MRR, Vercel bills of $2,000-$5,000/month are common. That's 4-10% of revenue going to hosting.

The Self-Hosting Economics

On AWS with SST:

  • Lambda: $0.20 per 1M invocations (vs Vercel's per-invocation charges)
  • CloudFront (CDN): $0.0085/GB for first 10TB (vs Vercel's $0.40/GB overage)
  • S3 (static assets): $0.023/GB storage
  • Typical cost at 10M invocations/month: $15-40/month

The gap is dramatic — but the operational complexity is real. You're trading Vercel's zero-ops experience for significant savings and full control.


OpenNext: The Adapter Layer

OpenNext is an open-source project that bridges the gap between Next.js and non-Vercel infrastructure. It handles:

  • SSR — Server Components and Server Actions on Lambda
  • ISR — Incremental Static Regeneration using a combination of S3 + Lambda + ElastiCache/DynamoDB for the revalidation cache
  • Image Optimizationnext/image optimization running on Lambda
  • Edge Middlewaremiddleware.ts running on CloudFront Functions (at the edge, globally)
  • Static Files — Uploaded to S3, served via CloudFront

Without OpenNext, deploying Next.js to AWS requires manually implementing all of these — weeks of infrastructure work. OpenNext packages it correctly.


SST v3: Infrastructure as Code for Next.js

SST v3 is the Infrastructure as Code framework that orchestrates all the AWS resources OpenNext needs. It uses TypeScript for infrastructure definitions.

Setting Up SST in an Existing Next.js Project

cd my-nextjs-app
npx sst@latest init
# SST detects it's a Next.js project and suggests NextjsSite

sst.config.ts (generated):

/// <reference path="./.sst/platform/config.d.ts" />

export default $config({
  app(input) {
    return {
      name: 'my-saas',
      removal: input?.stage === 'production' ? 'retain' : 'remove',
      home: 'aws',
    }
  },

  async run() {
    // PostgreSQL via RDS Aurora Serverless v2
    const database = new sst.aws.Aurora('Database', {
      engine: 'postgresql',
      scaling: { min: '0.5 ACU', max: '8 ACU' },  // Scale to zero when idle
    })

    // Redis for session storage and caching
    const redis = new sst.aws.Redis('Redis', {
      cluster: false,  // Single node for dev; true for production HA
    })

    // S3 for user file uploads
    const uploads = new sst.aws.Bucket('Uploads', {
      access: 'public',
    })

    // The Next.js app itself
    const web = new sst.aws.Nextjs('Web', {
      link: [database, redis, uploads],
      domain: {
        name: 'app.myapp.com',
        dns: sst.aws.dns(),  // Route53
      },
      environment: {
        DATABASE_URL: database.url,
        REDIS_URL: redis.url,
        NEXTAUTH_SECRET: process.env.NEXTAUTH_SECRET!,
        STRIPE_SECRET_KEY: process.env.STRIPE_SECRET_KEY!,
      },
    })

    return {
      url: web.url,
    }
  },
})

SST Development Experience

The sst dev command is the killer feature — it proxies AWS services locally with live reload:

npx sst dev
# ✓ Creates a dev stage in AWS (free until resources execute)
# ✓ Lambda functions run locally (not on AWS)
# ✓ S3, DynamoDB, RDS connect to real AWS dev resources
# ✓ Environment variables from AWS secrets injected automatically
# ✓ Hot reload on code changes

Unlike local Docker setups, sst dev gives you the exact production AWS environment locally — no more "works locally, breaks in production" surprises.


Environment Variables and Secrets Management

SST v3 integrates with AWS Secrets Manager for production secrets:

// sst.config.ts — reference secrets from AWS Secrets Manager
const stripeSecret = new sst.Secret('StripeSecretKey')
const nextauthSecret = new sst.Secret('NextAuthSecret')

const web = new sst.aws.Nextjs('Web', {
  link: [database, redis, stripeSecret, nextauthSecret],
  // SST injects linked secrets as environment variables automatically
})
# Set secrets in AWS Secrets Manager via SST CLI
npx sst secret set StripeSecretKey sk_live_... --stage production
npx sst secret set NextAuthSecret some_long_random_string --stage production

# List all secrets
npx sst secret list --stage production

The linked secrets are injected as environment variables at Lambda startup — no code changes required in your Next.js app. process.env.STRIPE_SECRET_KEY just works.


GitHub Actions CI/CD

SST deploys via the standard GitHub Actions pattern:

# .github/workflows/deploy.yml
name: Deploy to Production

on:
  push:
    branches: [main]

permissions:
  id-token: write  # Required for OIDC authentication with AWS
  contents: read

jobs:
  deploy:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v4

      - uses: actions/setup-node@v4
        with:
          node-version: '22'
          cache: 'npm'

      - run: npm ci

      - name: Configure AWS credentials (OIDC  no long-lived keys needed)
        uses: aws-actions/configure-aws-credentials@v4
        with:
          role-to-assume: arn:aws:iam::${{ secrets.AWS_ACCOUNT_ID }}:role/GitHubDeployRole
          aws-region: us-east-1

      - name: Deploy to production
        run: npx sst deploy --stage production
        env:
          NODE_ENV: production

  # Preview deployments for PRs
  preview:
    runs-on: ubuntu-latest
    if: github.event_name == 'pull_request'
    steps:
      - uses: actions/checkout@v4
      - uses: aws-actions/configure-aws-credentials@v4
        with:
          role-to-assume: arn:aws:iam::${{ secrets.AWS_ACCOUNT_ID }}:role/GitHubDeployRole
          aws-region: us-east-1
      - run: npm ci
      - run: npx sst deploy --stage pr-${{ github.event.pull_request.number }}

With this setup:

  • Every push to main deploys to production
  • Every PR gets its own isolated preview environment with a unique URL
  • No long-lived AWS credentials in GitHub secrets — OIDC role assumption is secure

Configuring IAM Role for GitHub OIDC

# Create the IAM role that GitHub Actions can assume
aws iam create-role \
  --role-name GitHubDeployRole \
  --assume-role-policy-document file://trust-policy.json

# trust-policy.json
{
  "Version": "2012-10-17",
  "Statement": [{
    "Effect": "Allow",
    "Principal": { "Federated": "arn:aws:iam::ACCOUNT_ID:oidc-provider/token.actions.githubusercontent.com" },
    "Action": "sts:AssumeRoleWithWebIdentity",
    "Condition": {
      "StringLike": { "token.actions.githubusercontent.com:sub": "repo:YOUR_ORG/YOUR_REPO:*" }
    }
  }]
}

ISR (Incremental Static Regeneration) with OpenNext

ISR is one of the most complex Next.js features to replicate outside Vercel. OpenNext handles it with a combination of S3 (serving cached pages) and Lambda (revalidating on demand):

// In your Next.js page
export async function generateMetadata({ params }: Props) {
  const product = await db.products.findById(params.id)
  return { title: product.name }
}

// ISR: revalidate this page every 60 seconds
export const revalidate = 60

export default async function ProductPage({ params }: Props) {
  const product = await db.products.findById(params.id)
  return <ProductView product={product} />
}

OpenNext stores the ISR cache in S3 and uses a Lambda@Edge function to serve pages from the cache or trigger revalidation. The behavior is functionally identical to Vercel's ISR.

On-demand revalidation also works:

// In a Server Action or API route
import { revalidatePath } from 'next/cache'

export async function updateProduct(id: string, data: UpdateProductInput) {
  await db.products.update(id, data)
  revalidatePath(`/products/${id}`)  // Works with OpenNext/SST
}

Deploying to Production

# First deploy (creates all infrastructure)
npx sst deploy --stage production

# Subsequent deploys (only changes)
npx sst deploy --stage production

# Preview deployments per PR (like Vercel's preview URLs)
npx sst deploy --stage pr-123

SST creates separate "stages" for dev, staging, and production — each stage is fully isolated infrastructure. This gives you Vercel-style preview environments on AWS.

Estimated AWS Costs for a Real SaaS

For a SaaS with 5,000 active users, 500K page views/month:

ResourceMonthly Cost
Lambda (SSR functions)~$5
CloudFront (CDN)~$12
S3 (static assets + uploads)~$3
RDS Aurora Serverless (scales to zero nights/weekends)~$15
ElastiCache Redis (t3.micro)~$13
Route53 + ACM~$1
Total~$49/month

At this scale, Vercel Pro would cost $20-100/month (depending on team size and function usage). The savings are modest at 5K users but compound dramatically at 50K+ users.


Which Boilerplates Support SST

Most boilerplates are Vercel-first but can be adapted to SST:

BoilerplateSST Support
T3 StackCommunity SST guide available
SupastarterHas official SST/AWS deployment docs
BedrockIncludes SST configuration by default
ShipFastCommunity migration guide (unofficial)
MakerkitVercel-first, no official SST support
Next SaaS StarterCommunity SST examples

Bedrock is notable — it's one of the few premium SaaS boilerplates that treats AWS/SST as a first-class deployment target, not an afterthought.


Vercel vs SST: When to Use Each

Use Vercel when:

  • You're pre-product-market-fit — ops simplicity beats cost savings
  • Your team has zero AWS experience — Vercel is genuinely zero-ops
  • You have < 100K monthly active users — Vercel's costs are manageable
  • Speed of development matters more than infrastructure control
  • You need Vercel-specific features (Edge Config, KV, Blob)

Use SST when:

  • You're post-PMF with predictable, growing traffic
  • You already have AWS infrastructure (RDS, Redis, S3)
  • Hosting costs are a meaningful line item (>$500/month)
  • You need compliance features (VPC, private networking, data residency)
  • Your team has the ops bandwidth to maintain AWS infrastructure

Methodology

  • Sources: SST documentation (sst.dev), OpenNext documentation (opennext.js.org), Vercel pricing page, AWS Lambda/CloudFront pricing calculator
  • Versions: SST v3.x, OpenNext v3.x, Next.js 15.x
  • Cost estimates based on AWS us-east-1 pricing, March 2026

Compare Next.js boilerplates with SST support on StarterPick — filter by deployment target and infrastructure options.

Related: Best Next.js Boilerplates 2026 · Vercel vs Railway vs Coolify: Deployment in Boilerplates 2026 · Bedrock Review 2026: Enterprise-Grade SaaS Starter

Comments