
A look behind the build of OurObit — a privacy-first online memorial service built on Nuxt 4 and Cloudflare Workers, launched in May 2026.
Charles Burnett
Over the years I've supported several friends and family members to craft and host obituaries. I've sampled a variety of services and never found they provided the right tone, usability, or value. So, I decided to create my own — see an overview of the completed OurObit project.
I had other motivations too: I wanted to gain experience with a new toolset and platform for building a SaaS; and I wanted to explore the use of AI coding tools.
Choosing Nuxt. For a development framework I considered Nuxt 4, Next.js, and Django. Django was tempting — I have the most experience with Python and its ORM is mature — but it felt overbuilt for a service that is fundamentally a content-and-auth problem, and server-side rendering a Vue or React front-end on top of it would have added complexity I didn't need. Next.js was the obvious React answer, but I've been building with Vue long enough that the mental overhead of React's paradigms felt like a tax on velocity. Nuxt won on familiarity, on the quality of its ecosystem (Nuxt UI, Nitro), and critically because Nitro's Cloudflare Workers preset meant I could target edge deployment from day one without a separate API layer.
Workers, cheap storage, and edge. The full stack runs as a Cloudflare Workers ESM bundle (~2.8 MB gzip), cold-starting in under 100 ms globally. Storage is Cloudflare D1 (SQLite via Drizzle ORM), R2 for photo storage, and KV for rate limiting and session state.
Payments via Polar.sh. A developer-friendly subscription platform chosen for its clean API, transparent pricing, and no lock-in. Supports monthly, yearly, and lifetime plans, with automatic BC GST + PST calculation.
Print keepsake — a four-attempt engineering story. Generating a 300 DPI file using Cloudflare Workers turned out to be surprisingly constrained:
@resvg/resvg-wasm fetching the WASM file at runtime — blocked by V8 isolate policy (dynamic WebAssembly.instantiate() is forbidden on CF Workers free tier).wasm_modules wrangler binding — rejected at deploy time because the project uses ESM workers, and wasm_modules is a CJS-only feature.import via a custom Rollup plugin — the WASM bundled correctly, but total gzip hit 3.9 MB, exceeding the 3 MB free-tier limit.utif (a pure-JS TIFF encoder, ~55 KB), drawing the memorial's SVG onto a 2550 × 3300 px canvas via the browser's native Canvas API. Zero impact on Worker bundle size.Security. Cloudflare Turnstile CAPTCHA on all public-facing forms; KV-backed IP rate limiting (10 attempts per 10 min on registration, 5/5 min on login); Drizzle ORM parameterized queries throughout to prevent SQL injection; HTTPS-only, no session tokens in localStorage.
Memorial connections. Families can propose consent-based links between related memorials — a obit_links_proposed table with a propose/approve/reject flow ensures both parties agree before any public connection appears.
IndexNow. On publish or republish of a search-opted-in memorial, a ping is sent to api.indexnow.org for immediate crawl scheduling.
In this project I used Microsoft Visual Studio Code as my IDE, and leveraged several AI models — or as I call them 'agents' — via Copilot Chat. At the start I varied my choice as I tried to get a feel for what results I would get from, for example, Claude Haiku, versus Sonnet and Opus, or from the various 'medium'-level or 1X cost GPT and Gemini with Claude offerings. I generally found that most of the 'medium' level models gave me good planning and coding suggestions. In the middle of the project though, when I was adding and changing things that had implications for other parts of the code, I used Claude Opus 4.7 (3X cost) routinely. I switched to this more powerful model when I noticed a few times that the agent was getting into what appeared to be loops, where it returned to an initial idea or pathway towards a solution. I probably could have continued alternating between 0.33X, 1X and 3X level models, but I was finding that my AI development budget was lower than I expected, so why not just use the most expensive?
What went well?
Where did AI models fall short?
What would I do differently?