Todo
A multi-list todo app that keeps everything in the browser — no account, no server, no sync, and nothing to pay for.
- Section
- productivity
- Year
- 2026
- Role
- Solo project
- Stack
- Next.js, React, TypeScript, Tailwind, localStorage
Summary
A multi-list todo app that keeps everything in the browser. Lists in a sidebar,
todos in a pane, both editable in place — double-click to edit, Enter to save,
Escape to revert. One versioned localStorage key, no account, no server, no
sync.
Story
Every todo app I’ve kept for more than a week eventually asked me to make an
account so it could sync a list I only open on one machine. So the tradeoff is
stated on the tin — nothing syncs, clearing site data erases everything, anyone
on that browser profile can read your lists — and lib/storage.ts is the only
module that touches storage, which makes it the seam if that ever changes. The
hard part was that a client-only app still server-renders once. Initial state is
deliberately empty on both sides and the real data arrives in a single hydrate
dispatch after mount; the effect that writes back to storage has to refuse to
run until that’s happened, or the empty startup state saves over your lists on
every page load. That’s why the hydrated flag lives inside the reducer
rather than beside it in its own useState — two separate updates can land in
different renders, and the save effect firing in the gap wipes the data.
Lessons
- Related state that guards a destructive effect belongs in one update. Split it across two and there’s a render where it’s inconsistent.
- Every reducer case spreads
...stateto preserve the flag; dropping that spread silently disables persistence rather than throwing. localStorageis plain text anything can edit — type-guard what you parsed and fall back to a default instead of casting.