research: mobile app strategy #36
Labels
No labels
bug
documentation
duplicate
enhancement
good first issue
help wanted
invalid
question
track:api
track:auto
track:core
track:deploy
track:infra
track:ui
v0.1.0
v0.1.1
wontfix
No milestone
No project
No assignees
1 participant
Notifications
Due date
No due date set.
Dependencies
No dependencies set.
Reference
barrettruth/delta#36
Loading…
Add table
Add a link
Reference in a new issue
No description provided.
Delete branch "%!s()"
Deleting a branch is permanent. Although the deleted branch may continue to exist for a short time before it actually gets removed, it CANNOT be undone in most cases. Continue?
Recommendation
PWA first, Expo second. Start with a Progressive Web App. It requires zero new tooling, zero App Store fees, and ships immediately on the existing Next.js stack. If PWA limitations become blocking (primarily push notification UX on iOS and lack of background sync), graduate to React Native with Expo — the only native option that preserves the TypeScript/React skill set and maximizes code reuse with the existing
src/core/business logic.Do not pursue Capacitor, KMP, Flutter, or native Swift unless the calculus changes significantly.
Comparison Table
src/core/, types, validation)1. Progressive Web App (PWA)
What it is
A PWA turns the existing Next.js web app into an installable, offline-capable app via service workers, a web manifest, and the "Add to Home Screen" flow. No App Store involved.
Effort
Very low. The delta web app already runs in a browser. Adding PWA support means:
manifest.jsonwith icons and theme colors.next-pwa/@serwist/next).This is days of work, not weeks.
Code sharing
100%. It is the same app — no separate codebase.
Offline support
Service workers can cache the app shell and API responses. For a todo app, a network-first strategy for
/api/taskswith a cache fallback provides a solid offline read experience. Offline writes can be queued in IndexedDB and replayed via the Background Sync API — but iOS does not support Background Sync. On iOS, queued writes would need to be replayed when the app regains focus, which is less reliable.Push notifications
Supported on iOS since 16.4, but with significant caveats:
App Store
Not applicable. No $99/yr fee, no review process, no TestFlight. Updates deploy instantly via the existing VPS.
Limitations
Verdict
The right starting point. Covers 80-90% of mobile needs for a self-hosted todo app with near-zero incremental effort. The main gaps are push notification UX and background sync on iOS.
2. React Native / Expo
What it is
A native mobile app built with React and TypeScript, compiled to actual native code. Expo is the managed framework around React Native that handles build tooling, OTA updates, and native module configuration.
Effort
Medium. Expo has gotten significantly easier —
npx create-expo-appscaffolds a working project, Expo Router provides file-based routing similar to Next.js, and EAS Build handles native compilation in the cloud (no local Xcode/Android Studio setup required for builds, though Xcode is needed for simulator testing).The UI layer must be rewritten since React Native uses
<View>,<Text>,<Pressable>instead of HTML elements. No<div>, no CSS-in-the-traditional-sense (thoughStyleSheetis similar, and NativeWind brings Tailwind-like classes to React Native). The existing Tailwind + base-ui component library would not transfer directly.Estimated timeline: 3-5 weeks to a functional MVP with task CRUD, list views, and auth.
Code sharing with delta
This is the strongest argument for Expo. The existing
src/core/layer —task.ts,recurrence.ts,dag.ts,urgency.ts,settings.ts,automation.ts— is pure TypeScript business logic that takes adb: Dbparameter. These modules have no React or DOM dependencies and could be extracted into a shared package in a monorepo (pnpm workspaces). The Expo app would import the same core logic, types fromtypes.ts, and validation schemas.Additionally, the Expo app can call the same REST API routes (
/api/tasks,/api/auth,/api/automations) that the web app uses. The API contract is already defined and tested.A realistic monorepo structure:
Offline support
Excellent.
expo-sqliteprovides a local SQLite database on-device. Combined with Drizzle ORM (which supports SQLite), the same schema definitions could theoretically be shared. Options:For a self-hosted app, manual sync via the existing REST API is the most appropriate approach — no third-party sync services needed.
Push notifications
Full native push notification support. Two paths:
expo-notificationslibrary still handles the client side; the server talks directly to Apple/Google.Both approaches work well with a self-hosted backend. The server stores device tokens (linked to user accounts in the existing SQLite DB) and sends notifications when relevant events occur.
App Store deployment
Verdict
The best native option if PWA limitations become blocking. Maximizes code reuse, stays in TypeScript/React, and has the most mature ecosystem. The cost is maintaining a second UI layer and the App Store overhead.
3. Capacitor (+ Ionic)
What it is
Capacitor wraps a static export of the web app in a native WebView shell. The web code runs as-is inside a native container, with access to native device APIs via Capacitor plugins.
Effort
Low to medium. The main work is converting the Next.js app to a static export (
output: 'export'innext.config.ts), which is a significant architectural constraint.The problem for delta
Delta uses server-side features extensively: server actions (
src/app/actions/), API routes (src/app/api/), server components, and Drizzle ORM queries that run on the server. A static export eliminates all of these. The Capacitor app would need to:use serverdirectives and server actions.fetchcalls to the delta API.images: { unoptimized: true }).This effectively means maintaining two build configurations of the same app, or rewriting significant portions for client-side-only operation. The "wrap your existing app" pitch breaks down when the app relies on server-side rendering.
The latest guidance (early 2026) also recommends against using Ionic's UI components with Next.js, calling the integration "very hacky," and suggests Konsta UI instead.
Offline support
Same as PWA — service workers and cache strategies. No advantage over a PWA here.
Push notifications
Full native push via Capacitor's push notification plugin. Works well.
App Store deployment
Same requirements as Expo: $99/yr Apple Developer Program, App Review, etc. Apple generally accepts WebView-wrapped apps, but they can be rejected if the app is deemed to provide no value beyond what a website offers. For a personal/small-user app, this is unlikely to be an issue.
Verdict
Not recommended for delta. The static export requirement conflicts with the existing server-side architecture. The effort to maintain a static export build alongside the server-rendered web app negates the supposed simplicity. A PWA provides the same web-based experience without the native shell overhead, and Expo provides a genuinely native experience if that is needed.
4. Swift / SwiftUI (iOS only)
What it is
A fully native iOS app written in Swift using Apple's SwiftUI framework.
Effort
High. SwiftUI is a new language and framework to learn. While the declarative syntax has conceptual similarities to React, the ecosystem is entirely different: URLSession for networking, Codable for JSON parsing, Core Data or SwiftData for persistence, Combine for reactivity. Every component must be built from scratch — no code sharing with the web app.
Estimated timeline: 6-10 weeks for a solo developer new to Swift, assuming prior programming experience.
Code sharing with delta
None. Different language, different runtime, different everything. The only shared artifact is the REST API contract.
Offline support
Excellent. Core Data, SwiftData, or direct SQLite access provide robust local storage. Apple's ecosystem has mature tooling for offline-first patterns.
Push notifications
Best-in-class. Full access to all iOS notification features: Time Sensitive, Live Activities, provisional notifications, notification grouping, rich media.
App Store deployment
Same $99/yr. SwiftUI apps tend to have smoother reviews since they use Apple's own framework.
Cross-platform
iOS only. An Android version would require a completely separate app in Kotlin/Java.
Verdict
Not recommended. The effort is too high for a solo developer who already has a working TypeScript/React web app. The iOS-only limitation is also a drawback. The only scenario where SwiftUI makes sense is if native iOS polish (widgets, Siri integration, Live Activities) becomes a hard requirement.
5. Kotlin Multiplatform (KMP)
What it is
JetBrains' framework for sharing Kotlin code across iOS and Android. Business logic is written once in Kotlin; UI can be native (SwiftUI/Jetpack Compose) or shared via Compose Multiplatform.
Effort
High. Requires learning Kotlin, Gradle build system, and either Compose Multiplatform or platform-specific UI frameworks. KMP adoption has grown (7% to 23% in 18 months), and Google announced official support at I/O 2024, but the ecosystem is still maturing compared to React Native.
Code sharing with delta
None with the existing TypeScript codebase. The delta REST API would be consumed as any HTTP client, but no business logic, types, or validation could be reused.
Offline support
Good. SQLDelight provides type-safe SQLite access across platforms.
Push notifications
Full native push on both platforms.
Verdict
Not recommended. KMP is a strong choice for teams with Kotlin/Android experience, but for a solo developer coming from TypeScript/React with an existing Next.js app, the learning curve and zero code reuse make it impractical.
6. Flutter
What it is
Google's cross-platform framework using the Dart language. Renders its own UI via the Skia/Impeller engine rather than using native components.
Effort
Medium-high. Dart is a new language but relatively easy to learn for TypeScript developers (both are typed, garbage-collected, and support async/await). Flutter's developer tooling (hot reload, DevTools) is excellent. However, no code can be shared with the existing Next.js app.
Estimated timeline: 4-6 weeks for an MVP.
Code sharing with delta
None. Dart and TypeScript are different languages. The REST API is the only integration point.
Offline support
Good.
drift(formerlymoor) andsqfliteprovide SQLite access. Flutter has mature offline-first patterns.Push notifications
Full native push via
firebase_messagingor platform-specific plugins.Performance
Flutter consistently benchmarks well: 60-120 FPS rendering, smaller binary sizes than React Native, and the Impeller rendering engine (stable in 2025) provides smooth animations. However, apps don't use native UI components, so they can feel subtly "off" on both platforms.
Verdict
Not recommended. While Flutter is a capable framework with excellent tooling, the Dart language requirement means zero code reuse with the existing TypeScript/React codebase. For a solo developer who already knows React, Expo provides a faster path with better code sharing.
Recommended Strategy
Phase 1: PWA (now)
Add PWA support to the existing Next.js app:
manifest.jsonwith app name, icons, theme color.@serwist/nextor similar (Workbox-based).This provides an installable, offline-capable mobile experience with zero additional infrastructure, zero App Store fees, and immediate deployment via the existing VPS.
Phase 2: Expo (if/when PWA hits walls)
Triggers for moving to Phase 2:
If Phase 2 is triggered:
packages/core,packages/web,packages/mobile.src/core/into the shared package.What to skip