Project Case Study
My Kitchen Board
A guest-first family meal planner and kitchen display. Plan a week of meals at the times a household actually eats, turn that week into one aggregated grocery list, and prop a tablet in the kitchen to show what is cooking next. Next.js 16 and Supabase, with an IndexedDB guest mode that works before anyone signs in.
View Live Project →Overview
My Kitchen Board is a family meal planner and kitchen display. The landing page states it in one line — "The week's meals. One happy kitchen." — and the promise under it is smaller than most planning tools make: "A little planning. A lot less what's-for-dinner."
The product is currently in Beta, free, and deliberately account-optional.
The Problem
Meal planning apps usually assume three things that real kitchens do not honour. That dinner happens at dinnertime. That everyone in the house eats the same thing. And that a visitor will create an account before they have seen anything work.
A household actually looks like this: breakfast at 7:00 for one child and 7:30 for another, two dishes at 6:00 PM that together are "dinner", a plan that is mostly the same every week, and one person doing the shopping for all of it. My Kitchen Board is built around that shape rather than around a tidy grid.
Key Product Decisions
1) Guest-first, not sign-up-first
The planner opens on a seeded sample week held in IndexedDB. It can be edited, cleared, and used indefinitely with no account — the landing page says so twice, because it is the main objection to overcome: "No account required · Saved on your device."
Signing in with Google is framed as one specific upgrade — the same kitchen on a phone and on a tablet — not as the price of entry.
2) Meals at real times, for real people
A meal is scheduled at an arbitrary household-local time, may overlap with another meal, and targets any combination of family members — including members who have no account and never will. Two dishes at 6:00 PM read as one dinner rather than as a conflict.
3) One grocery list that actually adds up
The week collapses into a single list. Duplicate ingredients merge, quantities scale to the servings planned, and every line keeps its provenance: which recipe version, on which occurrence, contributed it. Quantities are canonicalised with decimal.js — grams for mass, millilitres for volume, units for count — so scaling a recipe to 1.5 servings does not quietly drift.
4) Recipes that keep their history
Recipe versions are immutable. Editing a recipe creates a new version, and a plan that was already made keeps pointing at the text it was planned from. Materialized occurrences deliberately do not auto-pin to the newest version, so last Tuesday's dinner stays what it was.
5) A screen that belongs on a counter
The display view is built for a tablet left standing in the kitchen: today's meals in time order, serving sizes already adjusted, and timers built into the cooking steps. It is a different reading distance from the planning screens, and it is laid out as one.
6) Recurrence, because most weeks repeat
One-time, daily, every-N-day, weekly, every-N-week, and selected-weekday recurrence are all supported, so a standing Taco Tuesday is entered once rather than fifty-two times.
Technical Implementation
Stack
| Area | Implementation | Notes |
|---|---|---|
| Framework | Next.js 16 (App Router) + React 19.2 | TypeScript, React Compiler enabled |
| Data (guest) | IndexedDB via idb | Full product surface with no network |
| Data (cloud) | Supabase Postgres + Auth | 93 migrations, household-scoped RLS |
| Server state | TanStack React Query | Same hooks over guest and cloud repositories |
| Quantities | decimal.js | Canonical grams / millilitres / count |
| Time | Luxon | Household timezone, not the browser's |
| Photos | Cloudflare R2 via aws4fetch | Signed access, orphan sweep scripts |
| Testing | Vitest + Playwright | Domain logic unit-tested, flows end-to-end |
Two storage backends, one domain
packages/core holds the domain and data layers as plain TypeScript — roughly 125 modules covering groceries, recurrence, servings, cooking, energy, and sync — with no React and no Next in sight. Guest storage and the Supabase repository implement the same contracts, which is why guest mode is the whole product rather than a demo: the UI does not know which one it is talking to.
A content security policy that is split on purpose
A nonce and a prerendered page cannot coexist. Next stamps the nonce during the render, so anything built ahead of a request arrives with scripts the browser then refuses to run. The policy is therefore split rather than compromised:
Planner routes take a per-request nonce from src/proxy.ts and render dynamically. They are closed to crawlers and fetch their data in the browser, so rendering per request costs nothing that matters.
Public recipe pages and the sitemap keep a fixed policy and stay static and cacheable, which is the entire point of them.
Public pages without opening the tables
Publishing a recipe makes it readable at /r/<title>-<id> with no session. Anonymous access goes exclusively through three database functions; the anon role holds no grant on the recipe tables themselves, so those functions remain the only way in. Published pages carry the recipe alone — no author, no household, no comments.
Installable, and honest about it
The manifest, every icon, the favicon and both lockups are cut from a single SVG master by a build script, in three variants, because the platforms disagree about who rounds the corners. The service worker caches nothing: the kitchen's own records already live in IndexedDB, and a stale app shell is indistinguishable from a live one. It exists so Chrome will offer to install the app, and so an offline navigation gets a screen that explains itself instead of a browser error.
What This Case Study Demonstrates
Designing an offline-first product where the guest path is the product, not a trial
Keeping a domain layer framework-free so two storage backends can share it exactly
Modelling immutability and provenance so a past plan stays true after the recipe changes
Getting decimal-safe quantities and timezone-correct scheduling right at the bottom of the stack
Treating CSP, RLS, and public-page exposure as design decisions rather than deployment checkboxes
Live: https://www.mykitchenboard.com/
Role: Solo delivery (product → domain → schema and RLS → PWA → brand)