SCbty
sstawsnextjsdevopscost-optimization

Deploying a Next.js Portfolio on AWS for Almost Nothing with SST

The Problem with "Free" Hosting

Vercel's hobby tier is free — until it isn't. Bandwidth limits, execution time caps, no commercial use. Netlify, Railway, Render: same story. The free tiers are fine until you want real control, and the paid tiers cost $20+/month for a personal site that gets a few hundred visitors.

That's a bad deal for a portfolio. A portfolio doesn't need always-on servers. It needs to be fast, globally available, and cheap to run. The traffic is bursty at best — maybe someone clicks your LinkedIn link, skims for 30 seconds, and leaves.

The solution: run it on real AWS infrastructure, pay only when it's actually used, and let it scale to zero between visits.

What is SST?

SST (Serverless Stack) is an infrastructure-as-code framework. Version 3 — called "Ion" — is built on Pulumi under the hood. It abstracts the raw AWS complexity into composable, high-level components.

The key component for this use case is sst.aws.Nextjs. Under the hood it uses OpenNext to adapt Next.js for serverless deployment:

  • SSR pages → Lambda functions (invoked per request)
  • Static assets → S3 (served directly)
  • Everything → behind CloudFront (global CDN, caching)
  • TLS → ACM cert, automatically provisioned

The entire infrastructure is defined in a single sst.config.ts file alongside your application code. No Terraform modules, no CloudFormation templates, no separate infra repo.

The Config

Here's the complete sst.config.ts for this portfolio — all 43 lines of it:

/// <reference path="./.sst/platform/config.d.ts" />
 
export default $config({
  app(input) {
    return {
      name: "portfolio",
      // In production: retain resources on redeploy (prevents accidental deletion)
      // In other stages: remove everything on teardown (keeps dev clean)
      removal: input?.stage === "production" ? "retain" : "remove",
      // SST state stored in S3 in your own AWS account
      home: "aws",
      providers: {
        // Deploy infra to ap-south-1 (Mumbai)
        aws: { region: "ap-south-1" },
        // Use Cloudflare for DNS
        cloudflare: true,
      },
    };
  },
  console: {
    autodeploy: {
      // Push to main → auto-deploy to production via SST Console
      target(event) {
        if (
          event.type === "branch" &&
          event.branch === "main" &&
          event.action === "pushed"
        ) {
          return { stage: "production" };
        }
      },
      runner: {
        // CodeBuild runs the deployment
        engine: "codebuild",
      },
    },
  },
  async run() {
    new sst.aws.Nextjs("Portfolio", {
      domain:
        $app.stage === "production"
          ? {
              name: "subhamchakraborty.com",
              aliases: ["www.subhamchakraborty.com"],
              // Cloudflare manages the DNS records
              dns: sst.cloudflare.dns(),
            }
          : undefined,
    });
  },
});

That's it. One file, and SST handles the rest.

What Gets Provisioned

When you run sst deploy, here's what SST actually creates in your AWS account:

  • Lambda function — serves SSR pages and API routes on invocation
  • CloudFront distribution — CDN in front of everything; caches static assets at edge locations globally
  • S3 bucket — stores static assets (JS, CSS, images) and SST's own state
  • ACM certificate — TLS cert, auto-provisioned and auto-renewed in us-east-1 (required by CloudFront)
  • CloudFront Origin Access Control — so S3 only serves traffic through CloudFront
  • Cloudflare DNS recordsA/CNAME records pointing to the CloudFront distribution

The sst.cloudflare.dns() call handles Cloudflare DNS automatically. SST talks to the Cloudflare API directly using your API token, so you don't manually manage DNS records.

The Cost Breakdown

Here's why this is nearly free for a personal portfolio:

Service Free Tier Realistic Usage Estimated Cost
Lambda 1M requests/month ~1,000 req/month $0.00
Lambda compute 400,000 GB-seconds/month Negligible $0.00
CloudFront 1TB transfer/month (12 months) <1GB/month $0.00
S3 5GB storage, 20K GET/month <100MB, minimal GETs $0.00
ACM Always free 1 cert $0.00
Total $0–$1/month

The Lambda free tier resets monthly and never expires. CloudFront's free tier applies for the first 12 months; after that, you're looking at $0.0085/GB transferred — at a few GB/month, that's cents.

The only real cost is the domain name: ~$12/year from your registrar.

Compare that to alternatives:

Option Monthly Cost Control
Vercel Pro $20 Limited
Netlify Pro $19 Limited
DigitalOcean Droplet $6–12 Full (but always-on)
SST on AWS $0–1 Full

Benefits

True pay-per-use. Lambda bills per invocation and per 100ms of compute time. When no one's visiting the site, you pay nothing. There's no idle server burning compute 24/7.

Global CDN out of the box. CloudFront has 400+ edge locations. Static assets (the majority of a Next.js site) are cached globally and served from the nearest PoP. No separate CDN setup required.

Auto-scaling. Lambda scales from zero to thousands of concurrent invocations automatically. If your portfolio goes viral after a conference talk, it handles it without intervention.

Full AWS ecosystem. Because the infra is in your own account, adding other AWS services is straightforward. Need a contact form with SQS? A view counter with DynamoDB? You can add them in the same sst.config.ts without stitching together multiple platforms.

CI/CD built in. The autodeploy block in the config connects to SST Console. Push to main, and CodeBuild picks it up, runs sst deploy, and updates the production stack. No separate GitHub Actions workflow needed.

No PaaS lock-in. You own the AWS account. If SST ever disappears, the underlying CloudFormation/Pulumi resources remain. You're not tied to a company's continued existence or pricing decisions.

Trade-offs

Being honest: this setup has real downsides.

Cold starts. Lambda functions have cold start latency — typically 200–500ms on the first request after a period of inactivity. For a portfolio, this is acceptable; a visitor might see a slightly slower first load, but subsequent requests are warm. For a latency-sensitive production API, this would matter more.

SST learning curve. SST abstracts AWS, but it doesn't hide it. When something goes wrong — a CloudFront cache behaving unexpectedly, a Lambda permissions issue, a failed deploy mid-way — you need enough AWS knowledge to debug it. SST's error messages aren't always beginner-friendly.

SST Console dependency for autodeploy. The free tier of SST Console is sufficient for this, but it's another external service in the deploy chain. If SST Console has an outage, autodeploy breaks. You can always fall back to running sst deploy manually, but it's a dependency worth knowing about.

CodeBuild cold start adds deploy time. Vercel deploys in ~30 seconds. A CodeBuild runner provisioning from cold adds 2–3 minutes to every deploy. Not a dealbreaker, but noticeable if you're used to near-instant CI.

State management risk. SST stores its deployment state in an S3 bucket in your account. If that state gets corrupted — which can happen on a failed mid-deploy — recovery requires manual intervention. Enabling versioning on the SST state bucket is a good safeguard.

Overkill for purely static sites. This portfolio has dynamic content (the blog uses SSR for markdown processing), so Lambda is justified. If your portfolio is 100% static HTML, deploying directly to S3 + CloudFront, or using Cloudflare Pages, would be simpler with identical cost.

Is It Worth It?

Depends on what you're optimizing for.

If you're a backend or DevOps engineer who wants AWS infrastructure as part of your daily workflow — yes. You get real AWS experience, CI/CD that you understand end-to-end, and a near-zero bill. The config is 43 lines and once it's working, it just works.

If you just want a portfolio live in an afternoon — probably not. Vercel's free tier is fine, the DX is better, and the trade-offs are irrelevant at zero traffic. You can always migrate to SST later.

For me, the setup made sense: I work with AWS professionally, I wanted the portfolio infra to reflect that, and I didn't want to pay $20/month for a hobby site. The SST config took an afternoon to get right. Since then, it's been on autopilot.


The SST docs are at sst.dev — worth reading if you're considering this approach. The sst.aws.Nextjs component docs in particular cover the full set of configuration options I didn't use here (image optimization, custom Lambda memory/timeout, warming, etc.).