[{"data":1,"prerenderedAt":225},["ShallowReactive",2],{"navigation":3,"blog-page":14,"blogs":25},[4],{"title":5,"path":6,"stem":7,"children":8,"page":13},"Blog","/blog","blog",[9],{"title":10,"path":11,"stem":12},"OurObit.org memorial service launches","/blog/ourobit-memorial-service-launches","blog/ourobit-memorial-service-launches",false,{"id":15,"title":16,"additional_projects_list":17,"body":18,"description":19,"extension":20,"links":17,"meta":21,"navigation":22,"path":6,"seo":23,"stem":7,"__hash__":24},"pages/blog.yml","Latest posts",null,{"title":16,"description":19},"Some of my thoughts on development, design, and innovation.","yml",{},true,{"title":16,"description":19},"fL0PUQ6GKb9Fgqqc75ItstzJIogTMCKw-1BkrbzaWZg",[26],{"id":27,"title":10,"author":28,"body":32,"date":217,"description":218,"extension":219,"image":220,"meta":221,"minRead":222,"navigation":22,"path":11,"seo":223,"stem":12,"__hash__":224},"blog/blog/ourobit-memorial-service-launches.md",{"name":29,"avatar":30},"Charles Burnett",{"src":31,"alt":29},"/profile.png",{"type":33,"value":34,"toc":210},"minimark",[35,40,50,53,57,64,70,76,82,138,144,154,164,168,171,176,184,189,197,202],[36,37,39],"h2",{"id":38},"background","Background",[41,42,43,44,49],"p",{},"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 ",[45,46,48],"a",{"href":47},"/projects/ourobit","overview of the completed OurObit project",".",[41,51,52],{},"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.",[36,54,56],{"id":55},"implementation-highlights","Implementation Highlights",[41,58,59,63],{},[60,61,62],"strong",{},"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.",[41,65,66,69],{},[60,67,68],{},"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.",[41,71,72,75],{},[60,73,74],{},"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.",[41,77,78,81],{},[60,79,80],{},"Print keepsake — a four-attempt engineering story."," Generating a 300 DPI file using Cloudflare Workers turned out to be surprisingly constrained:",[83,84,85,102,114,124],"ul",{},[86,87,88,92,93,97,98,101],"li",{},[89,90,91],"em",{},"Attempt 1:"," ",[94,95,96],"code",{},"@resvg/resvg-wasm"," fetching the WASM file at runtime — blocked by V8 isolate policy (dynamic ",[94,99,100],{},"WebAssembly.instantiate()"," is forbidden on CF Workers free tier).",[86,103,104,92,107,110,111,113],{},[89,105,106],{},"Attempt 2:",[94,108,109],{},"wasm_modules"," wrangler binding — rejected at deploy time because the project uses ESM workers, and ",[94,112,109],{}," is a CJS-only feature.",[86,115,116,119,120,123],{},[89,117,118],{},"Attempt 3:"," Static ",[94,121,122],{},"import"," via a custom Rollup plugin — the WASM bundled correctly, but total gzip hit 3.9 MB, exceeding the 3 MB free-tier limit.",[86,125,126,129,130,133,134,137],{},[89,127,128],{},"Attempt 4 (success):"," Generate the TIFF entirely ",[60,131,132],{},"client-side"," using ",[94,135,136],{},"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.",[41,139,140,143],{},[60,141,142],{},"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.",[41,145,146,149,150,153],{},[60,147,148],{},"Memorial connections."," Families can propose consent-based links between related memorials — a ",[94,151,152],{},"obit_links_proposed"," table with a propose/approve/reject flow ensures both parties agree before any public connection appears.",[41,155,156,159,160,163],{},[60,157,158],{},"IndexNow."," On publish or republish of a search-opted-in memorial, a ping is sent to ",[94,161,162],{},"api.indexnow.org"," for immediate crawl scheduling.",[36,165,167],{"id":166},"ai-support","AI Support",[41,169,170],{},"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?",[41,172,173],{},[60,174,175],{},"What went well?",[83,177,178,181],{},[86,179,180],{},"Plan Mode — Requesting feedback on ideas is a great use of agents. As always, the better you scope and describe what you think you want, the better the resulting plan ideas. I was often bemused at the agent's insightful reference to concepts outside of the coding realm, like SEO, usability and even comments on client demographics.",[86,182,183],{},"Creating tests — In the past I've had trouble leaning into test-driven development, but AI agents build tests quickly and changing the test to adapt to changes in design is not so daunting. In the latter half of the project, I found I was building tests before writing code about half the time, including Playwright tests of the GUI.",[41,185,186],{},[60,187,188],{},"Where did AI models fall short?",[83,190,191,194],{},[86,192,193],{},"Disconnect between sessions — AI agents have memory within a session and they keep hidden files with notes between sessions. But... Early in the project I noted how disconnected the agents were when starting a new session. To try to resolve this, I asked the agent to help me build admin.md, service_plan.md, and build_log.md files and added them to my repo. In short, admin.md had all the technical specs, service_plan.md had the list of features for MVP and Post-MVP, and the build-log.md had a rolling list of what got done, what worked well, and when lessons were learned. I then set up two routines: 'UPMEM' to get the agent to update these three files at the end of each session, and 'DOWNMEM' for reading the three files to start a session.",[86,195,196],{},"Over-enthusiasm — I had multiple (dozens?) of experiences where the agent was ready to move on or declare a feature complete... before testing in test and production was complete. I think this is a little odd for a coding assistant tool; I always found something to change or fix during testing.",[41,198,199],{},[60,200,201],{},"What would I do differently?",[83,203,204,207],{},[86,205,206],{},"Hey coach? — I loved having the agents as coaches, explaining to me how development in Nuxt used this or that pattern. Agents are great because they are endlessly patient with explanations and diversions. As a result, I will probably use another coding framework for my next project — just to keep learning.",[86,208,209],{},"So many options — I may try using Claude Code Desktop instead of Visual Studio Code. Just to see how different it is. Part of that will be due to privacy considerations. With Code, Copilot sees the prompts and interfaces with the agent. With Claude, there would be one less party. I have disabled the settings in Copilot that would allow it to use my code and prompts for training. But, I'm not confident that it won't be. I also look forward to hearing how home-hosted agents improve.",{"title":211,"searchDepth":212,"depth":212,"links":213},"",2,[214,215,216],{"id":38,"depth":212,"text":39},{"id":55,"depth":212,"text":56},{"id":166,"depth":212,"text":167},"2026-05-17T00:00:00.000Z","A look behind the build of OurObit — a privacy-first online memorial service built on Nuxt 4 and Cloudflare Workers, launched in May 2026.","md","/CESB_on_OurObit.png",{},6,{"title":10,"description":218},"1hURnwl-BDpN23LUUZZaFrdBOVNMpo5b9ix6f9kO4Q4",1779084565376]