I started building my new personal site a few months ago and deployed it on Cloudflare Pages. I realized it takes quite a bit of effort — with auto-building, CDN, snapshots, and preview versions — but the best part is that it’s free.
When comparing it with other static site hosts, Cloudflare offers a bunch of powerful features such as:
- Auto-build support for multiple frameworks (Hugo, 11ty)
- CDN network ready to go
- Generous public file limits
- Build time long enough for small and medium sites (around 20 minutes)
See the full comparison below 👇
| Feature / Service | GitHub Pages | Netlify | Vercel | Cloudflare Pages | Render |
|---|---|---|---|---|---|
| Free Tier | ✅ Free for public repos | ✅ Generous free tier (~300 build mins/month) | ✅ Free tier for modern front-ends | ✅ Free with unlimited bandwidth (for most users) | ✅ Free static sites (limited build minutes) |
| Custom Domain & SSL | ✅ Yes, via DNS or CNAME | ✅ Easy setup | ✅ Automatic HTTPS | ✅ Full SSL via Cloudflare CDN | ✅ Automatic HTTPS |
| CDN / Edge Network | ⚙️ Limited / GitHub’s CDN | ✅ Built-in global CDN | ✅ Global edge network | 🚀 Excellent edge performance (same infra as Cloudflare CDN) | ✅ Built-in CDN |
| Build & Deploy Automation | GitHub Actions / Manual push | Built-in CI/CD, deploy previews | Instant deploys, auto rebuilds | Integrated Git deploys, branch previews | CI/CD with Git integration |
| Serverless Functions | ❌ None | ✅ Netlify Functions | ✅ Vercel Functions | ⚙️ Via Workers (optional) | ✅ Built-in |
| Private Repo Support (Free) | ❌ Public only | ✅ Yes | ✅ Yes | ✅ Yes | ✅ Yes |
| Typical Build Time (Free) | ~10 builds/hour | ~300 mins/month | ~100 hrs/month (shared) | ~500 mins/month (typical) | ~500 mins/month |
| Global Performance | 🌍 Basic | 🌍 Very good | 🌍 Excellent | 🌍 Outstanding (edge optimized) | 🌍 Good |
| Best For | Simple personal sites, docs, portfolios | Jamstack & static apps | Framework-based apps (Next.js, SvelteKit) | High-performance static or hybrid sites | Hobby or test projects needing backend |
| Ease of Use | ⭐⭐⭐ | ⭐⭐⭐⭐ | ⭐⭐⭐⭐ | ⭐⭐⭐⭐ | ⭐⭐⭐ |
| Free Bandwidth Limits | Limited / fair-use | Generous | Generous | Unlimited (with fair use) | Limited |
| Custom Headers / Redirects | Partial (via config) | ✅ Full support | ✅ Full support | ✅ Full support via _headers / _redirects |
✅ Full support |
When I first uploaded the site to Cloudflare, it worked pretty well without any issues. But as the site grew and I started focusing more on SEO and page speed, I ran into a problem — the maximum file limit (20,000).
I agree that 20K is quite a lot, but in my case, the site contains a ton of image files — and for each image, I need at least three versions (small, medium, and original sizes) for responsive loading (srcset).
So I started looking for some external hosting to move all the images to — something that could solve this problem (and of course, still be free for development use). And ohh, I found that Cloudflare also offers a service just for this — it’s called R2.
For a quick overview, Cloudflare R2 is an S3-compatible object storage service — basically, it works like AWS S3 but with no fees (Free Tier) for personal use. You can use it to:
- Store files (images, PDFs, videos, etc.)
- Serve them publicly over HTTPS
- Or restrict access via signed URLs
For more details about R2, its features, and pricing, check out the official docs: 👉 https://developers.cloudflare.com/r2/
1. Set up the R2 bucket
Create an empty bucket
- Go to your Cloudflare Dashboard, navigate to the R2 tab, and click Create bucket.
Give it a name (e.g.
cat-mediaorcdn-assets). The region can be left as default (Auto— doesn’t matter much).

After this step, you’ll have an empty bucket ready to use.
Make the bucket public
By default, the bucket is private, and you can only access or manage resources from the R2 Dashboard. If you want to get public URLs for uploaded files, you’ll need to enable public access.
Go to your bucket, open the Settings tab, scroll to Public Development URL, and click Enable.

If you want to use your own custom domain, go to the Custom Domains section and click Add to start setup.
Also, don’t forget to note the S3 API URL in the General section — you’ll need it later (as the endpoint) when configuring API Tokens.
Example structure
You can organize your bucket like this:
cdn-assets/
├── images/
│ ├── logo.png
│ └── thumbnail.jpg
├── videos/
│ └── intro.mp4
├── docs/
│ └── user-guide.pdf
And serve files like this:
https://pub-XXXXX.r2.dev/images/logo.png
https://cdn.your-own-domain.com/docs/user-guide.pdf
2. Upload and manage files in the R2 bucket
There are two ways to upload and manage files in your R2 bucket: using the R2 Dashboard interface or API Tokens.
R2 Dashboard
This is the easiest way! Everything is supported directly in the dashboard. To upload files, just click the Upload button and drag & drop your files into the upload area.

Using API Tokens
This method uses GUI tools, the CLI, or direct SDK integration in your app. First, create API Tokens by going to Manage API Tokens.

You can create either Account API Tokens or User API Tokens — each type is explained there in detail. After creating them, remember to store your keys (Access Key and Secret Key) somewhere private and safe.
Rclone
If you prefer the command line, I recommend installing and using the rclone CLI tool.
Once installed, run rclone config, then select the right options and provide the details.
After it’s configured, you can find where the config file is stored with:
rclone config file
You can also edit it with any text editor (vi, nano, sublime, etc.):
[r2]
type = s3
provider = Cloudflare
access_key_id = abc123
secret_access_key = xyz456
endpoint = https://<accountid>.r2.cloudflarestorage.com
acl = private
For more examples and usage, check out: https://developers.cloudflare.com/r2/examples/rclone/
PicGo / PicList
PicGo is an open-source image uploader that connects to many platforms (GitHub, Imgur, AliOSS, S3, Cloudflare R2…) and automatically copies the link for you. PicList is a fork of PicGo with more advanced features (like managing uploaded images, compression, and resizing) and is now more actively maintained than the original PicGo.
To connect Cloudflare R2, open the app → Manage → choose S3 API → enter your API Tokens and endpoint → click Save.

Once connected, you can upload and manage your media directly from the app.
Programming
Sometimes, you might want to integrate your app with the R2 bucket and control it directly through code. That’s totally fine — since R2 is S3-compatible, any S3 SDK works perfectly.
For example, in Node.js:
npm install @aws-sdk/client-s3
Then:
import fs from "fs";
import { S3Client, PutObjectCommand } from "@aws-sdk/client-s3";
const r2 = new S3Client({
region: "auto",
endpoint: "https://<your-account-id>.r2.cloudflarestorage.com",
credentials: {
accessKeyId: process.env.R2_ACCESS_KEY_ID,
secretAccessKey: process.env.R2_SECRET_ACCESS_KEY,
},
});
const upload = async () => {
await r2.send(new PutObjectCommand({
Bucket: "my-media",
Key: "images/photo.jpg",
Body: fs.readFileSync("./photo.jpg"),
ContentType: "image/jpeg",
}));
console.log("Uploaded!");
};
upload();
✅ TL;DR — Quick checklist
| Step | What to do |
|---|---|
| 1 | Create R2 bucket |
| 2 | Enable public access (or keep private for signed URLs) |
| 3 | Set up API Tokens and optional custom domain (cdn.example.com) |
| 4 | Upload and manage files (manually or via API) |
🏁 Final Thoughts
Overall, I think this combo — Cloudflare Pages + R2 — is one of the best setups for small to medium personal websites. You get a free, global CDN, simple deployment, and reliable object storage all under one roof.
No more juggling between different services — and no surprise bills either. On the other hand, you can also use it for Obsidian attachments, media libraries, or even app backends.
If you’re already using Cloudflare Pages, give R2 a try — it fits right in.

![[Node M1] FATAL ERROR: wasm code commit Allocation failed - process out of memory](/2022/12/node-m1-fatal-error-wasm-code-commit-allocation-failed-process-out-of-memory/fatal-error-wasm-code-commit-allocation-failed_hu44d502e91bc164e463548077191e4ae0_49497_700x350_fill_box_smart1_2.png)
