Skip to content
appsbynate
← productivity

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 ...state to preserve the flag; dropping that spread silently disables persistence rather than throwing.
  • localStorage is plain text anything can edit — type-guard what you parsed and fall back to a default instead of casting.