MealFlow
The same product, built three times, to find out what the runtime costs you.
- 3
- Runtimes
- 10 weeks
- Span
- None
- Shared code
The problem
Framework comparisons are usually written by people who built a to-do list in each, which tells you about the tutorial and nothing about the framework. I wanted the comparison where the product is fixed and non-trivial and the only variable is the runtime.
The product: a weekly meal planner, a recipe browser, and a categorised grocery list with price tracking and multi-month planning.
What I built
Three independent implementations of the same spec in ten weeks. React Native on Expo with file-based routing, Zustand and NativeWind. Flutter with Riverpod, go_router and Hive. A Next.js PWA on React 19 with Firebase authentication and Firestore.
Recipe discovery in all three comes from the same public recipe API, but there is no shared code, no shared backend and no shared schema. Three clients, three data layers, three architectures.
What the comparison actually showed
The Flutter build produced the cleanest architecture. A repository layer over local storage, with the API client and the persistence behind the same interface, fell out of the language and the ecosystem almost by default. It is also the only one of the three I would call structurally tidy.
The Next.js build produced the only genuinely multi-user product. Once real accounts and a real database were cheap to reach for, the features changed shape: shared household groups, email invitations, live sync between devices. That is not a framework capability, it is what proximity to a backend does to the scope you attempt.
The React Native build produced the most production-grade tooling around the least finished app. It inherited a mature starter, so it has component tests, end-to-end flows and a full CI matrix, and its login issues a hardcoded token to a backend that does not exist. That gap is the most useful thing the exercise taught me: tooling maturity and product maturity are independent, and a repository can look far more finished than it is.
The honest state of each
The Next.js one is the most complete and the only one with real persistence, but it was scaffolded from a generator, its README still describes a database it does not use, and its offline story is asset caching plus persisted local state rather than durable offline writes: the pending-operation queue is deliberately excluded from storage, so a reload before reconnecting loses it.
The Flutter one is a complete single-user app with no accounts and no sync. Its only test file is the untouched framework template, which references a class the app does not define and would not compile.
The React Native one still ships the starter kit’s demo API and feed screens, and its grocery data lives in component state.
None of them is a product. They are three answers to one question, and the answer was worth the ten weeks.