Back to Projects

Bullet.so

Turn Notion pages into fast, SEO-friendly websites

Year
2022 – Present
Role
Software Engineer — owned end-to-end
Status
Live
Visit bullet.so
Next.js TypeScript Node.js Hugo Cloudflare Pages Cloudflare Workers Cloudflare R2 Cloudflare KV Firebase Functions Firestore Stripe Paddle GrapesJS

What it is

Bullet.so turns Notion pages into fast, SEO-friendly websites. You connect a Notion workspace, pick the pages you want public, and Bullet generates and deploys a static site — with custom domains, theming, payments, gated content, analytics, and SEO built in.

My role

I joined Codevantage as the first engineering hire (Sep 2021) and started building Bullet.so in Aug 2022 — writing the first commits of both the dashboard backend and the site-builder service. I owned the product end-to-end as it grew into a SaaS used by hundreds of creators and small teams: site provisioning, the publish pipeline, payments, themes, membership, image delivery, SEO, and the deployment infrastructure. The team grew over time and I worked alongside 4–5 other engineers; I’m the #1 contributor on both repos.

What I built

A multi-tenant site-provisioning system on Cloudflare Pages

Each customer site = its own Cloudflare Pages project. I built the adapter that handles the full lifecycle — create / patch / delete a Pages project, attach and verify custom domains via the Cloudflare API, track deployments, and roll back to a previous deployment when something breaks. Getting this right is what lets a customer point their own domain at their own site and have it just work.

A Notion-to-static-site publish pipeline

The build service is a Node/TypeScript HTTP server that takes a publish request from the dashboard, recursively walks a Notion workspace, renders each page to HTML, stamps the result into a Hugo template, and deploys the output to Cloudflare Pages with wrangler. I wrote it from scratch (first commit Aug 2022) and it’s still in production years later.

Incremental “fast publish” for large sites

Full rebuilds of large sites were slow. I added a change-detection layer that compares Notion’s last_edited_time against a per-site record-map cache and only rebuilds the pages that actually changed — the rest are reused from the previous build. Big customer sites went from “publish takes minutes” to “publish takes seconds” in the common case.

Resilience against upstream failure

Publishing depends entirely on a third-party API, which means it goes down, throttles, and changes shape without warning. I built a resilience layer around it — client-side rate limiting, exponential backoff, watchdog timers with hard aborts on hung requests, and a cache fallback path so a stale-but-valid response beats a failed publish. The point was that a bad day upstream shouldn’t fail a customer’s publish.

A cached image delivery pipeline

Notion’s image URLs are signed and short-lived, so they can’t be served directly. I built a two-pass pipeline: on first publish, the page renders the original Notion URL; in the background, a p-queue-throttled worker uploads the image to Cloudflare Images (or R2 for enterprise sites) and writes the CDN URL to a SQLite + LRU cache; the next publish picks up the CDN URL.

Edge-auth gated content

Membership / paid content is enforced at the edge via a Cloudflare Pages Function rather than at the origin, so access checks run on every request close to the user with no origin hop. Access rules are stored in edge KV and evaluated per request; login supports both shared-password and passwordless email flows.

Dual payments, plan-gated billing

I built both Stripe and Paddle integration — checkout, subscriptions, upgrades, lifetime deals, plan-based feature gating, webhook handling. Two providers because different customer regions and tax-handling needs wanted different rails.

Visual theme editor + SEO

A GrapesJS-based theme editor so customers can change typography, colors, layout, dark mode, and inject custom code without touching markup. The published sites get full SEO: OG tags, sitemaps, JSON-LD, RSS, canonical URLs, security headers.

Tech & decisions

  • Hugo for the actual site builds — fast, mature, easy to template; we generate .md files with rendered Notion HTML inside a rawhtml shortcode.
  • Cloudflare Pages for hosting — one Pages project per customer site means real isolation, real custom domains, and no shared blast radius.
  • Pages Functions for membership — auth at the edge, no origin server hop.
  • Two payment providers — Stripe for global card / Paddle for tax-handled regions.
  • SQLite + LRU for the image cache — small, fast, fits the build server’s profile better than reaching for Redis.

Status

Bullet.so is live and used by hundreds of creators and small teams. I continue maintaining it — most recently a security-hardening pass on the database authorization rules, backed by an automated test suite so the rules can’t silently regress — alongside my current work on BunnyDesk.