Skip to content
Writing
Esc
Type to search all articles.
    All writing

    Building a $0 Platform on the Edge

    How static generation, Cloudflare Workers and D1 combine into a personal site that costs nothing to run.

    DS

    Debanjan Saha

    · 1 min read

    – claps
    On this page

    Most of a personal site is content that never changes between requests. That observation drives the whole architecture: render everything ahead of time, and reach for a server only for the few things that genuinely need state.

    The shape of the system

    Pages are generated at build time and served from Cloudflare’s CDN. Only two features need a database: view counts and claps. Those live in D1 and are reached through small API routes that run as a Worker.

    Request flow from the browser to an edge Worker and D1

    Why atomic upserts

    Two readers clapping at once must not lose an update. A read-modify-write in application code races; a single SQL statement does not:

    INSERT INTO post_stats (slug, claps) VALUES (?1, ?2)
    ON CONFLICT(slug) DO UPDATE SET claps = claps + excluded.claps
    RETURNING views, claps;

    The same idea in TypeScript, as it appears in the API route:

    const row = await env.DB.prepare(sql)
      .bind(slug, count)
      .first<{ views: number; claps: number }>();
    
    return Response.json({ views: row?.views ?? 0, claps: row?.claps ?? 0 });

    The best database round trip is the one you never make. The second best is the one that does everything in a single statement.

    What it costs

    PieceFree tier
    Pages hostingUnlimited static requests
    Workers100k requests / day
    D15M rows read / day

    Takeaways

    1. Prerender by default.
    2. Push state into a single atomic statement.
    3. Fetch dynamic numbers on the client so the page itself stays cacheable.

    Enjoyed this?

    A clap helps others find it.

    – claps

    Discussion

    Comments (Giscus) will appear here. Set PUBLIC_GISCUS_REPO, PUBLIC_GISCUS_REPO_ID, PUBLIC_GISCUS_CATEGORY and PUBLIC_GISCUS_CATEGORY_ID to enable them.

    Keep reading