Napalm Paste – A Fast, Private Paste Service Powered by Cloudflare Workers

31 Jan 2026 • 6 min read

Project Overview

Napalm Paste is a lightweight, high‑performance paste service built on Cloudflare’s edge network.
It runs entirely on Cloudflare Workers, using R2 for durable object storage, Workers KV for ultra‑fast caching, and Durable Objects for stateful analytics.

The goal is simple: give developers a fast, secure way to share code snippets, config files, or any text‑based data without the latency of a traditional backend. By rendering the initial page on the edge with Hono SSR and keeping the UI bundle under 50 KB (thanks to Tailwind CSS and minimal JavaScript), Napalm Paste feels instant even on slow connections.


Key Features

  • Sub‑10 ms global response – Edge execution and KV caching keep latency near‑zero.
  • Private pastes – Authenticated access via Cloudflare Access and optional domain‑wide sharing.
  • Burn‑after‑view & time‑based expiration – Auto‑delete after a single view or after 10 min – 30 days.
  • Human‑readable IDs – IDs like happy-river-tiger-magic-blue are easy to remember and share.
  • Syntax highlighting – Highlight.js automatically detects language and applies styling.
  • iOS Shortcuts integration – Share directly from any iOS app via a native Shortcut that calls the query‑parameter API.
  • Dark‑mode aware UI – System preference detection with a manual toggle, all CSP‑compliant.
  • Real‑time analytics – Durable Objects track request counts and cache hit/miss ratios.

Architecture Overview

A minimal edge‑centric diagram (≤ 10 lines, ≤ 60 chars wide) illustrates the data flow:

TEXT
+-----------+        +----------------+        +------------+
| Browser /|  HTTP  | Cloudflare     |  KV   |  Workers   |
| iOS Short.| -----> | Workers (Hono)| <---- |  KV (cache)|
+-----------+        +----------------+        +------------+
                           |
                           | R2 (object storage)
                           v
                     +---------------+
                     |   R2 Bucket   |
                     +---------------+

Durable Objects (analytics) sit alongside the Workers
  • Workers (Hono) – Handles routing, SSR, security middleware, and rate limiting.
  • Workers KV – Caches recent pastes (default TTL 5 min) for instant reads.
  • R2 – Persists the full paste payload; the single source of truth.
  • Durable Objects – Maintain counters and analytics across requests.

How It Works

1. Server‑Side Rendering with Hono

The entry point (src/hono-worker.ts) creates a Hono app and renders JSX components directly inside the Worker:

TS
// src/hono-worker.ts
import { Hono } from 'hono';
import { Layout } from './components/Layout';
import { PasteViewer } from './components/PasteViewer';

const app = new Hono();

app.get('/', (c) =>
  c.html(
    Layout({
      title: 'Napalm Paste',
      children: `<p>Welcome! Create a new paste below.</p>`
    })
  )
);

app.get('/p/:id', async (c) => {
  const id = c.req.param('id');
  const paste = await getPaste(id);
  return c.html(
    Layout({
      title: `Paste – ${id}`,
      children: PasteViewer({ content: paste?.content ?? 'Not found' })
    })
  );
});

export default app;

Layout and PasteViewer are tiny JSX components that output static HTML; because they run on the edge, the first paint arrives in milliseconds.

2. Storing and Retrieving Pastes (R2)

All paste bodies are written to an R2 bucket. The helper functions hide the R2 API details:

TS
// src/r2.ts
export const R2_BUCKET = (globalThis as any).R2_BUCKET as R2Bucket; // bound in wrangler.toml

export async function savePaste(id: string, content: string, ttlSeconds: number) {
  await R2_BUCKET.put(`${id}.txt`, content, {
    httpMetadata: { contentType: 'text/plain' },
    customMetadata: { ttl: `${Date.now() + ttlSeconds * 1000}` }
  });
}

export async function getPaste(id: string): Promise<string | null> {
  const obj = await R2_BUCKET.get(`${id}.txt`);
  return obj ? await obj.text() : null;
}
  • The customMetadata.ttl field is later used to enforce expiration.

3. Fast Reads via Workers KV

When a paste is requested, the Worker first checks KV. If a cached version exists, it returns immediately; otherwise it falls back to R2 and populates the cache:

TS
// src/kv.ts
export const KV_NAMESPACE = (globalThis as any).PASTE_KV as KVNamespace;

export async function fetchPaste(id: string): Promise<string | null> {
  const cached = await KV_NAMESPACE.get<string>(id);
  if (cached) return cached;               // cache hit

  const fresh = await getPaste(id);         // R2 read
  if (fresh) await KV_NAMESPACE.put(id, fresh, { expirationTtl: 300 }); // 5 min
  return fresh;
}

4. Analytics with Durable Objects

A single Durable Object class aggregates request counts and cache‑hit ratios. The Worker binds to it via the DO namespace defined in wrangler.toml.

TS
// src/durable.ts
export class AnalyticsDO {
  state: { requests: number; cacheHits: number } = { requests: 0, cacheHits: 0 };

  async fetch(request: Request) {
    const url = new URL(request.url);
    if (url.pathname === '/stats') {
      return new Response(JSON.stringify(this.state), { headers: { 'Content-Type': 'application/json' } });
    }

    // Any other request increments total count
    this.state.requests++;
    return new Response('OK');
  }

  // Called from the Worker when a KV hit occurs
  async recordCacheHit() {
    this.state.cacheHits++;
  }
}

The main worker can invoke recordCacheHit() after a successful KV read, giving real‑time insight into cache effectiveness.

5. Security Middleware

The Hono router includes two built‑in middlewares:

TS
app.use('*', async (c, next) => {
  // CSP with nonce for any inline script
  const nonce = crypto.randomUUID();
  c.res.headers.set('Content-Security-Policy', `script-src 'nonce-${nonce}'`);
  c.set('cspNonce', nonce);
  await next();
});

app.use('*', async (c, next) => {
  // Simple rate limiting (10 req/s per IP)
  const ip = c.req.headers.get('cf-connecting-ip') ?? 'unknown';
  const limitKey = `rl:${ip}`;
  const count = (await KV_NAMESPACE.get<number>(limitKey)) ?? 0;
  if (count > 10) return c.text('Too many requests', 429);
  await KV_NAMESPACE.put(limitKey, count + 1, { expirationTtl: 1 });
  await next();
});

These ensure each response complies with a strict CSP and that abusive traffic is throttled at the edge.


Getting Started

  1. Clone the repo

    BASH
    git clone https://github.com/xxdesmus/napalm-paste.git
    cd napalm-paste
  2. Install dependencies

    BASH
    npm ci
  3. Configure Cloudflare bindings (see wrangler.toml for R2_BUCKET, PASTE_KV, and ANALYTICS_DO bindings).

  4. Run a local dev server – Wrangler emulates the edge environment:

    BASH
    npm run dev
  5. Deploy to your Cloudflare account

    BASH
    npm run deploy

The local server renders the same HTML you’ll see in production, making debugging straightforward.


Recent Developments

The project has recently completed its migration from a React SPA to Hono SSR with Tailwind CSS (commit c373c81). This shift reduced the client bundle from ~212 KB to under 50 KB and introduced edge‑rendered HTML for faster first paints.

All npm audit findings were addressed in a dedicated security commit (fb49b76), and the theme‑toggle component was updated to be fully CSP‑compliant (da739b0). Documentation was refreshed to reflect the new architecture and feature set.


Conclusion

Napalm Paste showcases how a fully serverless stack—Cloudflare Workers, R2, Workers KV, and Durable Objects—can deliver a secure, ultra‑fast paste service with a minimal footprint. By rendering on the edge, caching aggressively, and providing privacy‑first features like private pastes and burn‑after‑view, it offers a compelling alternative to traditional pastebins.

Whether you’re looking to embed a paste service into your own workflow, study a modern edge architecture, or contribute to an open‑source project that pushes the limits of serverless performance, Napalm Paste is ready to copy, paste, and share.

Explore the code, spin up your own instance, and start sharing snippets at the speed of the edge!


Start searching

Enter keywords to search articles.