English
From Base64 to Cloudflare R2: How I Elegantly Solved the Logo Image Storage Problem
Preface
Here's the situation. I have a PNG image of a brand logo that I need to use on a webpage. At first, to save effort, I directly converted the image to Base64 format and embedded it in the CSS file.
And then I broke down.
That Base64 string looks like a novella: thousands of characters stuffed into the code, instantly bloating the entire file, and every edit requires scrolling for ages. Even worse, the browser can't cache it, so this "mysterious gibberish" has to be retransmitted every time the page loads.
So I began looking for a better solution.
Requirements list
Free——Personal project, don't want to spend money
Can bind your own domain——URL must be your own; you cannot use
xxx.oss.com/logo.pngthis kind ofNo filing required—using a foreign domain and server
Simple and elegant——Don't want to spend ages configuring it.
Solution Comparison
Did a quick round of research:
| Plan | Free quota | Bind domain name | Filing | complexity |
|---|---|---|---|---|
| Alibaba Cloud OSS | ❌ Paid | ✅ | ✅ Required | low |
| Qiniu Cloud | 10GB | ✅ | ✅ Required | low |
| Cloudflare R2 | 10GB | ✅ | ❌ Not needed | Extremely low |
| GitHub+jsDelivr | Infinite | Requires additional configuration | ❌ Not needed | Middle |
| ImgBB | Infinite | ❌ | ❌ | Extremely low |
Cloudflare R2 wins The reason is simple: my domain happens to be managed by Cloudflare, so binding the domain is a one-click task.
Complete configuration process
Step 1: Activate R2
Log in to the Cloudflare Dashboard → Click R2 in the left sidebar menu → Click “Get Started”.
Note
You need to add a credit card, but it’s only for abuse prevention. Free tier: 10GB storage + 1 million write operations per month + 10 million read operations. Even if a single logo image is used until the site shuts down, it won’t exceed the quota.
After confirming, click Subscribe; the fee will be displayed.$0.00.
Step 2: Create a storage bucket (Bucket)
Click "Create Bucket", and fill in the name assets(As you like, since a custom domain will be used later and this name will not be exposed), keep the location as default.
Step 3: Bind a custom domain
This is the most crucial step, and also the most elegant one.
Enter the newly created Bucket →Settings→Custom Domains(The description says:Expose the contents of this R2 bucket to the internet through a custom domain. Recommended for production use.)
Click "Connect Domain", enter the subdomain you want to use, for example image.greating.com, confirmed.
A miracle happened:
Cloudflare automatically for
image.greating.comApplied for and configured an SSL/TLS certificate.Automatically added a CNAME record to DNS resolution
CDN acceleration was automatically enabled.
Bucket contents are automatically publicly accessible through this domain.
The entire process Less than 10 seconds, and there is no need to turn on any additional “public access” switch. I didn’t even have time to take a sip of water before everything was ready.
💡 Tip
After binding a custom domain, public access takes effect automatically. You do not need to set "Public Access" again—that option is for R2's built-in r2.dev It’s for the domain and has nothing to do with the custom domain.
Step 4: Upload the Logo image
Click "Upload", and logo.png Drag it in, let go, done.
Step 5: Use
Pass directly https://image.greating.com/logo.png Access your images.
Final effect
Before refactoring (Base64 approach)
css
.logo {
background-image: url('data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAA...');
}The actual length of that Base64 string is Over 4,600 characters, this one line alone accounts for half of the CSS file.
After modification (R2 scheme)
css
.logo {
background-image: url('https://image.greating.com/logo.png');
}46 characters So refreshing it makes me want to cry.
Why is this said to be "elegant"?
Automatic TLS: No need to deal with Let's Encrypt yourself, and no need to worry about renewing it every three months.
Auto DNS: No need to manually add a CNAME record at the domain registrar
Automatic CDN Cloudflare global edge network automatically takes effect
Zero-configuration public access: After binding a custom domain, it is automatically made public; no additional settings are required.
No-code: No need to write any API calls, and no need to configure the SDK.
Completely free At least that’s the case for small files like Logo.
Unexpected gain
After completing the configuration, I found that Cloudflare also did these things along the way:
Force HTTPS redirect (no need to write Nginx rules yourself)
HTTP/2 + HTTP/3 automatic support
Global edge node caching (also fast for domestic access)
DDoS protection takes effect automatically
These are all buy one, get many — the kind of features you can enjoy without configuration.
Complete process review
The entire operation requires only four steps:
Enable R2 → Create Bucket → Bind custom domain → Upload files → DoneFrom opening Cloudflare to getting https://image.greating.com/logo.png, measured 3 minutes Enough.
A few tips to avoid pitfalls
Bucket name can be arbitrary., because a custom domain is ultimately used, the Bucket name will not be exposed
After binding the domain, wait 1-2 minutes., DNS takes a little time to take effect
No additional public access configuration is required.—It takes effect automatically after you bind a custom domain, so don’t go searching everywhere for a toggle like I did before.
The free quota is quite ample.: 10GB storage ≈ 200,000 Logo images
What else are you unsatisfied with?
To be honest, for the requirement of storing a few images, this solution is almost perfect.
If I had to nitpick: Cloudflare’s Dashboard is a bit complex, and it may take a few minutes to find the feature entry points the first time you use it. But considering how much it offers for free, this learning cost is completely acceptable.
Summary
If you meet the following conditions:
✅ Domain is managed in Cloudflare
✅ Need to store a small number of static images/files
✅ Want to bind my own domain
✅ Don't want to file a record
✅ Budget is 0
Just enable Cloudflare R2 directly. Don't hesitate.
From a Base64 string of over 4,000 characters to a 46-character URL, this migration took about 3 minutes. Of that, 2 minutes were spent figuring out how to enable R2, and the actual operation took less than 1 minute.
This is the appeal of modern cloud services: leave the complex things to the platform and keep the simple things for yourself.