As developers, we are always on the lookout for tools that would allow us to save time and effort. We want to be able to write code and run it everywhere. With React Native and Flutter, this dream is slowly turning into reality. Because now, not only can we run apps on both Android and iOS, we can run it on the web and even desktop as well.
In this article, we’re going to compare each platform. Then we’re going to zoom in on its web capabilities to see if it’s good enough to be used in production. Lastly, we’ll look at the pros and cons of each platform. After reading this article, you’ll be able to decide which one to use on your next project.
React Native vs. Flutter
For us to effectively judge React Native Web and Flutter for web, it’s important that we first compare the underlying platform. We have to consider not just the web capabilities, but also the overall platform experience. I’ll try to stay as concise as possible because these kinds of comparisons have been done countless times already. You can learn more here: Flutter vs. React Native vs. Xamarin.
We’ll use the following criteria as the point of comparison:
- Beginner friendliness
- Plugin support
- Developer productivity
- App performance
- Testing and deployment
- Web support
How quickly can a beginner go from zero knowledge of the platform to a fully-functioning app?
In React Native, it’s a five-step process:
- Install Node.js
- Install Expo CLI
- Generate a new project
- Install Expo client on your mobile device
- Run the project
There’s one extra step required to run it on the browser, but that’s negligible because it’s only a command to install the web dependencies.
The steps mentioned above are from the quick start guide. You’ll need to invest more time when setting up your machine if you go the CLI route. Though that’s something a beginner rarely needs to do since most of the native APIs are already implemented in Expo.
On the other hand, Flutter’s development setup can be summarized with the following steps:
- Download the Flutter SDK
- Install the prerequisite developer tools (e.g. curl, zip, git)
- Add the Flutter SDK to your system path
- Install Android Studio and Xcode
- Generate a new project
- Run the project
For the web setup, there are a few additional steps you need to go through.
It looks pretty straightforward, but it can be pretty daunting for a complete beginner with little to no experience of the command line and how to install different tools.
The community is another big factor to consider when choosing a platform. This community is the culmination of the number of libraries and plugins, tutorials, forum / Stackoverflow questions, and GitHub issues that are out there.
Web developers are a spoiled bunch. Almost all of the functionality we’re ever gonna need in building modern web applications has already a corresponding library that implements it. All we have to do is learn its API and we’re good to go.
Flutter has a pretty decent plugin system that implements most of the functionality that is commonly used in mobile apps (e.g. social login, Google Maps).
React Native wins this because we can use almost any npm module (except for those node modules with native dependencies).
Another important factor when it comes to deciding which tool to use is developer productivity. This can mean a lot of things, so we’re going to break it down into the following:
- Programming language
- Code structure
- Developer tooling
- UI components
- Native API access
Most people coming to explore solutions for building cross-platform apps using a single codebase are web developers. We want to reuse our existing knowledge of the web ecosystem to build mobile and desktop apps. This means that React Native is a more viable option for us since we practically don’t have to learn anything else to build apps using React Native.
Being able to find the things you’re looking for in a relatively short time and immediately grasp the information is another determinant of developer productivity.
Flutter’s documentation is more organized and more detailed than React Native. But when it comes to beginners, it can be pretty overwhelming to be greeted by tons of links. I like the simplicity of React Native’s documentation.
That said, we don’t stay a beginner forever. Once we gain more experience and get used to where things can be found, then it becomes easier. This is where Flutter’s documentation outshines React Native. Furthermore, React Native relies on a lot of community-written libraries, so its documentation is very fragmented. While Flutter has everything in one place.
As a new developer coming into an existing project, how easy it is to make sense of the way the code is organized?
In React Native, you can pretty much have any structure you want. Usually, it’s dependent on the size of the project and the various tools and libraries that your team has adopted. For example, using Redux will force you into a specific kind of structure since you will have reducers, actions, etc.
The situation is the same in Flutter, you can organize your code any way you want. But since navigation and state management are already baked into the framework, code organization can get more opinionated as well.
Hot reload is one of those things that can get annoying if a platform doesn’t support it. Being able to modify your code and see the changes on the fly as you save is a big productivity booster (especially on a dual-monitor setup).
Both React Native and Flutter support this feature. Though it’s faster in Flutter if you compare it with Expo. There’s just a tiny bit of difference when compared to the CLI route for React Native.
Another important thing to consider is the debugging experience. In React Native, error messages can be pretty hard to grasp. While in Flutter, due to Dart being a statically-typed language, the error messages it shows you tend to be more meaningful.
React Native components translates to native UI components, so it has the same look and feel of a native app.
On the other hand, Flutter paints the UI elements using a rendering engine. It’s responsible for creating all the basic functions of each of the widgets. This means that there’s a constant effort on Flutter’s development to match the native UI behavior whenever there’s an update to the respective platform.
React Native only provides a few basic components. Though there are various UI kits available, it still cannot beat the widget collection that’s built into Flutter. The only downside with Flutter is that it has more widgets available for Android compared to iOS.
Native API access
Both React Native and Flutter support native APIs for commonly used features such as image picker and geolocation. If you need a native functionality that’s not available yet, you can always write custom platform-specific code or create your own native module.
App performance is difficult to judge because it purely depends on what type of app you’re going to build. If your app doesn’t require complex layouts, transitions, and passing data to and from the native side then you can choose either of the platforms.
If you’re interested in the actual numbers, there’s a post written about it: Examining performance differences between Native, Flutter, and React Native mobile development. The key takeaway of the post is that Flutter performs better than React Native.
Both React Native and Flutter have web support. The former being more of a community-led effort, while the latter is led by the Flutter team.
Web support for React Native is more mature than in Flutter. More importantly, you can add web support to an existing React Native project. Flutter doesn’t currently allow you to do this (at least not in a way that’s mentioned in the official documentation).
We’ll dive more deeply into web support for each platform later on in this article.
Testing and deployment
Does the platform support automated testing and deployment (provisioning, continuous integration, and deployment)? This can be optional for smaller projects, but crucial for larger ones. It helps us prevent bugs in our apps and not get bogged down by the manual deployment process.
Apps created with Flutter web is rendered on a canvas, so it works completely differently from your usual websites. At this point, websites created with it feel more like Flash rather than native HTML. Thus, its main purpose is for creating web apps rather than websites.
For a more detailed explanation of how it all works, please check out the following articles:
As for widgets, there’s Flutter Widget Livebook which is built using Flutter web. It allows you to preview which of the Flutter widgets are currently available for rendering on the web. When it comes to plugins, not all of them have web implementation (e.g. ARCore, Filesystem access). In pub.dev, you can filter the results to only show the plugins that are available for the web.
Flutter web isn’t ready for production yet. Someone has created a website with it but right off the bat, you can tell that there are a few problems. First, is that it’s not SEO-friendly and screen-reader-friendly. Everything is rendered on the client-side so the initial load times are slower. There’s also no support for native scrollbars as everything is re-implemented by the Flutter itself.
If server-side rendering is important to you then you’re out of luck because it’s out of the scope of the current implementation of Flutter web. The Flutter team has only added it as a future milestone. Which means that it’s not going to be available anytime soon.
- The feature set is complete enough to enable developers to build rich, interactive web experiences
- Access to the same widgets that are available for Flutter for mobile
- Full access to all existing Dart libraries that run on the web
- Integration with IDE’s and Text editors (IntelliJ, VS Code)
- Performance is sub-optimal
- Not production-ready yet
- Not screen reader or SEO friendly
- Not mobile-friendly because of the bigger build size and the usage of Canvas
- Cannot select, copy, or paste text
- Debugging is hard because you cannot use in-browser debugging tools
- Currently more suited for web apps and games rather than typical websites
- No support for legacy browsers
React Native Web
React Native Web is an open-source project by Nicolas Gallagher which makes it possible for developers to use React Native components and APIs on the web. The clear advantage of this is that it allows for code-sharing between multiple platforms. So you only have to write mostly single code and be able to run it everywhere.
Perhaps one of the first questions that comes to mind when you heard about React Native Web is why not just use React? The answer to that is code sharing. If you use React, you can’t really take advantage of the tools that React Native has to offer. And you’ll be coding the same functionality twice.
Do note that not all of the native components and APIs are available to the web. This is because not all of them have an equivalent web API. The main thing that React Native Web does is to map out the web equivalent for each of the React Native components and API. So the
<View> component becomes a
<Image> component becomes
<img> and so on.
The second question that might come to mind is what’s the difference between React Native Web and Expo Web? How do I know which one to use? Well, just like how there’s the standard CLI route for React Native projects, there’s also the quick start route which is Expo.
Expo Web is just a nice addition that you get when you’re already building your projects with Expo. So if you already have an existing project, the main deciding point is whether you’re building your project in the standard CLI way or Expo. However, if your project is new, it will solely depend on what your project is all about. If you think you’re going to need a lot of third-party libraries with native dependencies then you’re better off sticking to the standard React Native CLI. Otherwise, there’s really no reason not to use Expo since it just makes things that much easier for you.
React Native Web is much closer than Flutter web when it comes to being production-ready. Thus, developers who plan to use it can now worry about the following problems:
- Just like how there are platform differences between Android and iOS, the same is also true for the web. This means that your existing React Native code won’t immediately run on the web just by using React Native Web. You still have to do a lot of platform detection and code modification. This is especially true for things like the filesystem
- Not all APIs which are available in React Native is available in the browser. This means that if you have an existing React Native app, you either have to re-implement the functionality on your own or simply not support it at all when on the web. An example of this API is the Image Editor
- Not all components that are built into React Native or Expo works in the web. An example of this is the Modal component. Sometimes there’s another developer who would publish their own version of the component which can run on the web, but in cases where it’s not then you have to build your own component. The same thing is true for libraries, the authors will have to write their own implementation for the web
- Just like React Native, React Native Web includes tools that allow developers to build accessible apps. This is provided through accessibility props such as
- Allows for executing web-specific code via platform detection. We can’t expect all the APIs to work, thus it’s very important to detect the platform so that you only execute code that works for the current platform
- Support for server-side rendering. You can use
AppRegistryto hard-code the HTML document string to be used for rendering the app
- Expo web serves as a wrapper for React Native Web so you can start a single Expo project that can be run on Android, iOS, web and even desktop (through Electron)
- Expo web supports Progressive Web Apps
- At the time of writing this article, React Native Web is still in beta, thus it’s not production-ready yet
- React Native packages with native dependencies cannot be run on the web. If you’re relying on third-party packages with native dependencies (Swift / Objective-C or Java / Kotlin code) then those won’t work on the web
- React Native packages with no native dependencies cannot be expected to work 100% either. This is especially true if it involves animations, transitions, gestures, keyboard, and lists
Flutter pros and cons
To wrap up, let’s take a look at what makes each platform great and not so great. First up is Flutter. Note that we’re not going to go over the good and bad points that are similar to the two platforms. Instead, these points can either make you “yay” or “nay” when it comes to choosing the platform that’s best for you:
- Performance is very close to native because your code compiles to native code
- Lots of built-in widgets for both Material Design and Cupertino
- Built-in testing support
- Detailed and well-organized documentation
- Error messages are more meaningful because Dart is strongly typed
- Slightly steep learning curve
React Native pros and cons
Let’s now go over the pros and cons for React Native:
- Larger community
- Slower hot reloading on Expo when compared to a standard React Native project
- Documentation is sparse and not very detailed
- Limited native UI components
- It doesn’t have an official testing utility
On the other hand, Flutter web isn’t quite ready yet because of its performance problems. This is specifically brought about by their use of canvas. Though this might change in the future since the Flutter team is focused on improving the performance.
Overall, it’s an exciting time to be a developer because we can now reach more platforms than ever. All of that without leaving the comfort of the tools we know and love.
Plug: LogRocket, a DVR for web apps
LogRocket is a frontend application monitoring solution that lets you replay problems as if they happened in your own browser. Instead of guessing why errors happen, or asking users for screenshots and log dumps, LogRocket lets you replay the session to quickly understand what went wrong. It works perfectly with any app, regardless of framework, and has plugins to log additional context from Redux, Vuex, and @ngrx/store.