Using Vercel KV for a Simple Blog CMS — What I Learned After 100 Posts
I built a simple blog CMS on top of Vercel KV for my utility site. After publishing over 100 posts through it, here's what works, what doesn't, and what I'd do differently.
Why KV Instead of a Database?
For a blog with a few hundred posts, a full database is overkill. Vercel KV (Redis) gives you:
- No schema migrations
- Free tier that's sufficient for small sites
- Simple key-value API
- Fast reads
The Data Structure
Each post is stored as a JSON object under post:slug:
const post = {
slug: 'image-compress-guide',
title_ko: '이미지 압축 완벽 가이드',
title_en: 'Complete Guide to Image Compression',
content_ko: '...',
content_en: '...',
status: 'published', // 'scheduled' | 'published'
published_at: '2026-09-10T00:00:00Z',
scheduled_at: null,
tags: ['image', 'compress', 'toolzip'],
lang: 'both', // 'ko' | 'en' | 'both'
};
await kv.set(`post:${post.slug}`, post);
Listing All Posts
KV doesn't support queries. To list all posts:
async function getAllPosts() {
const keys = await kv.keys('post:*');
const posts = await Promise.all(keys.map(k => kv.get(k)));
return posts
.filter(Boolean)
.filter(p => p.status === 'published')
.sort((a, b) => new Date(b.published_at) - new Date(a.published_at));
}
The problem: kv.keys() returns all keys in a single call. At 1,000 posts, this is still fast. At 10,000, you'll hit limits. Plan for pagination early.
Scheduled Publishing
I added a status: 'scheduled' state and a cron job that runs every hour:
// /api/cron/publish
export async function GET() {
const keys = await kv.keys('post:*');
const now = new Date();
let published = 0;
for (const key of keys) {
const post = await kv.get(key);
if (post?.status === 'scheduled' && new Date(post.scheduled_at) <= now) {
await kv.set(key, { ...post, status: 'published', published_at: post.scheduled_at });
published++;
}
}
return Response.json({ published });
}
What I'd Do Differently
- Add an index: Store a sorted list of slugs separately so listing doesn't require fetching all post data
-
Pagination from day one:
kv.keys()isn't paginated in the free tier - Search: KV has no text search — for that, you need Algolia or similar
What Works Well
For a site under 500 posts, this setup is fast, free, and simple enough to maintain solo. The admin interface I built took one afternoon and handles everything I need.
The blog runs at toolzip.app/blog.
Top comments (0)