The different tech strategies to building a cross-platform app
Tags: mobile, webdev, reactnative, flutter
Published: 2022-10-06 12:20:05

Last updated: 2024-03-08

What are the general technology choices when building an app that should run on both mobile and web/desktop?

Let's list them and detail some important factors and gotchas with each approach. You could also <a href="#heading-my-recommendation">skip to my recommendation</a> at the end. There I also compare and suggest some best-in-class starter repos, to quickly get you started.

Note that this article is mostly geared towards (senior) developers and CTO's, although others should be able to get an overall impression from it as well.

In no particular order:

## 1. iOS + Android + [webapp](https://web.dev/progressive-web-apps/)
- 3x codebase. The webapp (HTML+CSS+JS) is not reused on native.
- Must know and use 3 programming languages: Swift for iOS, Kotlin for Android, and JS for web.
- Hiring might be more difficult (specialized/smaller talent pool), as well as more difficult to create a cohesive company skillbase. A shared skillbase is desirable for synergy in knowledge sharing, and for flexibility: easier for all developers to pitch in where it's needed or if someone is sick.
- Must maintain 3 codebases, and keep feature releases in sync. This can be hard, since the difficulty of implementing various functionality can vary across platforms, and developers might have varying skill level and efficiency. So if you want to release the same feature on all platforms at once, you might need to stall a release on one platform (e.g. iOS) while waiting for the implementation be completed on another (e.g. Android). Different times for App Store review process for iOS and Android might also cause delay. Platform specific bugs is another reason the experience may vary across platforms, and for development across platforms to get out of sync (e.g. the Android developer can't work on a new feature at the same time as the iOS developer, due to having to work on fixing a bug in a previous release. Or vice versa.).
- Native features faster: Quickest path to utilizing native features/UX improvements once they are released, no need to wait for a third party implementation. Example: [shared element transitions](https://github.com/WICG/shared-element-transitions/blob/main/README.md) first came to native, then were replicated on the web.
- App startup time is [likely](https://twitter.com/jitl/status/1552513188683546624?s=20&t=x2dQKwXcLoFHi4oyrI5PEw) faster, since no need to load third party framework (like React Native or CapacitorJS).

## 2. [React Native](https://reactnative.dev/) + [React](https://reactjs.org/)
- 2x codebase  (must write and maintain 2 codebases), but "learn React once".
- If using [Expo OTA / EAS Update](https://docs.expo.dev/eas-update/introduction/) you could push out app updates to all clients immediately, without having to go through the App Store review process (which may take days or up to 2 weeks) or risking that users don't download the update. (But Apple wants you to go through the App Store review process for significant changes to the app, outside of bug fixes etc.)
- React Native can even run on [Windows and MacOS](https://microsoft.github.io/react-native-windows/) too, not only mobile. Even on [tvOS and other platforms](https://reactnative.dev/docs/out-of-tree-platforms). See [React Native's many platform vision](https://reactnative.dev/blog/2021/08/26/many-platform-vision).
- App startup/bootup time with the new Hermes engine that React Native uses is [likely fast enough](https://twitter.com/magnemg/status/1577338426931773442?s=20&t=PeTi26tM3ttHeqMR8fKmRA) (you can achieve sub-second bootup).
- **Native features later**: You'll have to wait for RN implementations of new native features released on each platform (or implement them in RN yourself), and potentially also for [bugfixes of RN builds](https://twitter.com/barelyreaper/status/1578340002484064257?s=20&t=xWZOUA_IgtwjNqRYkFG_uw). New native features unique to a platform (either iOS or Android) can typically not be utilized until an equivalent feature is available for the other platform (and someone makes a RN library that utilizes the two).
- Monorepo: You could share some amount of code between the native and web codebases, [notably](https://youtu.be/QO7SkFqRd7s?t=520):
  _Business logic, state management, some configuration (translation files, TS types, URL endpoints, currency conversion etc.), API calls, formatting request/response data, and authentication_.
But you would traditionally _not share UI Render code_ (look-and-feel, like: styling, animations, navigation). That would be platform-specific. The benefit is that users on a platform will get a look-and-feel closer to what they are used to. The downside is that look-and-feel across  platforms may _unwittingly_ diverge (native mobile vs. mobile web, for instance). That may afford users, who use or switch between several platforms, a more inconsistent experience. The consistency and simultaneous release of new features/UX would also need to be manually kept in sync by  developers, which may be challenging over time. But on the other hand, it might be a good solution if you expect your native and web products to _intentionally_ [diverge over time](https://www.reddit.com/r/reactnative/comments/khoxpm/should_i_go_with_reactnativeweb_or_standard_react/ggmm28l/). A possible monorepo starter kit with this approach is [react-native-universal-monorepo](https://github.com/mmazzarolo/react-native-universal-monorepo) or [create-t3-turbo](https://github.com/t3-oss/create-t3-turbo). It should already here be said that modern libraries for cross-platform styling (like Nativewind or Tamagui), and for cross-platform navigation (like Solito or react-native-url-router) challenge the aforementioned traditional principle that you should not share UI Render code (styling, animations, navigation) across platforms. Nativewind or Tamagui have become viable options, even to be used in monorepo approach (with two separate apps; write all components twice). But they realize their full potential if you do choose to share code across platforms (write all components only once), such as with using React Native Web.
- Flexibility/power: Your progress is not blocked by the web standardization process (and their implementation across browser vendors). Instead, you can rely/wait on libraries from the React Native ecosystem, or take full control and implement what you need yourself.

## 3. [React Native for Web](https://necolas.github.io/react-native-web/) (RNW)
- 1x codebase, "write React once". RNW could have been called "React Universal", as it can run on many platforms due to React Native. See [The case for the React Native Web singularity](https://www.swyx.io/react-native-web-singularity).
- RNW simply lets you use your React Native components (and app) on the web, by translating `<View>` to `<div>` etc. (You're not running the React Native engine on the web, but your components written in the React Native API is simply aliased/translated to be able to work with [react-dom](https://reactjs.org/docs/react-dom.html) which React normally uses on web.).
- Not using React Native Web from the start (if your users "[inevitably](https://youtu.be/UC-bUkbuvZs?t=212)" want to have a native app) was a [famous](https://mxstbr.com/thoughts/tech-choice-regrets-at-spectrum/) [regret](https://youtu.be/UC-bUkbuvZs?t=147).
- Ecosystem challenge: Most React Native library developers [don't](https://twitter.com/magnemg/status/1578412342143950853) [necessarily](https://twitter.com/dimaip/status/1116728579277164547) [develop](https://twitter.com/dimaip/status/1577935520004558851) with the web in mind, and vice versa. Which can make it [hard to find matching components for native and web and writing a consistent wrapper component](https://twitter.com/dimaip/status/1577946556581511169?s=20&t=j4Eu0sAk_MlrTQZ8MSUvPA). There are some [React Native packages with _known_ web support ](https://reactnative.directory/?web=true) (about 166/1142 of the few officially listed as of 2022-10-04). **React Native has about 24% the amount of libraries as the React ecosystem.** As of 2022-10-05: React Native has [41 615 libraries](https://www.npmjs.com/search?q=react-native) available on NPM, but React has a much [larger](https://www.npmjs.com/search?q=react) ecosystem of 212 722- 41 615 = 171 107 libraries for web. If you're planning on only ever deploying your app to the web, you might want to go with plain React instead for this reason.
- React Native Web is traditionally very SPA focused, and it's `StyleSheet.create` solution is using CSS-in-JS. So some have found it tricky to combine with SSR (for SEO purposes), [especially](https://dev.to/redbar0n/how-to-combine-react-native-web-responsivity-nextjs-ssr-to-get-seo-a4g) with responsivity through CSS media queries.
- Tip: **Use [Tamagui](https://tamagui.dev/docs/intro/compiler) for the optimal experience, and SSR compatibility**. It solves the aforementioned problem, as it compiles your styling to CSS media queries on web. Tamagui is a lightweight primitive style system, and fully fledged component UI library, that lets you easily create a design system. The community has made [tamagui-extras](https://tamagui-extras.vercel.app/) to provide even more components.
  - NB: Tamagui [used to use](https://tamagui.dev/blog/version-one) a trimmed version of React Native Web internally, called [react-native-web-lite](https://www.npmjs.com/package/react-native-web-lite), that has a smaller bundle size on web, and supports [Vite](https://vitejs.dev/). But now, since React Native Web has evolved a bit, then Tamagui, [starting from Tamagui v. 1.75.0](https://github.com/tamagui/tamagui/releases/tag/v1.75.0), [uses plain React Native Web again, and `react-native-web-lite` has been deprecated](https://github.com/tamagui/tamagui/commit/cc9143790df88bc748245edeb6663c922adf7ad3). In any case, if it were to change again later, Tamagui has a stated aim of always staying fully compatible with the API of React Native Web.
- Use with a cross-platform navigation/routing solution:
  - [Solito](https://solito.dev) (unifies two navigation systems: react-navigation and nextjs/router)
  - [react-native-url-router](https://github.com/software-mansion-labs/react-native-url-router) (a single navigation system, using [React Router](https://reactrouter.com/en/main) + [react-native-screens](https://github.com/software-mansion/react-native-screens) for stacks + [react-native-pager-view](https://github.com/callstack/react-native-pager-view)for tabs).
  - [Expo Router](https://twitter.com/Baconbrix/status/1573358730946793474?s=20&t=RP7xb9vifLg5OTO92E4iMg), upcoming, based on [expo-auto-navigation](https://github.com/EvanBacon/expo-auto-navigation) (proof-of-concept / experiment)
- A compromise is [only build a base component library](https://youtu.be/UC-bUkbuvZs?t=244) with RNW (buttons, headers, cards, etc.). Instead of sharing navigation/routing and styling.
- **Gesture interactions** (swipe, etc.) in browsers are no piece of cake (many ways of doing it, in either CSS or JS, and libraries of varying quality). But since React Native was made for mobile it is more tailored for gestures. So why no reuse those React Native libraries?

## 4. [Flutter](https://flutter.dev/)

- 1x codebase. Which is 1/2 the work of developing a separate iOS (Swift) and Android (Kotlin) native app. Potentially 1/3 of the work, if Flutter Web works for your use case (as it and can give you a webapp).
- Must learn the [Dart](https://dart.dev/) language.
- [Flutter Web](https://flutter.dev/multi-platform/web) is not suited ([according to themselves](https://twitter.com/magnemg/status/1376160164554887174?s=20&t=v5-p8lRnt5tv2ke2maR_LQ)) for content/document centric apps (or apps requiring SSR/SEO), as it renders everything onto a single Canvas. But Flutter with Flutter Web could esp. be useful for cross-plattform _games_.
- Full control of rendering. Optimizes for consistent UI cross-platform, at expense of platform-specific capabilities and look-and-feel (that users on each platform might be more familiar with). But has [Cupertino widgets](https://docs.flutter.dev/development/ui/widgets/cupertino) for iOS look-and-feel, to alleviate that. (Android uses [Material UI widgets](https://docs.flutter.dev/development/ui/widgets/material)). Could also use [flutter_platform_widgets](https://github.com/stryder-dev/flutter_platform_widgets) that automatically selects the UI widget's look-and-feel according to the mobile platform (iOS or Android).
- Native UI innovations for each platform may arrive later, as they need to be recreated in Flutter.
- _"it seems you can overlap native elements on top of a WebView, at least. But only in React Native and NativeScript, and not in Flutter."_ [my tweet](https://twitter.com/magnemg/status/1378767738802425862?s=20&t=mxTnq8eyM_m_DBvklXnCQQ)
- [Flutter vs. React Native vs. Native (iOS)](https://twitter.com/t3dotgg/status/1544168855412428801?s=20&t=z0FFT-6xD7OiAAiGcECFig) (architecturally).

So, to the various Hybrid-app approaches. Even though React Native and Flutter could be considered hybrid, in some sense (since they are not pure native), I have limited the use of the term "hybrid" to the approaches that render a webapp inside a native shell/wrapper app through using a Web View (an in-app browser window). These approaches all afford you the opportunity to have only ~ 1x codebase:

## 5. [CapacitorJS](https://capacitorjs.com/) + webapp _[hybrid]_
- _WebView that accesses native API's._
- [Ionic](https://ionicframework.com/) UI toolkit to get native look-and-feel (replicates style of iOS and Android components). An alternative is [Framework7](https://framework7.io/).
- Webapp can be built in any web framework like [Solid](https://www.solidjs.com/), [Voby](https://dev.to/fabiospampinato/voby-simplifications-over-solid-no-babel-no-compiler-5epg), [Vue](https://vuejs.org/), [Svelte](https://svelte.dev/) or [Qwik](https://qwik.builder.io/).
- You can use NativeScript and [NativeScript plugins from Capacitor](https://blog.nativescript.org/nativescript-capacitor-v2/).
- [UX/Performance comparison to React Native](https://ionic.io/blog/ionic-vs-react-native-performance-comparison), or [React Native vs. Ionic React+Capacitor](https://twitter.com/magnemg/status/1404073426193690634?s=20&t=Dj_Xoy5CQwWb15r8i04Aog).
- CapacitorJS's little secret is that it will by default _bundle your entire webapp into the App Store bundle that users download_, so it's not loaded from the web on startup. This to ensure that apps are not rejected by App Store if they don't use [enough native features](https://developer.apple.com/app-store/review/guidelines/#minimum-functionality) to enhance the app over a webapp the user could have accessed in a browser. Only adding Push Notifications will likely not count as enough native funtionality. Your experience may differ, and you may get through App Store review for a while, only to get rejected later, by a different reviewer. So it's wise to stay on  the safe side. But loading the webapp on-demand, and not having to push updates through the App Store would have been preferred DX-wise.
- [Ionic Appflow](https://ionic.io/appflow) or the much cheaper [Capgo](https://capgo.app/) solves the aforementioned App Store publishing problem (akin to EAS Update in React Native), to push near instant code updates, and can be used with Capacitor.
- [5 steps to a native app with Capacitor](https://www.youtube.com/watch?v=SSv--IrWH3c) is useful to watch to get a quick overview. As you see, you'll have to touch XCode and Android Studio..

## 6. [Hotwire Turbo Native](https://turbo.hotwired.dev/handbook/native) + webapp _[hybrid]_
- _Native navigation, web content; mix native and web screens._
- _Reuses a single shared Web View_ (WKWebView on iOS, WebView on Android). [This is a clear summary of how Turbo Native works](https://github.com/nandorojo/solito/discussions/157#discussioncomment-7457511) (and the requirements if you were to try to replicate it, for instance in React Native, see subsection 6.2).
- Actually [works with other frameworks](https://twitter.com/zaheerbaloch/status/1341764182568525828?s=20&t=ja0BdhPsy2AmJtK-Rdlrkw) besides Ruby on Rails (even though it was developed in that context).
- HTML over the wire. Could avoid the need for having a SPA with a GraphQL/REST/RPC API (and having to deal with client-side state management, navigation, etc.). Can use Rails/Laravel/Django for an MPA approach, or [Qwik.dev](https://qwik.builder.io/) for an SPA+MPA approach if you want to use JS and be cutting edge wrt. SSR and no hydration.
- [Hotwire Strada](https://hotwired.dev/) is a _standardized bridge_ that lets you drive native navigation [easier](https://twitter.com/dhh/status/1341759896954564608?s=20&t=ja0BdhPsy2AmJtK-Rdlrkw) through HTML attributes on your page. It was [supposed to be released in 2021](https://twitter.com/magnemg/status/1475626003301085184?s=20&t=DXVjmZOwiNWK8KKTAmq9GA), but due to some key developers leaving the company Basecamp that authors it, Strada was postponed until 2022, and is [not yet released](https://discuss.hotwired.dev/t/strada-release-date/2607) as of 2022-10-12... BUT you [don't need to wait for Strada to use Turbo Native](https://twitter.com/dhh/status/1341759896954564608?s=20&t=ja0BdhPsy2AmJtK-Rdlrkw) today. Update: [Strada is now scheduled for release early in 2023](https://discuss.hotwired.dev/t/strada-release-date/2607/11)...
- NB: "Turbo" confusingly has multiple meanings in a React Native (+ Web) context, so don't confuse [Hotwire's Turbo Native](https://turbo.hotwired.dev/) with React Native's own [Turbo Native Modules](https://reactnative.dev/docs/next/the-new-architecture/pillars-turbomodules) architecture, or the [monorepo](https://monorepo.tools/) build system [Turborepo](https://turborepo.org/) (as used in create-t3-turbo, mentioned later here) or its recent sister project [Turbopack](https://turbo.build/pack), which replaces Webpack as a bundler for the web (relevant to React Native Web projects).

Two general ways:

###  6.1 Turbo Native w/ iOS + Android shell
- The default option for apps made with [Hotwire Turbo](https://turbo.hotwired.dev/).
- [turbo-ios](https://github.com/hotwired/turbo-ios) and [turbo-android](https://github.com/hotwired/turbo-android) are the shell/wrapper apps handling native navigation, written for native iOS and Android. They are provided for you, and works out-of-the-box, but you risk having to fiddle with iOS and Android development for maintenance/debugging later on.
- Limited in navigation modes. You are on your own for custom navigation/transitions.
- Must write any potential native screens/features in Swift for iOS and Kotlin for Android. See how Turbo Native works: [Adding navigation bar buttons with Turbo Native (video)](https://youtu.be/vgLIWVWAYrg?t=246).
- [Likely](https://twitter.com/jitl/status/1552513188683546624?s=20&t=x2dQKwXcLoFHi4oyrI5PEw) a faster startup time with these specific native shells (for iOS or Android), than with a React Native shell.
- [Turbo Native demo for iOS](https://github.com/hotwired/turbo-ios/blob/main/Demo/README.md) or [Turbo Native demo for Android](https://github.com/hotwired/turbo-android/blob/main/demo/README.md) is the best way to get started, [according to DHH](https://twitter.com/dhh/status/1341760496182112256) (the Basecamp CTO).
- [Podcast with lead developer Jay Ohms - 2. Aug 2021](https://fragmentedpodcast.com/episodes/220/), to learn more about the overall approach.
- (If you're using Ruby on Rails and are building a SaaS product, then [Jumpstart Rails](https://jumpstartrails.com/) seems to save you a ton of work, for a decent price, and integrates with Turbo Native. [Podcast interview - 23. May 2022](https://podtail.com/en/podcast/fragmented-android-developer-podcast/229-native-where-you-need-it-with-turbo-launch-you/).)

### 6.2 Turbo Native w/ React Native shell
- By using [react-native-turbo](https://www.npmjs.com/package/react-native-turbo). On github it is called [react-native-turbo-demo](https://github.com/software-mansion-labs/react-native-turbo-demo).
- [This is a clear summary of how Turbo Native works](https://github.com/nandorojo/solito/discussions/157#discussioncomment-7457511) (and the requirements if you were to try to replicate it, for instance in React Native, such as react-native-turbo-demo attempts).
- Can write native screens with React Native. Which is useful  if you don't want to have to learn iOS and Android development to make a few screens (as a web developer).
- This would be my preferred Hotwire Turbo Native approach.


## 7. React inside [React Native WebView](https://github.com/react-native-webview/react-native-webview) _[hybrid]_

- Seems absurd: more often you'd rather use React Native Web.
- But could be useful if you need to [quickly port an existing webapp to mobile](https://medium.com/swlh/how-to-build-a-react-native-app-with-react-website-and-webview-2ae53594dc07), for instance to get push notifications on iOS (which was only [just released](https://webkit.org/blog/13878/web-push-for-web-apps-on-ios-and-ipados/) (Feb 16, 2023), and only for users that have upgraded to iOS 16). [yet](https://9to5mac.com/2022/06/06/ios-16-web-push-notifications-safari-update/) available in Safari ). Some [have had success](https://standardnotes.com/blog/react-native-is-not-the-future) with this approach, as opposed to a traditional React + React Native approach that would require 2x the codebase.
- Ecosystem: React Native third-party package ecosystem [is richer than](https://standardnotes.com/blog/react-native-is-not-the-future) for example Capacitor's. That's why you might want to use React Native for the shell app, rather than Capacitor. (It's also easier to turn a screen into a (React) Native screen, if need be.) But in response there's **[Open-Native](https://github.com/OpenNative/open-native)** that (at least) promises you can use React Native libraries in Capacitor and vice versa: _"Open Native is the long overdue Rosetta Stone that allows native modules to be used cross-ecosystem."_
- Deployment: Could also be useful to get a faster deployment pipeline than via App Store. Though [Expo OTA / EAS Update](https://docs.expo.dev/eas-update/introduction/) could also do that for an app made in pure React Native (for Web). But then you need to be concerned with that kind of tooling.
- Might be rejected by App Store if the React Native shell app doesn't use [enough native features](https://developer.apple.com/app-store/review/guidelines/#minimum-functionality) to enhance the app over a webapp. Only adding Push Notifications will likely not count as enough native funtionality. Your experience may differ, and you may get through App Store review for a while, only to get rejected later, by a different reviewer. So it's wise to stay on  the safe side.
- Could be used with a webapp made in any web framework  / render library, not just one made with React. Although React would allow some reuse of knowledge when working on the React Native shell.
- Shared knowledge, faster to rewrite in full React Native if that need arises.
- [react-native-react-bridge](https://github.com/inokawa/react-native-react-bridge) is a bridge that makes communication between React and React Native via the WebView easier, so you'd likely want to use that. Alternatively, you could try to ease communication by directly [making your WebView and React Native components run each other’s functions](https://medium.com/react-native-training/improving-communication-between-react-native-webviews-and-the-webpages-they-render-792c8f7db3e5).

## 8. [NativeScript](https://nativescript.org/) + webapp _[hybrid]_

- Has iOS and Android runtime.
- Access native platform APIs through JavaScript.
- Allows you to [declare the UI](https://apiko.com/blog/nativescript-vs-react-native-choosing-a-cross-platform-framework/) with the help of XML-based language and a subset of CSS.
- But for a fully cross-platform app (also on web), you would typically make a webapp and either:
  - access native API's [through CapacitorJS](https://docs.nativescript.org/capacitor/) and use it to bundle the webapp together with the app you ship to the App Store. The app should start up faster, since it has all the assets (JS/HTML/CSS) locally.
  - make a NativeScript wrapper/shell app that renders the webapp through [NativeScript's WebView](https://v7.docs.nativescript.org/ui/components/web-view). This way, you could ship updates to your app without going through the App Store. But app start up time may be slower, since it needs to download the app assets (JS/HTML/CSS).
  - alternatively, NativeScript has some [integration with Angular](https://blog.nativescript.org/code-sharing-alternate-approach/) and Vue that could allow you to achieve a great amount of [code sharing](https://docs.nativescript.org/code-sharing/index.html#code-sharing) while also avoiding a Web View.
- Directly running [NativeScript on Web is an open issue](https://github.com/NativeScript/NativeScript/issues/6845).
- [react-nativescript](https://github.com/shirakaba/react-nativescript) is another option, to use [React with NativeScript instead of React Native](https://github.com/shirakaba/react-nativescript#why-not-just-use-react-native-%EF%B8%8F), if you plan on writing custom native modules for use in React, but don't want to write them in Swift/iOS & Kotlin/Android (like you'd have to for React Native), but want to write them in JavaScript.
- Some people found it [still immature](https://news.ycombinator.com/item?id=26782346) on 2021-04-12. Maybe due to [many broken plugins in the ecosystem](https://proplugins.org/why.html) (Update: take this with a good pinch of salt, see the comments). And [one of the earliest and most prolific contributors left](https://fluentreports.com/blog/?p=1434) on 2022-07-01. (Update: There's controversy here, so see the comments below, where people have shared further insights, to judge for yourself if this seems significant or not.)
- Small community: _"For every 1,000 PR’s against Flutter or React Native – NativeScript gets a couple."_ said the [contributor who left](https://fluentreports.com/blog/?p=1434).
- Ecosystem:
  - Risk? _"Since you can't find good NativeScript developers to do the work, nor is there much of a third party eco-system left. It is really a bad idea for any companies to base anything off of NativeScript going forward."_ said on 2022-07-01 by the [contributor who left](https://fluentreports.com/blog/?p=1434).
  - **[Open-Native](https://github.com/OpenNative/open-native):** You can now [use React Native UI components](https://twitter.com/ammarahm_ed/status/1622259723729764354?s=20&t=v5AYCNDiIf2JYaa8sXOq3Q) in NativeScript. _"Open Native is the long overdue Rosetta Stone that allows native modules to be used cross-ecosystem."_

## 9. [Xamarin](https://dotnet.microsoft.com/en-us/learn/xamarin/what-is-xamarin) with C# and .NET
- Xamarin forms for simple UIs can share 98% the same code cross-platform. Works quite well, according to Nathan Hedglin in the comments.
- 100% API coverage, which React Native doesn't fully have. So no writing native wrappers.
- For enterprises that already have C# developers and C# backend services, then Xamarin can make sense.
- (Thanks to Nathan Hedglin in the comments for mentioning this possibility and making these points.)


## 10. [Progressive Web App (PWA)](https://www.youtube.com/watch?v=sFsRylCQblw)
- Allows users on native mobile to access your webapp through its own [app shortcut](https://www.goodbarber.com/help/shop/progressive-web-app-settings-r139/add-your-pwa-s-icon-to-a-home-screen-a320/) icon from their mobile home screen.
- Not truly cross-platform, but almost. Works wherever the platform has some sort of browser. But the PWA won't use native components/UI on each platform, since it's just made from HTML+CSS+JS. UX might feel less smooth, and app startup time might be a bit slower (as it has to download the app, and potentially run the JS to render it).
- [Konsta UI](https://konstaui.com/) gives pixel-perfect mobile components (in iOS design, and Android's Material Design) built with Tailwind (and is a [more modern](https://capgo.app/blog/building-a-native-mobile-app-with-nextjs-and-capacitor/#adding-konsta-ui) alternative to Ionic).
- Some potential downsides (especially if your webapp is an SPA that uses JS to re-implement basic browser features like navigation, scrolling etc.): long loading times, bad scrolling behavior, elements out of bounds, bad touch and tap targets, basic or poor-performing animations, layout shifts. But all of these issues could be circumvented or resolved, even though you may run into them.
- Simpler stack, because you, by and large, don't have to duplicate: tooling, performance optimizations, debugging tools, libraries and logging/observability. These extra costs are easy to overlook, if you go for a (React) Native app in addition to a webapp.
- Unfamiliar installation: This won't be a true native app, so it won't be available in the App Store. You could try to guide your users towards [installing the app as a shortcut on their mobile home screen](https://www.goodbarber.com/help/shop/progressive-web-app-settings-r139/add-your-pwa-s-icon-to-a-home-screen-a320/), but they likely haven't done that before. Educating the market [can](https://news.ycombinator.com/item?id=34205216) [be](https://youtu.be/UC-bUkbuvZs?t=212) an uphill battle.
- Push notifications: The biggest disadvantage has been that you don't get push notifications for PWA's on iOS (in none of the browsers, as they all basically wrap Safari), as Apple has been holding it back for years. But Apple recently (Feb 16, 2023) released [Push Notifications on iOS](https://webkit.org/blog/13878/web-push-for-web-apps-on-ios-and-ipados/) (when your users [have upgraded to iOS 16](https://9to5mac.com/2022/06/06/ios-16-web-push-notifications-safari-update/)). So it would now be available to the amount of iOS users who have upgraded to that version of iOS...
- Use any render library you want: [React](https://reactjs.org/), [Solid](https://www.solidjs.com/), [Voby](https://dev.to/fabiospampinato/voby-simplifications-over-solid-no-babel-no-compiler-5epg), [Qwik](https://qwik.builder.io/), [Vue](https://vuejs.org/), [Svelte](https://svelte.dev/) etc.
- Webapps have gotten [a surprising amount of native permissions](https://news.ycombinator.com/item?id=26588480) acessible from browsers, these days. Even though [some features are still lacking](https://news.ycombinator.com/item?id=26669706).
- You could get your webapp to desktop using [Electron](https://www.electronjs.org/) or the newer Tauri (which has [mobile support](https://github.com/tauri-apps/tauri/issues/843) [coming soon](https://github.com/tauri-apps/tauri#platforms)...).
- Listen to the [RNR 156: Progressive Web Apps vs. React Native (2020-02-25)](https://podyssey.fm/podcast/itunes1058647602/episode8123219-Progressive-versus-React-Native-React-Native-Radio) podcast episode to get insights from some experienced developers on this choice.
* See: [What PWA Can Do Today](https://whatpwacando.today/) and the [transitions demo](https://twitter.com/dannymoerkerke/status/1597187172783693824?s=20). Trying [PWABuilder.com](https://www.pwabuilder.com/) might get you started quickly. See also [pwastore.com](https://www.pwastore.dev/) for some critical info on App Stores.

NB: With the rise of tools that can compile to WebAssembly (WASM) that run in browsers, there are more options than those that made the cut for this article (e.g. [Blazor WASM (C#)](https://learn.microsoft.com/en-us/aspnet/core/blazor/?view=aspnetcore-6.0#blazor-webassembly), [Qt for Web](https://doc.qt.io/qtcreator/creator-setup-webassembly.html), etc.). For brevity, they were not included, and I'm also unsure how well these WASM approaches fare with respects to SSR and SEO. See my article [Notes on the future of WASM and JS](https://dev.to/redbar0n/notes-on-the-future-of-wasm-and-js-4g6o).

<a name="heading-my-recommendation"></a>
## My recommendation:

Obligatory disclaimer: It all depends on your particular use case and requirements, of course. For many usecases, a webapp/PWA would be sufficient. In the same spirit as: _"don't build an app if all you need is a site!"_. But if you build your app as a webapp/PWA, the users eventually [typically ask for and expect a native app](https://mxstbr.com/thoughts/tech-choice-regrets-at-spectrum/) (a big if, so know your usecase and your users). It can be hard to argue with and educate your market/users, and easier to get adoption if you go with what they are already familiar with.

So in general, to best get started, and avoid [redoing](https://mxstbr.com/thoughts/tech-choice-regrets-at-spectrum/) work, my recommended place to start for a cross-platform app is with option 3:

**React Native for Web** - specifically by using [t4-app](https://github.com/timothymiller/t4-app)

Because you can have 1x codebase, and there is [a lot](https://moiva.io/?npm=@capacitor/core+@hotwired/turbo+@ionic/core+@nativescript/core+react+react-native+react-native-web&github=flutter/flutter) of developers who know React (and can quickly transfer their skills to React Native) so hiring/employment will be easier. With it, you can also get SSR/SEO for your app on the web, and Expo services like [Expo EAS](https://docs.expo.dev/eas-update/introduction/) (over-the-air app updates) streamline deployment to native (skip App Store approval process for small updates). A bonus is that since it is basically React Native, you can also deploy the app to native [MacOS, Windows or tvOS](https://reactnative.dev/docs/out-of-tree-platforms), due to their [many-platform vision](https://reactnative.dev/blog/2021/08/26/many-platform-vision).

**The second best option** I recommend, would be to make a webapp, in any render framework you like (if so, I recommend [Qwik City](https://qwik.builder.io/qwikcity/overview/) due to its unrivalled SSR capabilities), which you [can rather easily turn into a PWA](https://twitter.com/vendure_io/status/1600412989642878977). Then try out Apple's recently (Feb 16, 2023) released [Push Notifications for Safari on iOS](https://webkit.org/blog/13878/web-push-for-web-apps-on-ios-and-ipados/) (when your users [have upgraded to iOS 16](https://9to5mac.com/2022/06/06/ios-16-web-push-notifications-safari-update/)). Alternatively, combine the webapp with Capacitor or Turbo Native if you need native-only features or native navigation. Where Turbo Native would give you more native navigation but also likely tie you more into its particular way of doing navigation. In Turbo Native, the bridge between your webapp and the native app would also likely be more oriented towards adding attributes to your HTML and having something like Hotwire Strada interpret that from the native side. Whereas with Capacitor it would be more normal to specifically manipulate the native side from your webapp through the JavaScript code you supply it with.

So, with the general React Native Web recommendation, here are quick ways to get started. Here are some good example starter repositories (repos) that will get you up and running quickly, with less config work. They share styling and most of them also share navigation/routing cross-platform.

**If you want something all-inclusive** and don't care about selecting the appropriate starter repo according to your specific tech choices (which can require a bit of experience and/or research), then **my general recommendation** would be to go with:

- **[t4-app](https://github.com/timothymiller/t4-app)** since it is quite feature-packed, which means the least work to get up and running, to focus on the most important part: _your customers' actual needs_. It uses: Tamagui, TypeScript, tRPC, and Turborepo. Plus Clerk for authentication, Solito for navigation, Next.js for SSR, Expo for RN, Jotai for state management, and Drizzle ORM plus Cloudflare Workers and Cloudflare D1 (SQLite DB for the Edge) on the backend. In my subjective opinion it has the best choice of technologies amongst the alternatives I've found and listed below. As of the last update to this article, at least.

If you fancy Tailwind CSS, try either of these starter repos:

Please note that _"Tailwind CSS has minimal support for animations and zero support for libraries that do not accept CSS classes."_ -- [NativeWind author, Mark Lawlor](https://www.nativewind.dev/guides/goals#non-goals)

- **[NativeWind + Solito + Next + Expo Monorepo example](https://github.com/nandorojo/solito/tree/master/example-monorepos/with-tailwind)**
  - You can even [upgrade it to use the Expo Router](https://twitter.com/fernandotherojo/status/1575118137338564608?s=21&t=FJ-mPbY6fk8wxz52sBWfHQ) to get filesystem based routing even on React Native with Expo.

- **[create-t3-turbo](https://github.com/t3-oss/create-t3-turbo)**
  - Like [create-t3-app](https://github.com/t3-oss/create-t3-app) (which is only for web) but for cross-platform as a [monorepo](https://monorepo.tools/) through using [Turborepo](https://turborepo.org/). So you could have several separate apps for various platforms together in the same place (and potentially share some code between them, coordinate versioning etc.).
  - NativeWind + Next + Expo (React Native) is also included here.
  - [tRPC](https://trpc.io/) is prescribed as the data fetching solution (to avoid GraphQL hassle and get fullstack type inference)
  - [Prisma](https://www.prisma.io/) is prescribed as the ORM (which is good, but can be a [heavy dependency](https://www.reddit.com/r/node/comments/wulc2w/what_is_prisma_bad_at/ilapkzk/)).
  - Navigation: It [lacks Solito](https://github.com/t3-oss/create-t3-turbo/issues/25), so it doesn’t attempt to unify navigation across native and web… Which would make you less dependent on React Navigation (which Solito uses). So instead you could f.ex. use [react-native-url-router](https://github.com/software-mansion-labs/react-native-url-router) which aligns with [React Router](https://reactrouter.com/) even for native.

- **[create-t3-turbolito](https://github.com/ebg1223/create-t3-turbolito)** which is a fork of create-t3-turbo that adds [Solito](https://solito.dev/) (for cross-platform navigation) and [Clerk](https://clerk.dev/) for cross-platform authentication.

- **[create-kaol-app](https://github.com/chamatt/create-kaol-app)**  is a bit more [fully packed](https://github.com/chamatt/create-kaol-app#-included-packages), with:
  - Expo + Next.js, as the other alternatives.
  - Styling:
      - [twrnc aka. tailwind-react-native-classnames](https://github.com/jaredh159/tailwind-react-native-classnames) instead of NativeWind, [comparison](https://github.com/jaredh159/tailwind-react-native-classnames/discussions/171#discussioncomment-3097079). But [twrnc doesn't have responsivity + SSR support](https://github.com/jaredh159/tailwind-react-native-classnames/discussions/171#discussioncomment-3097079).
      - [Dripsy](https://github.com/nandorojo/dripsy) unstyled UI primitives which are responsive (at the [expense of SSR support](https://github.com/nandorojo/dripsy/search?q=ssr&type=issues) though).
    -  Auth: it's [own authentication setup](https://github.com/chamatt/create-kaol-app/issues/5), since [NextAuth is not for native](https://github.com/chamatt/create-kaol-app/issues/5), until [further](https://github.com/nextauthjs/next-auth/issues/569) [ado](https://github.com/nextauthjs/next-auth/issues/2294).

But if you don't want to use Tailwind / NativeWind, and don't want to go with my general [t4-app](https://github.com/timothymiller/t4-app) (all-inclusive) recommendation, but select more of the tech yourself, then I recommend either of the following starter repos with Tamagui. **Choose the one that is the best fit with the technologies you need, and that includes the _fewest_ technologies in addition to that**:

- Official [Tamagui starter repos](https://github.com/tamagui/tamagui/tree/master/starters):
  - Native + Web w/ SSR: **[Tamagui + Solito + Next + Expo Monorepo](https://github.com/tamagui/starter-free)**. Monorepo means it has two app directory structures in a single repository: Expo for native and NextJS for web. Each directory imports shared logic and components from a separate packages directory.
  - Native + Web: **[Tamagui + Expo Web](https://github.com/tamagui/tamagui/tree/master/starters/expo-router)**, which shares a single directory structure for React Native and Web, through using Expo Router's filesystem routes. Ideal for SPA's and PWA's on web. No SSR on web by default.
  - Web only: **[Tamagui + Vite](https://github.com/tamagui/tamagui/tree/master/starters/simple-web)**. Relatively bare-bones. No NextJS, no tRPC, no Prisma, no built in authentication solution. Could potentially be combined with either Vite plugin [SSRx](https://github.com/marbemac/ssrx) or [Vike](https://vike.dev/) to get SSR.

- **[Luna](https://github.com/criszz77/luna)** is a [monorepo](https://monorepo.tools/) starter repo that has Tamagui + Solito + Next.js, and like the other alternatives is made with TypeScript and React Native Web. But Luna is for those that want a bare React Native setup, since it comes **without Expo**.

- **[tamagui-react-native](https://github.com/sourcetoad/tamagui-react-native)**. "A starter kit isolated for Tamagui meant for React Native only." Without Expo. Very bare bones. If you don't care about rendering to the web, and if you for some reason don't like to use Expo. As of 2024 all React Native experts recommend using Expo, so please share a comment on this post on any potential reasons to avoid Expo.

- **[create-universal-app](https://github.com/chen-rn/CUA) (CUA)** [NB: usable, but no longer maintained] was based off of the create-t3-turbo repo which means it used **Expo** with the **Expo Router**, and has **tRPC** and **Prisma**. But it has removed Nativewind in favor of Tamagui for cross-platform UI/styling, and adds **[Clerk](clerk.dev)** for cross-platform mobile authentication (since NextAuth, now called [Auth.js](https://authjs.dev/), is still focused on web only). CUA has implemented its own custom cross-platform sign-in page for mobile and web, made with Tamagui. Which could prevent some auth provider vendor lock-in. CUA also has _**step-by-step documentation + a video tutorial + explains the rationale**_ behind the package decisions. See also its [Reddit discussion](https://www.reddit.com/r/reactnative/comments/1065uqo/i_made_a_template_for_making_fullstack/).

- **[pax-react-native-starter](https://github.com/pax-k/pax-react-native-starter)** is a monorepo that in addition to the aforementioned tech, also has Storybook, Jest, and Turborepo integration set up. It aims to give you a rapid web development workflow even when developing for React Native: build features using a browser, deploy to Chromatic/Storybook for review by team members, and CI/CD deploy with Github Actions and Expo EAS.

- **[t4-app](https://github.com/timothymiller/t4-app)** - Tamagui, TypeScript, tRPC, and Turborepo. Plus Clerk for authentication, Solito for navigation, Next.js for SSR, Expo for RN, Jotai for state management, and Drizzle ORM plus Cloudflare Workers and Cloudflare D1 (SQLite DB for the Edge) on the backend.

- **[uni-stack](https://github.com/bidah/uni-stack)** - _"typesafe setup to build fullstack expo universal native apps"_ that uses Expo & Expo Router, TypeScript, Prisma, tRPC, SQLite DB, and for UI it lets you choose either [Nativewind](https://www.nativewind.dev/) / [Tamagui](https://tamagui.dev/) / [GluestackUI](https://gluestack.io/).

The Tamagui and NativeWind ones should [work with responsivity and SSR/SEO](https://dev.to/redbar0n/how-to-combine-react-native-web-responsivity-nextjs-ssr-to-get-seo-a4g). But with Tamagui you also get a pre-built component/UI library, a good and customisable design system, plus an optimizing compiler.

Even if you don't need a native app today, you could start by developing a cross-platform app with a focus only on the web, using React Native Web, and React Native libraries (for a good mobile/gesture experience). Then, the path to releasing the native version of your app at any time will be minimal, since it would be sharing nearly all of the code already. Which, if you’re lucky, would mean only hours of work, or more typically a few days work. But in any case it would not take weeks or months of work, which it normally would take to develop a separate native app.

## Share your experience in the comments

- Did I forget a strategy?

- How do you feel 2 or more of these strategies compare, based on your experience?
    - Comparisons such as [React Native vs. Ionic React+Capacitor](https://twitter.com/magnemg/status/1404073426193690634?s=20&t=Dj_Xoy5CQwWb15r8i04Aog) and [this performance/UX comparison](https://ionic.io/blog/ionic-vs-react-native-performance-comparison) are especially valuable.

- Has PWA's, or any of the hybrid approaches, come so far that they marginalize the benefits of the more native options?
    - Using the cutting edge [Shared Elements Transitions API + Astro](https://www.youtube.com/watch?v=6mv0_jsWhoE) is one such advance.