How to Build a Blog with SonicJS and Cloudflare Workers
Step-by-step tutorial on building a blazingly fast blog using SonicJS headless CMS deployed on Cloudflare Workers with D1 database and R2 storage.

How to Build a Blog with SonicJS and Cloudflare Workers
Key Stats:
- Sub-50ms response times worldwide
- 3 content collections: Authors, Categories, Posts
- Built-in relationships and media handling
- 1 command to deploy:
npm run deploy
Building a blog has never been faster. In this tutorial, we'll create a complete blog backend using SonicJS, leveraging Cloudflare Workers for global edge deployment. Your blog will have sub-50ms response times worldwide.
What We're Building
By the end of this tutorial, you'll have:
- A complete blog content API
- Author management with relationships
- Category and tag support
- Media uploads for featured images
- Full-text search capability
Prerequisites
- Node.js 20+
- Cloudflare account
- Wrangler CLI installed
Step 1: Project Setup
Create a new SonicJS project:
npm create sonicjs-app my-blog
cd my-blog
Step 2: Define Content Collections
Authors Collection
Create src/collections/authors.ts:
import { defineCollection } from '@sonicjs-cms/core'
export const authorsCollection = defineCollection({
name: 'authors',
slug: 'authors',
fields: {
name: {
type: 'string',
required: true,
maxLength: 100,
},
email: {
type: 'email',
required: true,
unique: true,
},
bio: {
type: 'text',
maxLength: 500,
},
avatar: {
type: 'media',
},
twitter: {
type: 'string',
maxLength: 50,
},
github: {
type: 'string',
maxLength: 50,
},
},
})
Categories Collection
Create src/collections/categories.ts:
import { defineCollection } from '@sonicjs-cms/core'
export const categoriesCollection = defineCollection({
name: 'categories',
slug: 'categories',
fields: {
name: {
type: 'string',
required: true,
maxLength: 50,
},
slug: {
type: 'string',
required: true,
unique: true,
},
description: {
type: 'text',
maxLength: 200,
},
},
})
Posts Collection
Create src/collections/posts.ts:
import { defineCollection } from '@sonicjs-cms/core'
export const postsCollection = defineCollection({
name: 'posts',
slug: 'posts',
fields: {
title: {
type: 'string',
required: true,
maxLength: 200,
},
slug: {
type: 'string',
required: true,
unique: true,
},
excerpt: {
type: 'text',
maxLength: 300,
},
content: {
type: 'richtext',
required: true,
},
featuredImage: {
type: 'media',
},
author: {
type: 'relation',
collection: 'authors',
required: true,
},
category: {
type: 'relation',
collection: 'categories',
},
tags: {
type: 'array',
of: 'string',
},
status: {
type: 'select',
options: ['draft', 'published', 'archived'],
default: 'draft',
},
publishedAt: {
type: 'datetime',
},
seo: {
type: 'object',
fields: {
metaTitle: { type: 'string', maxLength: 60 },
metaDescription: { type: 'string', maxLength: 160 },
ogImage: { type: 'media' },
},
},
},
})
Step 3: Configure the CMS
Update src/index.ts:
import { Hono } from 'hono'
import { createSonicJS } from '@sonicjs-cms/core'
import { authPlugin, mediaPlugin, cachePlugin } from '@sonicjs-cms/core/plugins'
import { authorsCollection } from './collections/authors'
import { categoriesCollection } from './collections/categories'
import { postsCollection } from './collections/posts'
type Env = {
DB: D1Database
CACHE: KVNamespace
STORAGE: R2Bucket
}
const app = new Hono<{ Bindings: Env }>()
app.use('*', async (c, next) => {
const cms = createSonicJS({
database: c.env.DB,
cache: c.env.CACHE,
storage: c.env.STORAGE,
collections: [
authorsCollection,
categoriesCollection,
postsCollection,
],
plugins: [
authPlugin(),
mediaPlugin(),
cachePlugin({
defaultTTL: 3600,
patterns: {
'/api/content/posts': { ttl: 300 },
'/api/content/posts/*': { ttl: 600 },
},
}),
],
})
c.set('cms', cms)
return next()
})
export default app
Step 4: Set Up Database
Create and configure D1:
# Create database
wrangler d1 create my-blog-db
# Create KV namespace for caching
wrangler kv:namespace create CACHE
# Create R2 bucket for media
wrangler r2 bucket create my-blog-storage
Update wrangler.toml with your resource IDs:
name = "my-blog"
main = "src/index.ts"
compatibility_date = "2024-01-01"
[[d1_databases]]
binding = "DB"
database_name = "my-blog-db"
database_id = "your-database-id"
[[kv_namespaces]]
binding = "CACHE"
id = "your-kv-id"
[[r2_buckets]]
binding = "STORAGE"
bucket_name = "my-blog-storage"
Run migrations:
npm run db:generate
npm run db:migrate:local
Step 5: Test Your API
Start the development server:
npm run dev
Create an Author
curl -X POST http://localhost:8787/api/content/authors \
-H "Content-Type: application/json" \
-H "Authorization: Bearer YOUR_TOKEN" \
-d '{
"name": "Jane Doe",
"email": "jane@example.com",
"bio": "Technical writer and developer advocate.",
"twitter": "janedoe"
}'
Create a Category
curl -X POST http://localhost:8787/api/content/categories \
-H "Content-Type: application/json" \
-H "Authorization: Bearer YOUR_TOKEN" \
-d '{
"name": "Tutorials",
"slug": "tutorials",
"description": "Step-by-step guides and how-tos"
}'
Create a Post
curl -X POST http://localhost:8787/api/content/posts \
-H "Content-Type: application/json" \
-H "Authorization: Bearer YOUR_TOKEN" \
-d '{
"title": "Hello World",
"slug": "hello-world",
"excerpt": "My first blog post with SonicJS",
"content": "<p>Welcome to my new blog!</p>",
"author": "author-id-here",
"category": "category-id-here",
"tags": ["introduction", "hello"],
"status": "published",
"publishedAt": "2025-12-12T00:00:00Z"
}'
Query Published Posts
# Get all published posts with author data
curl "http://localhost:8787/api/content/posts?status=published&include=author,category"
# Search posts
curl "http://localhost:8787/api/content/posts?search=hello"
# Filter by category
curl "http://localhost:8787/api/content/posts?category=category-id"
Step 6: Deploy to Production
Deploy your blog to Cloudflare's global network:
# Apply production migrations
npm run db:migrate:prod
# Deploy
npm run deploy
Your blog API is now live at https://my-blog.your-subdomain.workers.dev!
Step 7: Connect Your Frontend
Use any frontend framework to consume your blog API:
React/Next.js Example
// lib/api.ts
const API_URL = process.env.NEXT_PUBLIC_CMS_URL
export async function getPosts() {
const res = await fetch(`${API_URL}/api/content/posts?status=published&include=author`)
return res.json()
}
export async function getPost(slug: string) {
const res = await fetch(`${API_URL}/api/content/posts?slug=${slug}&include=author,category`)
const data = await res.json()
return data.data[0]
}
// app/blog/page.tsx
import { getPosts } from '@/lib/api'
export default async function BlogPage() {
const { data: posts } = await getPosts()
return (
<div>
{posts.map((post) => (
<article key={post.id}>
<h2>{post.title}</h2>
<p>{post.excerpt}</p>
<span>By {post.author.name}</span>
</article>
))}
</div>
)
}
Performance Tips
Enable Aggressive Caching
cachePlugin({
defaultTTL: 3600,
patterns: {
'/api/content/posts': { ttl: 60 }, // List updates quickly
'/api/content/posts/*': { ttl: 3600 }, // Individual posts cache longer
},
})
Use ISR in Next.js
export const revalidate = 60 // Revalidate every 60 seconds
Optimize Images
SonicJS integrates with Cloudflare Images for automatic optimization:
mediaPlugin({
imageOptimization: true,
variants: ['thumbnail', 'medium', 'large'],
})
Key Takeaways
- SonicJS makes building a blog API straightforward
- Collections define your content structure with TypeScript
- Relationships connect authors, categories, and posts
- D1 and KV provide edge-first data storage
- Deploy globally with a single command
Next Steps
- Add comments using a custom collection
- Implement newsletter subscriptions
- Set up webhooks for build triggers
- Add full-text search with Cloudflare Workers AI
Happy blogging with SonicJS!
Related Articles

Getting Started with SonicJS: Complete Beginner's Guide
Learn how to set up SonicJS, the edge-first headless CMS for Cloudflare Workers. This comprehensive guide covers installation, configuration, and your first content API.

Why Edge-First CMS is the Future of Content Management
Discover why edge-first content management systems like SonicJS are revolutionizing how we build and deliver digital experiences with unprecedented speed and reliability.

Understanding SonicJS Three-Tiered Caching Strategy
Learn how SonicJS implements a three-tiered caching strategy using memory, Cloudflare KV, and D1 to deliver sub-15ms response times globally.