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 login

Initialize your project structure:

mkdir cloudflare-workers-blog
cd cloudflare-workers-blog
wrangler init blog-worker

Configure 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-db

Define 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.sql

Worker 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 deploy

Test 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}s

Production 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.