How I Host My Portfolio for $0/Month on Cloudflare (And Why I Left Vercel)
TL;DR
I host my Astro portfolio on Cloudflare Workers completely free using static output, getting global edge deployment and zero cold starts without paying Vercel's premium.
Key Takeaways
- Cloudflare Workers free tier: 100K requests/day with zero bandwidth limits beats Vercel's 100GB cap
- V8 isolates mean 5ms cold starts vs 200-500ms for traditional serverless functions
- OpenNext adapter makes Next.js work on edge runtime with minimal config changes
- Edge runtime constraints require prebuild strategy - no filesystem access at runtime
- Global deployment to 330+ cities happens automatically without multi-region setup
I haven’t paid for hosting in two years. Not because I’m using some sketchy free trial, but because my portfolio genuinely costs $0/month to run on Cloudflare Workers.
This isn’t a flex post. It’s the architecture breakdown of how divkix.me runs on Cloudflare’s edge network with Astro 7, why I picked it over Vercel, and the actual constraints you’ll hit. I’ve also built a full-stack app on the same edge platform — but this post is about the simpler static-site case.
The Stack Nobody Tells You About
Here’s what powers this site today:
- Astro 7 (static output, Content Collections for the blog)
- Cloudflare Workers - serves the static build from 330+ edge locations
- Wrangler - Cloudflare’s deployment CLI
I originally built this on Next.js 15 with the OpenNext adapter, but I’ve since migrated to Astro 7. The reason is simple: this is a content site, not an app. Astro builds everything to static HTML, CSS, and JS at build time, and Cloudflare Workers serves those files from the edge. No adapter, no edge-runtime workarounds, no fighting the platform. The earlier Next.js setup worked, but Astro’s static output is a cleaner fit for a portfolio and blog.
Why Not Vercel? (The Real Reasons)
Vercel hosts Next.js perfectly. But here’s why I left.
The bandwidth limit hits in weird ways. 100GB sounds generous until you add images or get any kind of traffic spike. Cloudflare has no bandwidth cap, period.
Vercel owns Next.js. That’s fine for integration, but it means you’re a captive customer. If pricing or terms change, your migration options are limited. Cloudflare Workers runs on open web standards.
Cold starts are the practical difference. Vercel’s serverless functions use containers, 200-500ms on a cold start. Cloudflare Workers use V8 isolates, which are lightweight JS contexts. Cold starts are 5ms. Not 5 seconds. 5 milliseconds.
And global edge is just the default on Cloudflare. Vercel charges extra for it. Workers deploy to 330+ cities automatically with no configuration.
The Free Tier Reality Check
Let’s compare the actual numbers:
| Feature | Cloudflare Workers | Vercel | Netlify |
|---|---|---|---|
| Requests/Day | 100,000 | Unlimited* | Unlimited* |
| Bandwidth | Unlimited | 100GB | 100GB |
| Function Invocations | 100K/day | 100 hours compute | 125K/month |
| Cold Start Time | ~5ms | 200-500ms | 200-500ms |
| Global Edge | Yes (330+ cities) | $20/mo add-on | Paid plans only |
| Overage Cost | $0.50/1M requests | Pay-as-you-go | Pay-as-you-go |
*Vercel/Netlify limit bandwidth, not requests. Hit 100GB and you’re throttled or billed.
For a portfolio or blog, you’ll never hit 100K requests/day unless you’re Hacker News frontpage famous. I average 2-3K requests/day. Not even close.
Edge Runtime Constraints (The Pain Points)
This is the section where I’d warn you about edge runtime constraints — and when I ran the site on Next.js 15 + OpenNext, they were real. No Node.js. No filesystem. No fs.readFileSync(). Those constraints broke a lot of Next.js patterns. Migrating to Astro’s static output sidestepped most of them, because Astro reads your MDX at build time instead of at request time.
The Blog Problem
When the site ran on Next.js, my blog used MDX files read from the filesystem at request time — a pattern that breaks on the edge:
// This DOES NOT WORK on Cloudflare Workers
import fs from 'fs';
import path from 'path';
export function getBlogPosts() {
const files = fs.readdirSync('content/blog');
return files.map(file => {
const content = fs.readFileSync(`content/blog/${file}`);
return parseMDX(content);
});
}
No fs module at runtime. The solution? Prebuild everything.
The Prebuild Pattern
I wrote a build script that runs before deployment:
// scripts/generate-posts-metadata.js
import fs from 'fs';
import path from 'path';
import matter from 'gray-matter';
const postsDir = 'content/blog';
const files = fs.readdirSync(postsDir).filter(f => f.endsWith('.mdx'));
const posts = files.map(filename => {
const content = fs.readFileSync(path.join(postsDir, filename), 'utf8');
const { data } = matter(content);
return {
slug: filename.replace('.mdx', ''),
...data,
readingTime: calculateReadingTime(content)
};
});
fs.writeFileSync('content/blog/posts.json', JSON.stringify(posts, null, 2));
Now at runtime, I just import the JSON:
// lib/content.ts
import postsData from '@/content/blog/posts.json';
export function getAllPosts() {
return postsData; // No filesystem needed
}
This runs at build time with Node.js, outputs static JSON, and the edge runtime only reads JSON. Problem solved.
Wrangler Config Basics
Here’s the minimal wrangler.jsonc config:
{
"name": "divkix-me",
"compatibility_date": "2025-11-24",
"compatibility_flags": [
"nodejs_compat",
"global_fetch_strictly_public"
],
"assets": {
"directory": "./dist"
}
}
nodejs_compatenables some Node.js APIs (Buffer, process.env)global_fetch_strictly_publicenforces standards-compliant fetchassets.directorypoints to Astro’s static build output (dist/)
Astro builds the site to dist/, and Wrangler uploads those static assets to Cloudflare’s edge.
Performance: The Actual Numbers
I ran tests from 5 global locations. Here’s reality:
Homepage (SSG)
- San Francisco: 23ms
- London: 31ms
- Singapore: 28ms
- Mumbai: 35ms
- São Paulo: 42ms
Blog Post (SSG)
- San Francisco: 45ms
- London: 52ms
- Singapore: 48ms
- Mumbai: 61ms
- São Paulo: 58ms
These are total response times, not TTFB. With Astro’s static output served from Cloudflare’s edge, there are no cold starts to worry about — it’s just static files delivered from the nearest POP.
For comparison, my old Vercel setup averaged 80-120ms on dynamic routes because of container cold starts.
Build and Deploy Commands
My package.json scripts:
{
"scripts": {
"prebuild": "node scripts/generate-posts-metadata.js",
"build": "bun run prebuild && astro build",
"preview": "bun run build && wrangler pages dev dist",
"deploy": "bun run build && wrangler pages deploy dist"
}
}
Workflow:
bun run prebuild- generates posts.json from MDXastro build- Astro builds the static site todist/wrangler pages deploy- uploadsdist/to Cloudflare
First deploy took 2 minutes. Updates take 30-45 seconds.
The Honest Downsides
- Debugging Is Harder
Local development uses Node.js. Production uses V8 isolates. Sometimes code works locally but breaks on Workers. You’ll need to test with wrangler pages dev before deploying.
- No Incremental Static Regeneration (ISR)
Astro outputs static files, so there’s no ISR concept — you rebuild and redeploy when content changes. For a portfolio, this doesn’t matter. For a high-traffic blog that updates constantly, you’ll want a static rebuild pipeline or a dynamic host.
- The Next.js + OpenNext Era Had Sharp Edges
My earlier Next.js setup relied on OpenNext, a community-maintained adapter. Updates lagged behind Next.js releases and edge cases popped up. Migrating to Astro eliminated that dependency entirely — static output needs no adapter.
- Limited Node.js APIs
nodejs_compat flag enables some APIs, but not everything. No child processes, no native modules, no complex crypto. Check compatibility before committing.
- Build Times
The prebuild step (generating posts metadata) plus Astro’s static build adds a little time, but it’s still fast. Vercel’s builds are quicker for Next.js because they control the entire stack, but for a static site the difference is negligible.
When You’d Actually Pay
Cloudflare charges after 100K requests/day. Let’s math this out:
- 100K requests/day = 3M requests/month (free)
- Next 10M requests = $5
- 13M requests/month = $5 total
Compare to Vercel Hobby (free) to Pro ($20/mo) jump. No middle ground.
For context, a site getting 13M requests/month is doing 430K requests/day. That’s 180 requests/minute every minute of every day. Your portfolio won’t hit this unless it’s not a portfolio anymore.
The Migration Path
If you’re on Vercel now:
- Create
wrangler.jsoncwithassets.directorypointing at your build output (dist/for Astro) - Set your build command (
astro build, or whatever your framework uses) - If you’re coming from Next.js, audit your code for
fs,path, and other Node.js APIs that don’t work on the edge - Move any filesystem operations to prebuild scripts
- Test locally:
bun run preview - Deploy:
bun run deploy - Add custom domain in Cloudflare dashboard
I migrated from Vercel in 3 hours. The Next.js-to-Astro move came later, but the Cloudflare side stayed the same — just point Wrangler at the new build output.
Should You Do This?
Worth it if you want $0/month with no asterisks, you’re building a portfolio or blog or anything low-traffic, and you’re comfortable with edge runtime constraints. The cold start speed and global edge are genuinely nice, not just marketing.
Probably not worth it if you need ISR, rely on Node.js-specific libraries, want zero-config deployment (Vercel is much easier there), or have a site that makes heavy use of database connections, Workers have connection pooling limits that bite you at scale. (I’ve written a fuller comparison of serverless Workers versus traditional VPS servers if you’re deciding between the two.)
For divkix.me, Cloudflare Workers is perfect. No hosting bills, global performance, and the constraints force better architecture decisions. I prebuild everything anyway. Why not make it official? If you’re weighing this against the rest of your infrastructure, my side-project stack for 2026 shows how this hosting setup fits alongside everything else I ship.
The free tier isn’t a trial. It’s permanent. Cloudflare makes money from enterprises, not personal portfolios. Use that to your advantage.
Resources:
- Astro Documentation
- Cloudflare Workers Docs
- My Portfolio Source Code (see wrangler.jsonc and prebuild scripts)
Related posts:
- When to Use a VPS vs Cloudflare Workers in 2026 — real cost comparison from running projects on both architectures
The hosting bill that doesn’t exist? That’s not a hack. That’s just picking the right tool for the job.
Frequently Asked Questions
Can I use Next.js App Router with Cloudflare Workers?
Yes, but with constraints. Server components work fine, but you can't use Node.js APIs like 'fs' at runtime. OpenNext handles the adaptation automatically.
What happens when I exceed 100K requests/day?
Cloudflare charges $0.50 per million requests after that. For a portfolio, you'd need viral traffic to hit this. I've never exceeded it.
Do I need to change my Next.js code significantly?
Minimal changes. Main constraint is no filesystem access at runtime. Use prebuild scripts to generate JSON from MDX/markdown instead of reading files dynamically.
How does performance compare to Vercel?
Equal or better. Both use edge networks, but Cloudflare's V8 isolates have faster cold starts than Vercel's serverless functions. Real-world: 20-50ms response times globally.
Related Posts
I Spent $600 Testing AI Coding Tools: Claude Code vs Cursor vs Copilot (2026 Results)
I've spent $50+/month on AI coding tools for a year. Here's what actually works, what's overhyped, and when to use each tool (including free local LLMs).
I Built a Full-Stack AI App on Cloudflare Workers With D1, Durable Objects, and Queues — Here's What Actually Worked
Upload a PDF resume, get a live web portfolio. I built clickfolio.me entirely on Cloudflare's edge stack, D1 for data, R2 for files, Queues for async processing, Durable Objects for real-time WebSockets, and Gemini for AI parsing. Here's every technical decision, including the ones I regret.
PickMyClass: Never Miss Your Dream Class Again
How I built a class notification system that watches for open seats and instructor updates, so students never miss their dream classes due to constant refreshing.
Divanshu Chauhan (@divkix)
Software Engineer & MS CS @ Arizona State University. Currently SWE Intern @ Cloudflare. Based in Tempe, Arizona, USA.
Expertise: Cloudflare Workers, Next.js, Edge Computing, Serverless. More about divkix