Evolution of Our Architecture
When building ildora, we wanted the reach of the web with the snappiness of a native application. The web platform has evolved dramatically—modern browsers support offline caching, push notifications, installable app experiences, and hardware-accelerated animations. The question wasn't whether a web app could deliver a premium user experience, but how to architect one correctly.
While we started with a local-first philosophy, we've evolved our architecture to leverage the power of Cloud Database as our primary database for authenticated users, ensuring seamless synchronization and high reliability while maintaining a fast local experience for guests.
This modern architecture guarantees:
- Seamless Sync: Data is instantly available across all your devices when authenticated. Change something on your phone, and it's reflected on your desktop within seconds.
- Privacy & Security: User data is protected by industry-standard encryption and secure authentication. We never request bank credentials or scrape financial data.
- Flexible Storage: Guests can use the app with local storage (zero-account entry point), while registered users benefit from cloud persistence and cross-device sync.
- Performance: Sub-second interactions regardless of network quality, thanks to optimistic UI updates and efficient data patterns.
The Stack
Every technology choice we made was deliberate, optimized for performance, developer experience, and user privacy:
- Framework: React Router v7 — A modern, standards-based SPA architecture that provides file-based routing, nested layouts, and type-safe data loading. We chose React Router v7 over alternatives like Next.js because our app is a true SPA (Single Page Application)—there's no need for server-side rendering of financial data, and client-side rendering gives us maximum privacy since data processing happens entirely in the browser.
- Database: Cloud Database — For secure, real-time cloud data storage for authenticated users. Our database layer handles all subscription CRUD operations, user preferences, and sync state management.
- Guest Storage: LocalStorage — For a zero-account entry point that keeps data entirely on the user's device. This is a core architectural decision: we believe users should be able to try the full app experience without creating an account or sharing any personal data.
- Distribution: Cloudflare Pages — For global edge delivery with sub-50ms time-to-first-byte across the world. Cloudflare's edge network has over 300 data centers globally, meaning users in Mumbai, São Paulo, and Berlin all get near-identical performance.
- Authentication: Secure Authentication Provider — For user identity management, session handling, and secure token management. This offloads the complexity of authentication (OAuth, SSO, password hashing, session management) to specialists.
- Styling: Tailwind CSS + shadcn/ui — For a consistent, accessible design system that can be iterated on rapidly without shipping unused CSS.
Lesson 1: Centralized Truth with Local Speed
One of the biggest challenges in building a modern web app is keeping the UI responsive while ensuring data is securely saved to the cloud. Users expect native-app-level speed (< 100ms response for interactions) while their data needs to persist across devices and survive browser refreshes.
The core tension: Cloud storage provides durability and sync, but network round-trips add latency. Local storage provides instant response, but lacks persistence and cross-device access.
Our approach: Optimistic UI Updates
When a user adds, edits, or deletes a subscription, the UI updates immediately using local state. The write request is sent to the cloud database asynchronously. If the write fails (network interruption, server error), we display a non-intrusive notification and queue the operation for retry.
This pattern means: Add a subscription:* UI shows the new entry instantly (< 50ms), database confirms within 200–500ms. Delete a subscription:* Item disappears from the list immediately, deletion propagates to the cloud in the background. Offline edits:* In guest mode, all operations are local-only. In authenticated mode, failed writes are queued and retried when connectivity returns.
The result: users perceive the app as instantaneous, regardless of their network quality.
Lesson 2: The Guest-to-Authenticated Migration Path
A critical UX challenge: what happens when a guest user—who has been using local storage and may have entered dozens of subscriptions—decides to create an account?
We needed a smooth migration path that: 1. Preserves all locally-stored subscription data. 2. Transfers it to the cloud database seamlessly. 3. Doesn't interrupt the user's workflow or require re-entry. 4. Handles edge cases like partial migrations, duplicate detection, and conflicting data.
Our solution: When a guest user creates an account, we trigger a one-time migration process that reads all subscriptions from local storage, validates them against the cloud schema, and batch-inserts them into the user's cloud database. The local storage is then cleared (with user confirmation) to avoid data duplication.
This "frictionless upgrade" path is essential for conversion: if signing up felt like starting from scratch, many users would stay as guests forever.
Lesson 3: Edge Computing and Global Performance
Deploying on Cloudflare Pages gave us a significant advantage: edge computing. Instead of serving the app from a single server in us-east-1, our static assets are cached at 300+ edge locations worldwide.
What this means in practice:
| User Location | Traditional Server | Cloudflare Edge |
|---|---|---|
| New York | ~20ms | ~10ms |
| London | ~80ms | ~15ms |
| Mumbai | ~200ms | ~25ms |
| São Paulo | ~180ms | ~20ms |
| Tokyo | ~140ms | ~15ms |
For a subscription tracker, these performance numbers matter more than you might think. Users open the app dozens of times per month to check costs, add subscriptions, or reference renewal dates. Each interaction needs to feel instant—any loading delay breaks the "quick check" use case and discourages regular engagement.
Lesson 4: Designing for Privacy by Default
Privacy isn't a feature we added—it's a constraint we architected around from day one. Every technical decision was filtered through the question: "Does this minimize the data we handle?"
Key architectural decisions driven by privacy:
- No bank API integration: We deliberately chose not to integrate with services like Plaid or Yodlee that connect to users' bank accounts. While automatic transaction import would be a competitive feature, it requires trusting a third party with banking credentials—a privacy trade-off we weren't willing to make.
- No analytics telemetry: We don't use Google Analytics, Mixpanel, or any third-party analytics service that transmits user behavior data to external servers. Any analytics we collect is privacy-preserving and first-party only.
- No advertising SDK: Our business model doesn't depend on advertising, so we have zero incentive to collect or sell user data.
- Export-first data model: We built comprehensive CSV and JSON export from the beginning—not as an afterthought. Users can extract their complete dataset at any time, ensuring they're never locked in.
Lesson 5: PWA Capabilities and Limitations
Building as a Progressive Web App (PWA) gave us significant advantages, but also came with real limitations that we want to be transparent about.
What PWAs do well: Installability:* Users can "install" the app to their home screen on iOS, Android, and desktop operating systems. It launches in its own window, without browser chrome, feeling like a native app. Offline capability:* Service workers allow the app shell to load even without internet connectivity. Cross-platform single codebase:* One codebase serves web, mobile, and desktop—eliminating the need for separate iOS/Android apps. Instant updates:* Deploy a new version, and users get it next time they open the app. No app store review process, no version fragmentation.
Where PWAs have limitations (and how we work around them): iOS restrictions:* Safari has historically lagged behind Chrome in PWA feature support. Push notifications on iOS only became available with iOS 16.4, and background sync remains limited. Native API access:* PWAs can't access some device features (Bluetooth, NFC, advanced file system) the way native apps can. For a subscription tracker, this isn't a limitation—we don't need those capabilities. App store discoverability:* PWAs aren't in the App Store or Google Play by default, making organic discovery harder. We compensate with strong SEO and direct web presence.
Implementation: Secure Cloud Persistence
The ability to sync between devices is a core requirement for a modern app. A user might add a subscription on their phone during lunch and want to see the update on their laptop that evening. We've streamlined our architecture to use a cloud database for registered users, with a clear separation between guest (local) and authenticated (cloud) data paths.
The data flow for authenticated users:
- User interacts with the UI (add/edit/delete subscription).
- Local state updates immediately (optimistic update).
- API request is sent to our secure backend.
- Backend validates the request, handles authentication, and persists to the database.
- On success, the local state is confirmed. On failure, the user is notified and the optimistic update is rolled back or queued for retry.
This approach allows for true multi-device synchronization and robust data persistence without the complexity of managing a separate local database for authenticated users.
Real-World Deployment Lessons
Building ildora taught us practical lessons that textbooks don't cover:
1. State management complexity scales non-linearly. With two storage backends (local and cloud), every piece of state needs a clear "source of truth" declaration. We initially underestimated this complexity and had to refactor our state management layer to clearly separate "view state" from "persisted state."
2. Error boundaries are essential for financial apps. When a user is viewing their subscription dashboard—potentially making financial decisions—a JavaScript error that crashes the UI is unacceptable. We implemented comprehensive error boundaries that catch rendering failures and display helpful recovery UI instead of blank screens.
3. Performance testing must include slow networks. Testing on fast Wi-Fi doesn't represent real-world usage. We regularly test on throttled 3G connections to ensure the app remains usable in poor network conditions—because a user checking their subscriptions while traveling abroad or in a rural area shouldn't have a degraded experience.
4. Accessibility is table stakes. A subscription tracker used by people of all ages and abilities must be fully keyboard-navigable, screen-reader compatible, and WCAG 2.1 AA compliant. We invested in an accessible design system (built on shadcn/ui's accessibility-first components) from the beginning rather than retrofitting accessibility later.
Conclusion
Building ildora has taught us that modern apps need to be flexible, fast, and respectful of user choice. By providing a seamless guest experience with local storage and a powerful cloud-backed experience for registered users, we've built an app that is fast (sub-100ms interactions), robust (works offline), and respects user choice (you decide where your data lives).
The web platform in 2025 is remarkably capable. With the right architectural decisions—edge computing for global performance, optimistic UI for instantaneous feel, privacy-by-default for user trust, and PWA technology for cross-platform reach—it's possible to build a financial tool that rivals native app experiences without requiring app store installs or platform lock-in.
The future of personal finance tools isn't about collecting the most data. It's about giving users the most control. And that starts with how you architect your software.