The Hidden Cost of Ad Creative Distribution
Most playable ad programs don't account for infrastructure costs. You build the ad, you upload it to the ad network, you run the campaign. But behind the scenes, you need:
- A hosting server for the ad creative (or CDN with a custom domain)
- A tracking system to measure impressions, clicks, and conversions
- An analytics dashboard to compare variant performance
- A/B testing infrastructure to serve different variants to different audiences
These costs add up fast. A typical setup might run $50-200/month on AWS or GCP. For a startup or indie maker running 5-10 campaigns, that's real money.
Cloudflare Workers eliminate this cost entirely. PlayableAd Studio's entire infrastructure stack — hosting, tracking, analytics, and A/B testing — runs on Cloudflare's free or near-free tier.
Architecture Overview
```
[Ad Creative served from R2]
↓
[Tracking Pixel → Worker Analytics Endpoint]
↓
[D1 Database stores every impression + click]
↓
[Real-time Dashboard → Worker-rendered HTML]
↓
[A/B Router → KV + Worker decides variant delivery]
```
Every component is a Cloudflare product. Every component has a generous free tier.
Component 1: Creative Hosting with R2
R2 is Cloudflare's S3-compatible object storage with zero egress fees. Each ad variant is:
```
https://cdn.playablead.studio/variants/{campaign_id}/{variant_id}/index.html
```
With a Worker sitting in front to add CORS headers, cache control, and content negotiation:
```typescript
export default {
async fetch(request: Request, env: Env): Promise<Response> {
const url = new URL(request.url)
const key = url.pathname.slice(1) // remove leading /
const object = await env.ASSETS.get(key)
if (!object) return new Response("Not Found", { status: 404 })
return new Response(object.body, {
headers: {
"Content-Type": "text/html",
"Cache-Control": "public, max-age=31536000, immutable",
"Access-Control-Allow-Origin": "*"
}
})
}
}
```
Cost at scale: **$0 per month** for the first 10GB of storage and 1 million requests.
Component 2: Real-Time Tracking with Workers + D1
Every playable ad includes a 1x1 tracking pixel that fires on impression and another on CTA click:
```html
<!-- Impression pixel -->
<img src="https://track.playablead.studio/e?ev=impression&v={vid}&c={cid}"
width="1" height="1" style="display:none"/>
<!-- Click redirect -->
<a href="https://track.playablead.studio/e?ev=click&v={vid}&c={cid}&r={destination_url}">
Play Now
</a>
```
The Worker endpoint logs every event to D1:
```sql
CREATE TABLE events (
id TEXT PRIMARY KEY,
variant_id TEXT NOT NULL,
campaign_id TEXT NOT NULL,
event_type TEXT NOT NULL,
platform TEXT,
timestamp TEXT NOT NULL,
user_agent TEXT,
ip_hash TEXT
);
```
D1's free tier handles 5 million reads and 100k writes per day — more than enough for most playable ad campaigns.
Component 3: A/B Testing with Workers + KV
The A/B testing system assigns a variant to each user based on a consistent hash:
```typescript
function getVariant(userId: string, campaignId: string, variants: string[]): string {
const hash = await crypto.subtle.digest(
"SHA-256",
new TextEncoder().encode(userId + campaignId)
)
const index = new DataView(hash).getUint32(0) % variants.length
return variants[index]
}
```
Campaign managers control the variant distribution via KV — no code changes needed:
```bash
Distribute 50% to variant A, 30% to B, 20% to C
wrangler kv key put staging:campaign_42 variants '[
{"id": "a", "weight": 50},
{"id": "b", "weight": 30},
{"id": "c", "weight": 20}
]' --namespace-id $KV_NAMESPACE
```
Component 4: Analytics Dashboard
The analytics dashboard is itself a Worker-rendered HTML page that queries D1:
```typescript
export default {
async fetch(request: Request, env: Env): Promise<Response> {
const url = new URL(request.url)
const campaignId = url.searchParams.get("campaign_id")
const results = await env.DB.prepare(`
SELECT
variant_id,
COUNT(*) FILTER (WHERE event_type = 'impression') as impressions,
COUNT(*) FILTER (WHERE event_type = 'click') as clicks
FROM events
WHERE campaign_id = ?
GROUP BY variant_id
`).bind(campaignId).all()
return Response.json(results.results)
}
}
```
This powers a real-time dashboard showing:
| Variant | Impressions | Clicks | CTR |
|---------|------------|-------|-----|
| A (Green CTA) | 1,234 | 89 | 7.2% |
| B (Blue CTA) | 1,198 | 102 | 8.5% |
| C (Red CTA) | 1,211 | 65 | 5.4% |
Cost Breakdown
| Service | Free Tier | Our Usage | Monthly Cost |
|---------|-----------|-----------|-------------|
| R2 Storage | 10 GB | ~200 MB | $0 |
| Workers | 100k req/day | ~5k req/day | $0 |
| D1 | 5M reads/day | ~10k reads/day | $0 |
| KV | 1M reads/day | ~500 reads/day | $0 |
| **Total** | | | **$0/mo** |
Lessons Learned
1. **D1 read-after-write timing** — D1 is eventually consistent. After logging an impression, a subsequent query may not see it for 1-2 seconds. We added a 1-second debounce on the dashboard refresh.
2. **KV warmup** — KV reads under 100ms cold but can spike higher on first access for infrequently read keys. Pre-warm popular campaign configs on deploy.
3. **Worker cold starts** — The first request after idle hits a cold start (~200ms). Scheduling a cron ping every 5 minutes keeps Workers hot.
4. **Pixel blocking** — Some ad networks block tracking pixels from unknown domains. We register each tracking domain with the ad network before launch.
The Takeaway
Zero-cost infrastructure for playable ads isn't a hack — it's a deliberate architecture choice. By building on Cloudflare Workers, R2, D1, and KV, PlayableAd Studio delivers a production-grade ad creative pipeline for exactly $0 per month. For startups and indie teams running playable ad campaigns, this removes the last barrier to entry: infrastructure cost.