From Vercel to AWS, part 1: the $50/month stack
← Back
July 2, 2026Architecture9 min read

From Vercel to AWS, part 1: the $50/month stack

Published July 2, 20269 min read

Three engineers. Twelve weeks of runway. A B2B SaaS that needed to be in front of paying customers before the money ran out. The CTO spent one afternoon on infrastructure. Vercel for the frontend and API routes. Supabase for the database, auth, and file storage. Railway for the background worker that processed reports. Upstash for the job queue. Resend for transactional email. Total monthly cost at launch: $47. Three months and 200 paying customers later, the stack had not changed.

This is not a story about cutting corners. It is a story about matching your infrastructure complexity to your actual problem. AWS can run anything. It can also consume three weeks of engineering time before you write a single line of product code. At twelve weeks of runway, that trade is not available to you.

This is part 1 of a series on how small companies manage their services today, when to outgrow the modern dev platforms, and how to migrate without stopping product work. Part 1 covers the stack itself: what to use, what each service actually handles, and what you are deferring rather than abandoning.

The stack, component by component

The modern small-company stack has converged around a set of services that each eliminate an entire infrastructure category. You are not choosing between hosted services and self-managed — you are choosing between spending engineering time on DevOps or on product. At fewer than ten engineers, the answer is almost always: do not spend time on DevOps.

Vercel handles your frontend and your API layer. Next.js on Vercel gives you edge-deployed static assets, server-side rendering, and API routes that run as serverless functions — all with zero configuration. A deploy takes ninety seconds. Preview URLs for every pull request are automatic. The free tier handles surprising traffic. The Pro tier at $20/month adds team features and higher limits. What you do not get: control over cold start behavior, function runtimes longer than 60 seconds on Pro, and predictable compute for CPU-intensive work.

// A Vercel API route that talks to Supabase — the whole backend in 20 lines
import { createClient } from '@supabase/supabase-js';
import type { NextApiRequest, NextApiResponse } from 'next';

const supabase = createClient(
  process.env.SUPABASE_URL!,
  process.env.SUPABASE_SERVICE_KEY!
);

export default async function handler(req: NextApiRequest, res: NextApiResponse) {
  if (req.method !== 'POST') return res.status(405).end();

  const { data, error } = await supabase
    .from('reports')
    .insert({ user_id: req.body.userId, content: req.body.content })
    .select()
    .single();

  if (error) return res.status(400).json({ error: error.message });
  return res.status(201).json(data);
}

Supabase handles your database, auth, storage, and realtime. Supabase is Postgres with a managed connection pooler (PgBouncer), row-level security, a PostgREST API layer, built-in auth with social logins and magic links, S3-compatible file storage, and realtime subscriptions over websockets. For a three-person team, this eliminates four separate infrastructure concerns that would each require dedicated configuration on AWS.

// Supabase RLS in practice — users only see their own data
-- No application code needed for this security boundary
CREATE POLICY "users can read own reports"
  ON reports
  FOR SELECT
  USING (auth.uid() = user_id);

CREATE POLICY "users can insert own reports"
  ON reports
  FOR INSERT
  WITH CHECK (auth.uid() = user_id);

-- The query runs in your API route with the user's JWT
const { data } = await supabase
  .from('reports')
  .select('*');  // RLS enforces the user_id filter automatically

The Supabase free tier includes 500MB database, 5GB storage, 5GB egress, and 50,000 MAU. Pro at $25/month raises these meaningfully: 8GB database, 100GB storage, unlimited auth users. The practical limit that matters first is not storage — it is connection count: 100 connections on free, 500 on Pro.

Railway handles your background workers and scheduled jobs. Anything that cannot run in a 60-second Vercel function lives on Railway: report generation, webhook processing, PDF rendering, email digest jobs. Railway deploys from a Dockerfile or auto-detects your runtime. You pay for actual compute seconds used, not idle time. At low volume, a worker that runs for 30 minutes a day costs under $2/month.

// railway.toml — worker service that processes the Upstash queue
[build]
builder = "dockerfile"
dockerfilePath = "workers/Dockerfile"

[deploy]
startCommand = "node workers/report-processor.js"
restartPolicyType = "on_failure"
restartPolicyMaxRetries = 3

# Environment variables pulled from Railway's dashboard
# UPSTASH_REDIS_URL, SUPABASE_URL, SUPABASE_SERVICE_KEY

Upstash handles your queues and caching. Upstash is serverless Redis, billed per command rather than per hour. A queue that processes 10,000 jobs a month costs about $1. The free tier covers 10,000 commands per day. For a job queue powering your Railway worker, Upstash with the BullMQ client is the straightforward choice.

Resend handles transactional email. Three API lines and a React template. 100 emails per day free, $20/month for 50,000. You do not configure SMTP, manage IP reputation, or handle bounce webhooks in your first six months.

Clerk handles authentication if Supabase auth is not enough. If you need organizations, roles, fine-grained permissions, or enterprise SSO from day one, Clerk at $25/month replaces a multi-month auth implementation. If basic email/social auth is sufficient, Supabase auth is free and integrated.

What this stack costs at different stages

The cost profile of this stack is almost flat from zero to significant traction:

// Monthly cost at different customer counts
// Assumes: Next.js app, 1 background worker, moderate API traffic

Stage: Pre-launch (internal testing)
  Vercel Hobby:   $0
  Supabase Free:  $0
  Railway Hobby:  $5
  Upstash Free:   $0
  Resend Free:    $0
  Total:          $5/month

Stage: Early customers (1-50 users)
  Vercel Pro:     $20
  Supabase Pro:   $25
  Railway Dev:    $5-15
  Upstash Pay-as: $1
  Resend Free:    $0
  Total:          $51-61/month

Stage: Growing (200-500 users, $20-50k ARR)
  Vercel Pro:     $20
  Supabase Pro:   $25 + $5 compute add-on
  Railway:        $20-40
  Upstash:        $10
  Resend Starter: $20
  Total:          $95-120/month

For comparison: a minimal AWS setup — one RDS db.t4g.small, one ECS task, an ALB, and a NAT Gateway — costs roughly $180/month before you account for data transfer, CloudWatch logs, or the engineering time to configure IAM roles and VPCs. The cost difference at this scale is not the interesting part. The opportunity cost is: three to five weeks of an engineer's time that cannot go toward the product.

If you are adding an LLM feature at this stage

The patterns from the agents-in-production series apply here: the LLM call belongs in a background worker, not in a Vercel API route. A Railway worker with Upstash as the queue is the direct equivalent of the queue-based architecture from Part 1 of that series. The 60-second Vercel function timeout makes the inline LLM call pattern worse than it looks in staging. Queue it. The infrastructure is already there.

// Vercel route: fast, enqueues the LLM job
export default async function handler(req: NextApiRequest, res: NextApiResponse) {
  const jobId = crypto.randomUUID();
  await redis.lpush('llm-jobs', JSON.stringify({ jobId, ...req.body }));
  return res.status(202).json({ jobId });
}

// Railway worker: picks up the job, calls the LLM, saves to Supabase
while (true) {
  const raw = await redis.brpop('llm-jobs', 30);
  if (!raw) continue;

  const job = JSON.parse(raw[1]);
  const result = await openai.chat.completions.create({ ... });
  await supabase.from('ai_results').insert({ job_id: job.jobId, result: result.choices[0].message.content });
}

What you are deferring, not skipping

Using this stack is not a technical compromise. It is a sequencing decision. You are deferring: custom Postgres configuration and query tuning below the ORM, fine-grained control over your compute topology, the ability to run in a private VPC, and multi-region failover. None of these matter when your primary risk is not shipping fast enough.

What you are not deferring: reliability. Supabase runs on AWS under the hood, with daily backups, point-in-time recovery on Pro, and a 99.9% uptime SLA. Railway's uptime record is competitive. Vercel's edge network is one of the most reliable content delivery infrastructures available. The stack you are running on is not fragile — it is just someone else's configuration.

Part 2 covers the specific breaking points: the Supabase connection limit at 3 AM, the Vercel cold start that starts showing up in your P99, and the Railway behavior under sustained load that tells you the time has come to look at what is next.

Share this
← All Posts9 min read