No servers in my basement — and none in production either
elangodev.com runs entirely on managed services: Vercel for Next.js hosting, Supabase for Postgres and auth, AWS S3 for media, external push endpoints for notifications. I do not SSH into a box to deploy. git push triggers builds; API routes become regional functions; database connections flow through Supabase poolers. That architecture let me ship a technical blog, blog and handbook pages, and a client chat inbox as one solo developer.
Serverless here means event-driven compute billed per invocation, not zero servers globally — but I outsource capacity planning to vendors who specialize in it. My job is idempotent handlers, clear environment secrets, and database schemas that survive traffic spikes when a blog post gets shared widely.
Request flow on a typical visit
A visitor hits elangodev.com on Vercel's CDN. Static assets cache at the edge. app/page.jsx executes on a serverless Node runtime, calls createSimpleClient(), returns HTML with profile data. Client hydration loads Framer Motion and ProfileContext. Analytics waits for consent then inserts via Supabase REST from the browser. No long-lived socket unless chat realtime subscribes temporarily.
When they open a blog article, the server component fetches the published post by slug and streams HTML with JSON-LD. Interactive widgets hydrate client-side; persistence is a few Postgres writes for analytics. Peak load splits across CDN cache, function concurrency, and connection pool — not one monolithic Node process I must scale manually.
API routes as micro-endpoints
I decompose backend work into route handlers instead of a single Express app. /api/chat/send validates auth, inserts messages, fans out web push. /api/drop signs S3 URLs. Blog content APIs serve sanitized HTML for syndication. Each file exports HTTP verbs with focused responsibility — easier to reason about than a sprawling router module.
// app/api/chat/send/route.js — serverless POST handler
export async function POST(request) {
const user = await resolveUser(request);
if (!user) return unauthorized();
await supabaseAdmin.from("chat_messages").insert({ ... });
await dispatchPushNotifications(receiver_id, payload);
return new Response(JSON.stringify({ success: true }), { status: 201 });
}Cold starts happen — typically hundreds of milliseconds on chat sends. I accept that for infrequent admin replies versus paying for always-on containers. For read-heavy blog pages, revalidate = 3600 amortizes function work across many CDN hits.
Homepage revalidation as a serverless cache strategy
export const revalidate = 3600 on app/page.jsx means Vercel regenerates my portfolio at most once per hour after the first request in a window. That ISR pattern reduces Supabase reads during traffic spikes — hiring managers refreshing my link after a conference talk hit CDN-cached HTML instead of cold database joins every time. When I update experience rows in the dashboard, the next visitor after revalidation sees fresh data without me redeploying code.
export const revalidate = 3600;
export default async function Page() {
const supabase = createSimpleClient();
const { data: userData } = await supabase
.from("users")
.select("*, experience (*), education (*), skills (*)")
.eq("full_name", "elango")
.maybeSingle();
return <HomeContent initialProfile={userData} />;
}Supabase as the shared data plane
One Postgres project holds users, posts, analytics, chat, and push subscriptions. RLS enforces tenant boundaries at the row level. Server routes use service role only after explicit auth checks; public pages use anon policies for published content.
Serverless functions are connection-hungry if each opens raw Postgres TCP. Supabase client libraries speak HTTP to PostgREST and GoTrue — better fit for ephemeral Vercel invocations. Long dashboard sessions in the browser reuse a single client instance; burst traffic during API routes scales with Supabase pooler capacity, not my config files.
Scaling editorial traffic without pre-provisioning
Blog posts and handbook pages create bursty traffic — share a link, readers arrive from Search or social at once. Static marketing pages cache well. Dynamic slug routes hit the database for article bodies and related posts. I index slug and status columns so lookups stay millisecond-scale even when the posts table grows.
Images may reference S3 URLs generated by the drop pipeline — uploads never touch Vercel memory. Media bandwidth scales on AWS, not function egress. That separation is deliberate serverless hygiene: compute for logic, object storage for bytes.
Observability from managed dashboards
Vercel Analytics and Speed Insights ship from layout.jsx with one import each. Supabase logs slow queries. I do not run Prometheus on a VPS. When chat push fails, function logs show webpush errors; stale subscriptions delete themselves on 410 responses.
Environment variables partition secrets: VAPID keys, AWS credentials, service role keys never reach NEXT_PUBLIC_ prefixes. Preview deployments on Vercel branches get scoped env copies so I test serverless paths before merging.
Tradeoffs I accept openly
Long-running jobs — bulk seed scripts, heavy image batch processing — run locally or CI, not inside request handlers. WebSocket-heavy games are a poor fit; my realtime needs are Postgres changes and push notifications, not 60fps server authoritative state.
Vendor lock-in is real. Migrating off Supabase would hurt, but the Postgres underneath is portable SQL. Next.js route handlers map to other hosts with effort. I trade portability for velocity as an independent builder.
Architecture sketch for similar portfolios
Static and ISR pages on CDN. Server Components for SEO data. Client islands for interaction. API routes for secrets. Postgres with RLS. Object storage for files. Push via standardized web APIs. No cron server — use Vercel cron or Supabase pg_cron when scheduled tasks appear.
Serverless architecture on elangodev.com is how I punch above my team size. Blog and handbook traffic scale globally because functions and poolers absorb spikes while I sleep. That is the scaling story worth telling — not abstract infinite scale diagrams, but the actual routes and tables I depend on every day.




