Workers KV for Domain Routing

There's something magical about handing someone a custom domain for their personal site. Instead of mysite.jottings.me, they get myblog.com. It feels like they own it—because they do. But behind that magic is a gnarly technical problem: how do you route hundreds of custom domains to the right content, instantly, at scale?

When we built custom domain support for Jottings, I knew we couldn't rely on traditional DNS and origin server lookups. That's too slow, too expensive, and too fragile. We needed something that worked at the edge, in real-time, with subsecond latency.

Enter Cloudflare Workers KV.

The Problem with Traditional Domain Routing

Let's say you have 1,000 Jottings users, and 200 of them add custom domains. With a traditional approach, here's what happens on every request:

  1. Request comes in for myblog.com
  2. DNS resolves to your origin server
  3. Origin server queries a database to find which site owns myblog.com
  4. Origin server routes to the correct content (R2 bucket, in our case)
  5. Content is returned to the user

That's four steps. Each adds latency. Each is a potential failure point. And if your origin database is down? Every custom domain stops working.

We needed something faster. Something that works at the edge.

Why Workers KV Is Perfect for This

Cloudflare Workers KV is a globally distributed, key-value store that lives in Cloudflare's edge network. I'll repeat that: it's at the edge. When a request hits Cloudflare, your Worker can query KV and get a response in milliseconds, from a server physically close to the user.

For domain routing, KV is perfect because:

  • Speed: Lookups happen at the edge, not at your origin. No round-trip to a database.
  • Global: KV is replicated across Cloudflare's 300+ data centers. Your lookups work fast everywhere on Earth.
  • Eventual consistency: Writes can take a few minutes to replicate globally, but for domain routing, that's fine. You don't add custom domains every second.
  • Cheap: KV is billed per read/write operation, not by storage size or bandwidth. For domain routing, it's pennies.
  • Simple: It's just a key-value store. No complex queries, no migrations, no maintenance.

How Our Domain Routing Works

Here's the flow at Jottings:

When a user adds a custom domain:

  1. They enter myblog.com in the dashboard
  2. We create a Cloudflare Custom Hostname (SSL certificate + CNAME validation)
  3. Once verified, we write a KV entry: myblog.commysite.jottings.me (the subdomain)
  4. We sync that mapping to our Worker

On every request to a custom domain:

  1. Request hits Cloudflare (via CNAME)
  2. Our Worker does a single KV lookup: "What subdomain owns myblog.com?"
  3. KV returns mysite in milliseconds
  4. Worker routes to https://static.jottings.me/sites/mysite.jottings.me/{path}
  5. Content served from R2

That's it. One lookup. Subsecond latency. No origin servers involved.

Here's what that looks like in code:

export default {
  async fetch(request) {
    const url = new URL(request.url);
    const hostname = url.hostname;

    // Look up the custom domain in KV
    const subdomain = await ROUTING_KV.get(`domain:${hostname}`);

    if (subdomain) {
      // Custom domain found, route to the right site
      const newPath = `/sites/${subdomain}.jottings.me${url.pathname}`;
      return fetch(`https://static.jottings.me${newPath}`, request);
    }

    // Not a custom domain, handle accordingly
    return new Response("Not found", { status: 404 });
  }
};

Simple, right? One KV lookup, one fetch. The entire routing logic fits in a few lines.

Why This Matters

Speed is obvious. But there are deeper benefits:

Reliability: If our DynamoDB goes down, custom domains still work. If the origin API fails, custom domains still work. The Worker is stateless—it just reads KV and proxies the request. Nothing to break.

Scalability: We can have 10,000 custom domains and the performance doesn't degrade. KV handles the lookups; R2 handles the content. Both scale infinitely.

Cost: We pay for the Worker compute (negligible) and KV operations (~$0.50 per million reads). At scale, routing 1 million requests to custom domains costs us less than a dollar. Compare that to an origin server sitting idle 99% of the time.

Simplicity: No caching logic, no cache invalidation, no complex database queries. Just a straightforward key-value lookup. It's the minimum viable solution, and it's fast enough.

The Trade-off: Eventual Consistency

KV isn't instantaneous. When you add or remove a custom domain, it takes a few minutes to replicate globally. So for a few minutes, some users in distant regions might see a 404 or hit the old destination.

We could solve this by querying DynamoDB in the Worker, but that defeats the purpose—we're trying to avoid origin servers. Instead, we made a deliberate choice: custom domain changes can have a few minutes of latency. It's not a problem for our use case. Users rarely add domains during live traffic anyway.

What We Learned

Building this taught me a few things:

  1. Edge computing is real: For certain problems, computing at the edge is orders of magnitude better than origin servers.
  2. KV is underrated: It's not just for caching. It's a legitimate data store for use cases that need global distribution and high-read throughput.
  3. Simple is sustainable: The simpler the system, the fewer ways it can break. Our Worker is boring and boring is good.

If you're building a multi-tenant platform, especially one that needs custom domains or global routing, do yourself a favor: consider Workers + KV before reaching for a traditional database.


If you're thinking about a personal microblog with your own domain, Jottings makes it effortless. Add a custom domain in settings, point your DNS, and watch the magic happen. No server setup, no SSL certificates to manage. Just your site, your domain, working everywhere fast.

Start your site on Jottings today. Create your account and bring your own domain.