Architecture Overview
Building a blog that serves content in under 5ms requires strategic use of edge computing and caching. This tutorial implements a three-tier architecture: Cloudflare Workers for request handling, D1 for persistent storage, and KV for edge caching.
The key to achieving sub-5ms response times lies in serving cached content from edge locations closest to users. Fresh content hits the database, but popular posts are cached in KV stores across Cloudflare's global network.
Prerequisites and Setup
You'll need a Cloudflare account with Workers enabled. Install Wrangler CLI and authenticate:
npm install -g wrangler
wrangler auth loginInitialize your project structure:
mkdir cloudflare-workers-blog
cd cloudflare-workers-blog
wrangler init blog-workerConfigure wrangler.toml
Update your configuration file to include D1 and KV bindings:
name = "blog-worker"
main = "src/index.js"
compatibility_date = "2024-01-15"
[[d1_databases]]
binding = "DB"
database_name = "blog-db"
database_id = "your-database-id"
[[kv_namespaces]]
binding = "CACHE"
id = "your-kv-namespace-id"Database Schema Design
Create a D1 database and define the schema. D1 uses SQLite, so design your tables accordingly:
wrangler d1 create blog-dbDefine your blog schema in schema.sql:
CREATE TABLE posts (
id INTEGER PRIMARY KEY AUTOINCREMENT,
slug TEXT UNIQUE NOT NULL,
title TEXT NOT NULL,
content TEXT NOT NULL,
excerpt TEXT,
published_at DATETIME DEFAULT CURRENT_TIMESTAMP,
updated_at DATETIME DEFAULT CURRENT_TIMESTAMP,
status TEXT DEFAULT 'published'
);
CREATE INDEX idx_posts_slug ON posts(slug);
CREATE INDEX idx_posts_status ON posts(status);
CREATE INDEX idx_posts_published ON posts(published_at);Apply the schema:
wrangler d1 execute blog-db --file=schema.sqlWorker Implementation
The Worker handles routing, implements caching logic, and serves responses. Here's the core implementation:
export default {
async fetch(request, env, ctx) {
const url = new URL(request.url);
const pathname = url.pathname;
if (pathname === '/') {
return handleHomePage(env);
}
if (pathname.startsWith('/post/')) {
const slug = pathname.split('/')[2];
return handlePost(slug, env);
}
return new Response('Not found', { status: 404 });
},
};Caching Strategy Implementation
Implement a multi-layer caching strategy that checks KV first, then hits D1 if necessary:
async function handlePost(slug, env) {
const cacheKey = `post:${slug}`;
// Check KV cache first
const cached = await env.CACHE.get(cacheKey);
if (cached) {
return new Response(cached, {
headers: {
'Content-Type': 'application/json',
'Cache-Control': 'public, max-age=300',
'X-Cache': 'HIT'
},
});
}
// Fetch from D1 if not cached
const post = await getPostFromDB(slug, env.DB);
if (!post) {
return new Response('Post not found', { status: 404 });
}
// Cache for 5 minutes
await env.CACHE.put(cacheKey, JSON.stringify(post), {
expirationTtl: 300
});
return new Response(JSON.stringify(post), {
headers: {
'Content-Type': 'application/json',
'Cache-Control': 'public, max-age=300',
'X-Cache': 'MISS'
},
});
}Database Query Optimization
Optimize your D1 queries for performance. Use prepared statements and efficient indexing:
async function getPostFromDB(slug, db) {
const stmt = db.prepare(
'SELECT id, slug, title, content, excerpt, published_at FROM posts WHERE slug = ? AND status = ?'
);
const result = await stmt.bind(slug, 'published').first();
return result;
}
async function getRecentPosts(db, limit = 10) {
const stmt = db.prepare(
'SELECT id, slug, title, excerpt, published_at FROM posts WHERE status = ? ORDER BY published_at DESC LIMIT ?'
);
const results = await stmt.bind('published', limit).all();
return results.results;
}Performance Optimizations
Edge-Side Includes (ESI) Pattern
Implement partial caching for dynamic content sections:
async function handleHomePage(env) {
const cacheKey = 'homepage:posts';
let posts = await env.CACHE.get(cacheKey);
if (!posts) {
posts = await getRecentPosts(env.DB);
await env.CACHE.put(cacheKey, JSON.stringify(posts), {
expirationTtl: 600 // 10 minutes
});
} else {
posts = JSON.parse(posts);
}
const html = generateHomepageHTML(posts);
return new Response(html, {
headers: {
'Content-Type': 'text/html',
'Cache-Control': 'public, max-age=300'
},
});
}Cache Warming Strategy
Implement cache warming for popular content using Scheduled Workers:
export default {
async scheduled(controller, env, ctx) {
// Warm cache for popular posts
const popularSlugs = ['getting-started', 'advanced-tips', 'performance-guide'];
for (const slug of popularSlugs) {
const post = await getPostFromDB(slug, env.DB);
if (post) {
await env.CACHE.put(`post:${slug}`, JSON.stringify(post), {
expirationTtl: 3600
});
}
}
},
};Content Management API
Build admin endpoints for content management. Implement authentication and cache invalidation:
async function handleAdmin(request, env) {
if (request.method === 'POST' && request.url.includes('/admin/posts')) {
const post = await request.json();
// Insert into D1
const stmt = env.DB.prepare(
'INSERT INTO posts (slug, title, content, excerpt) VALUES (?, ?, ?, ?)'
);
await stmt.bind(post.slug, post.title, post.content, post.excerpt).run();
// Invalidate related cache keys
await env.CACHE.delete(`post:${post.slug}`);
await env.CACHE.delete('homepage:posts');
return new Response('Post created', { status: 201 });
}
}Monitoring and Analytics
Performance Tracking
Add timing headers to monitor cache performance:
async function handlePost(slug, env) {
const startTime = Date.now();
// ... existing logic ...
const responseTime = Date.now() - startTime;
return new Response(content, {
headers: {
'X-Response-Time': `${responseTime}ms`,
'X-Cache': cached ? 'HIT' : 'MISS',
// ... other headers
},
});
}Error Handling and Fallbacks
Implement robust error handling with graceful degradation:
async function getPostFromDB(slug, db) {
try {
const stmt = db.prepare(
'SELECT * FROM posts WHERE slug = ? AND status = ?'
);
return await stmt.bind(slug, 'published').first();
} catch (error) {
console.error('Database error:', error);
return null;
}
}Deployment and Testing
Deploy your Cloudflare Workers blog:
wrangler deployTest performance using curl to measure response times:
curl -w "@curl-format.txt" -o /dev/null -s "https://your-worker.workers.dev/post/sample"Create a curl format file for detailed timing:
# curl-format.txt
time_namelookup: %{time_namelookup}s
time_connect: %{time_connect}s
time_appconnect: %{time_appconnect}s
time_pretransfer: %{time_pretransfer}s
time_redirect: %{time_redirect}s
time_starttransfer: %{time_starttransfer}s
time_total: %{time_total}sProduction Considerations
For production deployment, implement additional optimizations:
- Configure custom domains with SSL
- Set up monitoring with Cloudflare Analytics
- Implement rate limiting to prevent abuse
- Use Cloudflare Images for optimized asset delivery
- Configure security headers and CSP policies
This architecture delivers sub-5ms response times by serving cached content from edge locations while maintaining data consistency through strategic cache invalidation. The combination of D1's SQLite performance and KV's global distribution creates an optimal foundation for high-performance blogging platforms.