A 5-Month Side Project on Free Tiers: $0 Cloud Bill Beyond a Domain
Want to start a side hustle with small products? Do you really need to buy a cloud server? Is it going to be a significant monthly expense?
I've been tinkering with side projects on and off for over five months. All told, I spent zero on cloud services. My only cost was 6 yuan, for one domain. Frontend, backend, database, and image storage all run on Vercel and Cloudflare's free tiers.
First, let me clarify the role of each component I actually used, so you don't mix them up:
- Vercel: Mainly hosts frontend pages like Vue and Next.js. Next.js works best on Vercel, with a smooth out-of-the-box experience.
- Cloudflare Pages: Also deploys frontend, suitable for static pages, Astro, and regular Vue projects; its biggest advantage is unlimited free bandwidth, but some advanced Next.js features require extra adaptation to work here.
- Cloudflare Workers: Runs backend endpoints, writing API services like Express, no server management needed, deploy directly to global edge nodes.
- D1: Cloudflare's built-in SQLite database, for business tables like tasks, users, and other structured business data.
- R2: Stores images and uploaded files; the benefit is that it does not charge egress traffic fees for file downloads.
Keep the two storage types separate: D1 stores tabular business data; R2 is specifically for binary files like images and attachments.
- K2, Basin — I never touched these two. K2 is a message queue, Basin is a data lake for massive data; K2 also requires a paid plan. For my current small side-project MVP, they're completely unnecessary, so no point studying them.
Quick comparison of the two frontend platforms: For Next.js projects, prefer Vercel — natively adapted, fewer pitfalls. For regular static Vue or H5 pages, Cloudflare Pages is better — no worries about traffic spikes generating huge bills.
Frontend deployment: Cloudflare Pages in practice
Cloudflare Pages hosts frontend code. First, go to the Pages feature.
Screenshot note: This is the entry to Cloudflare's sidebar "Workers & Pages"; click in to create a new Pages frontend project.
Next, link your GitHub repository and choose the repo holding your frontend code.
Screenshot note: After authorizing GitHub, the page lists all your repositories; select the frontend repo to deploy.
After my first deployment, the page threw an error and wouldn't open.
Screenshot note: Deployment build succeeded but visiting the page gives a 404/error — in most cases the build command or output folder settings are wrong.
Find the project settings, modify deployment configuration, adjust build command and output directory.
Screenshot note: Fill in the build command for the framework, and the folder that gets the built resources, e.g. dist.
Save the config and trigger a rebuild.
Screenshot note: Commit code or trigger manually; Pages will rerun the build.
When the build status turns green, deployment succeeded — you can visit the default domain.
After deployment, you can bind your own subdomain. For example, if the root domain is example.com, the main site www.example.com goes to Vercel, and I create a subdomain vue.example.com assigned to Cloudflare Pages.
Screenshot note: The Pages custom domain config page; fill in the subdomain already hosted on Cloudflare, and the DNS records generate automatically. Once configured, you can access the page with your own domain.
Backend: Workers + D1, a lot of real pitfalls
This backend section had the most pitfalls. I used better-sqlite3 for SQLite locally — everything worked fine locally, but as soon as I pushed the code to a production Worker, the endpoint returned 500.
Screenshot note: The Worker project management UI; here we upload the express backend code and manage bound resources.
The production endpoint returned a 500 error page.
Screenshot note: Browsing the endpoint returns a 500 internal error, but the page doesn't show what the problem is — need to look at logs.
Workers can also bind a custom subdomain; I bound the api subdomain to the Worker service.
Screenshot note: Worker custom domain config; fill in the api subdomain, if the domain is hosted on the current Cloudflare account, binding succeeds.
When production fails, go to the Observability logs page to see event logs — here you see console logs and exception stack traces.
Screenshot note: Events log list; every request is recorded here. Filter for error-level logs to get the error stack trace.
Metrics overview page — statistics on Worker runtime metrics.
Screenshot note: This only counts Worker runtime low-level crashes; business code returning 500 doesn't count into the Errors metric here — don't be misled by 0 Errors.
D1 database monitoring panel shows query count, rows read/written, storage space, number of tables, query latency.
Screenshot note: D1 database overview — if the table count looks wrong, go to the Console tab and run SQL directly to create tables or view data.
Looking at the production log stack trace, I finally understood: better-sqlite3 depends on OS-native binaries, which can't run in the Worker environment. You have to write code to differentiate environments — the production environment only uses the bound D1 instance, and only in local development do you load the local sqlite file.
I also hit a cognitive pitfall in production debugging: the Worker overview page showing Errors=0 does not mean the business endpoints have no errors. That metric only counts low-level runtime crashes. Cases where Express catches an exception and actively returns 500 are business-level errors and don't count into this metric. In that case, the log won't automatically print the error stack — you need to write a global error-handling middleware yourself, manually print the error object, and only then can you see the real error in Observability Events.
My daily habit: when an endpoint fails, check the Events log first, filter error level. Prefer wrangler dev for local debugging — many problems reproduce locally, far easier than reading production logs.
Supporting storage
R2 object storage
Compare it to Alibaba Cloud OSS; good for images and user uploads, its biggest advantage is that outbound traffic isn't billed.
Screenshot note: R2 bucket list page; create a bucket here, then bind R2 in Worker/Pages for file upload and read.
K2 Streams, Basin
I haven't used either at this stage. K2 is a message queue, Basin is a data lake for massive data analytics, and K2 requires a paid plan. For a small side-project MVP, these are unreachable, so no need to spend energy on them.
Domain config in practice, CNAME alias
CNAME is not a page redirect; it's just a domain alias resolution record. If the cloud icon on the record turns orange, traffic goes through Cloudflare first, then forwards to the backend service; if it's grey, it's only domain resolution — traffic goes directly to the origin server, and CDN/protection don't take effect. One small detail: root domains can't directly use CNAME.
Assume:
- My domain:
my-site.com(hosted on Cloudflare) - Third-party platform's assigned target:
target.platform-host.com(e.g. Vercel'scname.vercel-dns.com) Requirement: I want visitingweb.my-site.comto actually open the site deployed on Vercel
Screenshot note: DNS record config screen — add a CNAME record; set the proxy cloud to orange (Proxied). SSL/TLS encryption mode — set to
Full(strict), otherwise you get HTTPS redirect errors.
Another critical point: after configuring CNAME in Cloudflare, you also need to add this custom domain in the Vercel project for verification — otherwise the site still won't open.
My usual production debugging process
After hitting enough problems, I've put together my own order:
- If the frontend page acts up, open the Vercel Observability panel and check for error spikes in CDN or Serverless functions.
Screenshot note: Vercel project overview page; enter Observability from here.
Screenshot note: Vercel Observability monitoring charts; look at error curves for CDN and Functions to locate frontend issues.
- For a backend endpoint returning 500:
- Don't be fooled by Errors=0 on the Worker overview page;
- Switch to Observability's Events page, filter error level, filter POST requests;
- If you don't see a stack trace, check whether you wrote a global error handler to print the err object;
- Watch out for whether new version traffic hasn't switched over yet; use an endpoint version number to confirm the code actually running.
- If you suspect a database issue, open the D1 console, check the table count, run SQL in the Console to verify tables.
Caveats about free quotas
Vercel and Cloudflare's free plans can support side-project idea validation, but each component has its own free quota limits. D1, R2, and Workers all have call limits — enough for low-traffic MVP projects.
An MVP architecture you can copy directly
A ready reference for friends who want to do side projects:
- Frontend web:
- Next.js projects prefer Vercel; regular static Vue pages can use Cloudflare Pages
- Backend: Cloudflare Workers + Express
- Business database: D1
- Images/attachments: R2
- Domain: buy your own domain, host it on Cloudflare
Total cost: only the domain fee. Suitable for small tools, personal SaaS, quickly validating side-project ideas.
Ending
Many people want to try a personal side project but before they start, they worry about server costs being too high, so they never begin.
From my own experience, during the idea-validation phase, there's no need to buy a cloud server at the start. The Vercel + Cloudflare free stack is enough to get a complete product online.
Of course, if you have more useful features or other products, feel free to share in the comments!
Top 1 of 2 from juejin.cn, machine-translated. The original thread is authoritative.
The hard part isn't deployment; without a good idea, nothing works.
[嘿哈] Yes, with a good idea, I'd have time to chat on Juejin instead of going out to make money [偷笑]