Building a Portfolio Website with Cloudflare Workers, Hono, and D1
← Back to Blogs
4 min read 749 words Tamim Iqbal

Building a Portfolio Website with Cloudflare Workers, Hono, and D1

Cloudflare WorkersHonoD1Web DevelopmentTutorialTypeScriptPortfolio

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.

Developer coding a portfolio website with TypeScript and Cloudflare Workers
Building on the edge: Cloudflare Workers runs at 300+ global locations with zero cold starts

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.