Building a Portfolio Website with Cloudflare Workers, Hono, and D1
Why Cloudflare Workers Is the Best Stack for a Developer Portfolio in 2026
Most developer portfolios are static HTML hosted on GitHub Pages, or WordPress blogs running on shared hosting. Neither option gives you full control, real-time data, or the performance that a professional site demands. Cloudflare Workers solves all three problems at once — and with Hono as the framework and D1 as the database, the stack is surprisingly simple to build.
This is a complete guide to the architecture behind a production-grade developer portfolio running on Cloudflare's edge network, based on real implementation experience.
The Stack: What and Why
Cloudflare Workers — Serverless JavaScript/TypeScript runtime running at 300+ edge locations globally. Zero cold starts. Free tier covers 100,000 requests per day, which is enough for any personal portfolio. Deploy with a single CLI command.
Hono — A lightweight, ultra-fast web framework designed specifically for edge runtimes. Express-like API with TypeScript-first design. Handles routing, middleware, and context in under 14KB. Significantly faster than Express on the Workers runtime.
Cloudflare D1 — SQLite-compatible serverless database that lives at the edge alongside your Worker. No separate database server to manage, no connection pooling, no TCP latency. You write standard SQL and it just works.
Cloudflare KV — Key-value store used for session management, caching, and configuration. Used here to store admin authentication sessions securely.
Project Structure
src/
index.ts — Main Hono app, all routes
env.d.ts — TypeScript types for bindings
db/
blog.ts — All D1 queries
templates/
layout.ts — HTML shell with SEO meta tags
blog-list.ts — Blog listing page
blog-show.ts — Individual post page
admin/
blog.ts — Admin CRUD interface
middleware/
lang.ts — i18n language detection
services/
recaptcha.ts — Bot protection
migrations/
0001_blog.sql — D1 schema
wrangler.toml — Cloudflare config
Setting Up the Project
Start with the Workers CLI:
npm create cloudflare@latest my-portfolio
cd my-portfolio
npm install hono
Configure wrangler.toml to wire up D1 and KV bindings:
[vars]
SITE_URL = "https://yourdomain.com"
BLOG_ADMIN_USER = "admin"
[[d1_databases]]
binding = "DB"
database_name = "my-portfolio-db"
database_id = "your-database-id"
[[kv_namespaces]]
binding = "KV"
id = "your-kv-namespace-id"
The Database Schema
D1 uses standard SQLite syntax. The blog schema is minimal and efficient:
CREATE TABLE blogs (
id INTEGER PRIMARY KEY AUTOINCREMENT,
title TEXT NOT NULL,
slug TEXT NOT NULL UNIQUE,
content TEXT NOT NULL,
tags TEXT,
meta_description TEXT,
feature_image TEXT,
is_published INTEGER DEFAULT 0,
created_at TEXT DEFAULT CURRENT_TIMESTAMP,
updated_at TEXT DEFAULT CURRENT_TIMESTAMP
);
CREATE UNIQUE INDEX idx_blogs_slug ON blogs(slug);
CREATE INDEX idx_blogs_published ON blogs(is_published, created_at DESC);
Run the migration with: npx wrangler d1 migrations apply my-portfolio-db --remote
Routing with Hono
Hono's routing API is clean and TypeScript-aware:
import { Hono } from 'hono';
import type { Env } from './env.d.ts';
const app = new Hono<{ Bindings: Env }>();
app.get('/blogs', async (c) => {
const posts = await getAllPosts(c.env.DB);
return c.html(renderBlogList(posts));
});
app.get('/blog/:slug', async (c) => {
const post = await getPostBySlug(c.env.DB, c.req.param('slug'));
if (!post) return c.notFound();
return c.html(renderBlogShow(post));
});
export default app;
SEO: The Layer Most Tutorials Skip
A portfolio site that nobody finds is just an expensive hobby. The SEO layer built into this stack includes:
- Schema.org JSON-LD — WebPage, Person, Organization, WebSite with SearchAction, BlogPosting on post pages
- Open Graph tags — og:type changes to 'article' on blog posts, with article:published_time and article:tag
- Hreflang — for multilingual content (en/bn/fr/es/ar variants)
- Sitemap index — /sitemap.xml, /sitemap-images.xml, /sitemap_index.xml
- IndexNow — instant indexing endpoint for Bing, Yandex, Naver, and Seznam
- HSTS preload — Strict-Transport-Security with two-year max-age
The Admin Panel
The admin panel at /admin/blog is server-rendered HTML — no React, no build step, no JavaScript framework. It uses Tailwind CSS via CDN for styling and plain form POST for all CRUD operations. Session authentication is stored in KV with a 7-day TTL.
This approach keeps the admin panel fast, dependency-free, and easy to maintain. Total admin panel code: under 200 lines of TypeScript.
Deployment
npx wrangler deploy
That's it. Cloudflare builds the Worker, uploads it to all edge locations, and your site is live globally in under 30 seconds. Subsequent deploys take about 10 seconds.
Performance Results
On a 3G mobile connection, the portfolio loads in under 1.2 seconds. Lighthouse scores: Performance 98, Accessibility 97, Best Practices 100, SEO 100. There is no JavaScript framework to parse, no database round-trip latency (D1 is co-located with the Worker), and no server to maintain.
The total infrastructure cost for this stack at personal portfolio scale: $0/month. Cloudflare's free tier covers everything.