React Native Web at Headout

It used to take us 5 engineers to ship a new project:

  • 2 from Web, as mobile and desktop web were separately built
  • 1 from the backend team
  • 1 from the Android team
  • 1 from the iOS team

Add QA testing overhead on top of each platform 😫

We started adopting React Native in 2020, a framework that helps us ship both Android and iOS apps from a single cross-platform codebase. This let us ship each project with 4 engineers. Like most startups, we had more work on our product roadmap than ever. We had a very lean frontend engineering team. We wanted to move faster and this meant shipping more with lesser people involved, amazing developer experience and fewer points of failure.

Announcing React Native Web at Headout 🥳

This finally allows us to build once and render everywhere.


What is React and React Native for non engineers


React is an open-source javascript library, originally built by Facebook. It is used to build large scale websites and is one of the most common setups used by modern frontend engineering teams.

React Native is an open-source component framework, again built by Facebook. It is used to build mobile apps that run natively on Android and iOS, with one codebase. It can also be used to build Windows, MacOS and Web apps.

Building apps in React Native over native mobile apps sped up engineering at Headout in many ways. It improved developer experience by getting things like hot reloading, engineers didn’t have to wait for the whole app to compile to see changes they made. We could bring in web engineers into mobile engineering projects because react native code is written in JavaScript and is based on the React philosophy. Our teams were also able to push out hot fixes and updates skipping App Store review times directly. But the major benefit here was to build features once and launch them on both iOS and Android app stores.

The best thing about React Native, in-fact is that it’s a universal UI language. You can use it to ship things to any platform. React (engineers read react-dom) uses building blocks like <div>, <input> and <p> whereas React Native uses <View>, <TextInput> and <Text>. What this allows us to do is to build extensions that make React Native work on new platforms. React Native was originally supported just for making Android and iOS apps. Microsoft soon started leading the development of React Native on desktop platforms for MacOS and Windows. Twitter started experimenting with React Native on the Web. It finally lets us build one component and use it across all platforms.


🛠 Frontend engineering at Headout

Headout’s frontend mainly has two repos – proteus and next-deimos.

Proteus contains the Headout app’s source which is built in React Native. It previously used to be split into separate iOS and Android apps natively written in Swift and Kotlin respectively. We incrementally migrated to React Native and moved all code in one place. Some of the native old code still resides in Proteus.

Next-Deimos on the other hand powers Headout’s consumer website. It’s built with React and we use Next.js to render things on the server before serving it to our users.

Our goal was to merge these 2 codebases and power both apps and web via a single codebase. We first decided on building an MVP for the Help FAQ page (headout.com/help) which didn’t affect business and was non critical. It had very few moving parts and the design was relatively simple. I started on perfecting dev tooling in ad-hoc for us while working on other app projects.

We first explored rendering the current Proteus app repo’s Help Screen with Next.js. This will be live on to some url (say: proteus.app/help) which could later be rewritten to headout.com/help from our web codebase. We had a lot of discussions surrounding it and while it was theoretically possible and while we were able to make it work to an extent, we faced a lot of errors and issues while practically implementing it.

After this, we figured it would be better to have a separate repo – called Aqua which contained the shared business logic and common components. These functions and constants could be exported from Aqua and then further imported into Proteus and Next-Deimos separately. For development, we could’ve used a monorepo with yarn workspaces but we settled for another solution I talk about shortly.

🤔 How

Challenges and how we mitigated every one of them

⚡️ Development and Hot Reloading

The first thing we needed was to make hot reloading work.

The problem – Metro bundler which is used in react-native does not support symlinks in node_modules. We could not go ahead and use yarn link as we normally would with any other framework.

The solution – After trying many things, we figured yalc worked best for our usecase in development. Yalc is a simple local NPM repository for locally developed packages. Whenever we make some changes in our local package – Aqua, we trigger a local build and use yalc publish to push the build to our local npm store. This is then picked up by our parent repos – Proteus and Next Deimos automatically and they’re reloaded!

Some things we had to do in order to get Hot Module Reloading (HMR) working on Next.js:

  1. Use next-transpile-modules and add our local dependency package – Aqua
  2. Edit next.config.js and add the following code, be sure to change package names.
/*
 * Make HMR work in all ./node_modules/@headout/ libraries
 * Needed after the next.js 12 upgrade along with transpiling the @headout libraries
*/

config.snapshot = {
    ...(config.snapshot ?? {}),
    managedPaths: [
        /^(.+?[\\/]node_modules[\\/])(?!@headout)/,
    ],
};

// Replace the '**/node_modules/**' with a regex that excludes node_modules except @headout
config.watchOptions = {
    ...(config.watchOptions ?? {}),
    ignored: [
        '**/.git/**',
        '**/node_modules/!(@headout)**',
        '**/.next/**',
    ],
};

Doing this is required with Next.js > 12 as it uses Webpack 5 and it has strict develop caching. Without this, you’d have to delete the .next folder every time you make a local change in your local package.

🎨 Responsive Styling

A big hurdle we faced was to get responsive styling working without using if-else statements on screen widths and make the code readable. There were a few existing libraries that did this but we didn’t like the syntax they’ve used.

We started making our own in-house responsive styling library that works on both web and apps with a very simple and easy-to-read syntax. We’ve been using this in production and it’s been going great! We plan to make this library open-source soon.

🌎 Screen Navigation

We use react navigation on our apps codebase – Proteus and Next.js’s default navigation on web. To make this work in React Native Web, we created a simple custom hook that exposed both web and app navigation params that we could pass separately.

This isn’t an ideal solution because you’d ideally want to have parity on the navigation params and naming conventions. But we settled on a simple hook until we start using React Native Web more extensively. We can easily decide conventions later and run a simple codemod to refactor existing hook code.

🎬 Animations

React Native does not support CSS animations on the web and we have to use the Animated API or Reanimated v2 to make animations work. Or we could use something like Framer Motion or react-spring which work beautifully on both platforms.

To run motion graphics, we’ve been using Lottie and it works perfectly.

📂 Using Third Party Libraries and Native Modules

Most react native libraries that we will find do not support web yet. To work around this, we’ve been creating custom components that we need, for example, for rendering Google Maps, we use react-native-maps and for the web, we use react-gmaps. We have separate component files for web and apps – Map.web.tsx and Map.tsx. This lets React Native know that we’ll be bundling the .web.tsx files on web platforms.

We had a lot of fun and learning while experimenting with React Native Web. We are planning to migrate more pieces to react native web and are excited about the future. There are bound to be challenges when working on cutting edge, but every problem is solvable when you have strong motivation, continued rigor and a stellar team. The benefits of using something powerful like this are ten fold given a month of engineering research.

Sounds interesting? Headout is hiring! Join us and build the future of how the world discovers the best real-life experiences! Apply here