Skip to content

Case studyNo. 01

Independent product

BakePrice

A recipe-costing and pricing tool for home bakers and cottage bakery owners.

Plate 01. The suggested-price formula, set as type. Not a screenshot.

Problem

BakePrice answers two questions: what a recipe really costs, and what to charge for it. It is built for a non-technical home baker who prices cakes, cookies, cupcakes and bread from a spreadsheet, paper notes or intuition. The scope is recipe costing and pricing, not general bakery management.

The intended path is a free calculator that works without an account, then saving a recipe, signing in, a persistent recipe library, and a one-time Pro purchase for unlimited use.

Constraints & decisions

  • Ingredients are shared entities. An ingredient row holds the only copy of its package price; a recipe line holds only a quantity and a unit. Recipe economics are derived when they are read, so updating butter's price once re-prices every recipe that uses butter, and there is no stored total to go stale.
  • One calculation engine. shared/domain/ is imported by the React client for the live preview and by Convex queries for dashboard summaries, so the client and the server cannot disagree on a number.
  • Units never cross dimensions silently. Weight, volume and count are closed dimensions. Converting between them is a typed failure with a readable message, because a cup of flour and a cup of honey do not weigh the same.
  • Margin, not markup. The suggested price is cost / (1 − margin).
  • Email codes instead of magic links. The anonymous calculator draft lives in the visitor's tab. A code is typed back into that tab, so the draft survives sign-in even when the email is opened on a phone. A magic link signs in whichever device clicked it.
  • The draft survives sign-in. It is kept in localStorage, not sessionStorage, because a new tab or window during sign-in would lose it. After sign-in the calculator finishes the save, and the draft is cleared only once the save succeeds.
  • Polar as merchant of record. The Pro unlock is a one-time Polar purchase. The webhook is signature-verified, only orders for BakePrice's own product grant Pro, and a refund revokes it.
  • Limits are enforced on the server. Free-plan limits are checked in Convex mutations, not only in the interface.

Solution

The frontend is Vite, React 19, strict TypeScript, Tailwind CSS and React Router. Convex provides the database, server functions and HTTP actions. Convex Auth sends sign-in codes through Resend, and Netlify hosts the static build.

The calculation engine and its tests sit in shared/domain/, apart from both the interface and the backend. Webhook verification runs in a Node action because the Polar SDK needs Buffer. A provisioning script creates the Polar product and webhook and writes the backend environment, so secrets never appear in src/, in a VITE_-prefixed variable or in a committed file.

The result panel is written to stay honest while a baker types. It never shows NaN, Infinity or a negative zero amount. It prints the achieved margin when rounding moves it more than half a point from the target. A recipe with a line it cannot cost says so instead of showing a confident total.

Outcome & lessons

As built, BakePrice has a free anonymous calculator on the landing page, free accounts that save up to two recipes, and a one-time Pro purchase that lifts the plan limits on recipes and ingredients. Pro keeps high safety ceilings, because economics are derived on read and an account past the backend's per-query document limit could not load.

Limitations accepted for the first version:

  • USD only.
  • Saving a recipe updates a shared ingredient's package price without a confirmation step. The change is reported afterwards ("re-priced N other recipes").
  • No pagination; those Pro ceilings bound the data instead.
  • Weight-to-volume conversion is refused rather than estimated. Per-ingredient density is the planned next addition, and the schema has room for it.