←  Back to overview
Work Talk

How We Build Native & Web Apps Using the Same Codebase

When we build a digital product that's supposed to work on more than one platform, about 95% of the code is shared. Here's how we make that happen and why.

Why share code between platforms anyway?

It’s not a secret that we're big fans of the React ecosystem. Generally speaking, most web projects at Bothrs are created using Next.js while the mobile applications we develop are built using Expo; both of which utilize React to create the respective user interfaces.

It regularly happens that a project doesn’t just need a web platform, or a native app; clients often  like to have both and that makes perfect sense. Even if it’s not included in the initial scope of the project, a lot of mobile projects will need some kind of a website at some point. If you don’t believe me, try finding three different apps on your phone that don’t have any kind website associated with them.

That being said, the whole point of sharing code is twofold: On the one hand, it makes it a lot easier to get the user experience to feel consistent when largely the same code is being used. On the other, it drastically lowers our lead time, given we don't need to write everything twice and do less debugging overall.

Possible scenarios

Let's quickly go over a few scenarios that can occur, when we both need web and native presence for a specific client project. Everything largely depends on the platforms our client's customers most commonly use, or whether having a web counterpart even makes sense at all.

  • It can be as simple as a landing page with a button to download the app. This is the case for the COVIDSafe or BeReal app.
  • Some services like Instagram or Twitter for instance, operate using a full-fledged web application that feels like a copy of the mobile app. It has full feature parity.
  • Another approach is to still have feature parity, but make each version more customized to fit the platform, I.E. rearrange the layout to make better use of screen real estate or show more detailed information. Strava is good example of an application that takes this route.

In either case, it's best if both the website and the mobile application have the same look and feel, so it makes all the more sense to share code between these platforms. This article goes over how we achieve this at Bothrs; sharing as much code across platforms as possible to maintain both speed and quality.

How we develop native and web applications

Expo & React Native

Let me first introduce you to one of the protagonists of this fairy tale: Expo. Expo allows us to write high quality apps that are used by thousands of users without having to think about the differences between iOS and Android. It provides us with a nice toolbox to create, test and share apps easier than ever before. If you want to know more about this framework, we've dedicated a separate article to this subject which you can check out here.

Next.js

When building for the web, there's an infinite number of technologies to choose from. With every modern framework out there, you’ll most likely be able to create highly performant websites. But Next.js, in our humble opinion, makes this extremely easy to do so.

I won’t go into too much detail as to why we love Next.js, so here are some quick pointers:

  • It's very SEO-friendly
  • It's easy to optimize pages by means of caching
  • It provides a great developer experience overall
  • It requires little setup to get going
  • It's easy to create back end API routes
  • It bolsters an amazing community
  • Vercel, the company behind Next.js, is a leading player in the React ecosystem

Going fully cross-platform using Solito

Now that we've established the tools and frameworks we use for web or native applications, it's time to bring them both together to create a truly unified codebase. To do that, we use Solito. This small framework allows us to combine Next.js and React Native to build powerful cross-platform applications. It allows for cross-platform development with minimal amounts of custom code needed for each platform.

Routing

While web and native applications can share a lot of logic and UI components, taking the user from page to page works a bit differently under the hood.

On the web, routing is URL-based. Any given URL, take for instance https://yoursite.com/nl/home?darkmode=true, contains two critical pieces of information:

  1. nl/home → This part of the URL indicates where the user is on the website. The added benefit here, is that a user's "location" in the app so to speak, can be shared.
  1. darkmode=true → This part contains some extra information that can alter the user experience in some way, like having a dark mode.

A native application on your phone however, does not have an address bar where you can input URLs. Luckily, Solito creates an abstraction of this, meaning we can now use one system, allowing us to use URL routing on both platforms. They achieved this by exposing some extra components and hooks that developers can use.

Architecture

Solito at its core is a monorepo with a Next.js and Expo project inside of it. Monorepos allow you to work on related software, like a React Native mobile app and a Next.js web app, within the same git repository. The folder structure looks something like this:

In the root of a project, there are 2 important folders: Apps and packages. You can think of Solito as the “glue” between Next.js and Expo. In the apps folder, you'll have both a regular Next.js project and an Expo project.

The package folder contains all the logic and UI components that both projects can share. The code is structured per “feature”, similar to how an architecture with modules would work. For every feature, the logic and UI can be found here. Finally, the UI is built using React Native, which can be then converted to HTML for web use.

Dynamic

Although sharing all your screens and features between native and web might sound like dream, this isn’t always the case actually. There are plenty of use cases where you want to be able to create different flows depending on the platform.

Let’s take an ecommerce platform as an example. The first page the users sees upon loading the app or website have different goals. On the web, there are two goals:

  1. Convincing visitors that their brand is the one they should buy from.
  2. Making conversions happen.

With native apps however, usually only that second goal matters. Because if you’ve downloaded a shopping application, you’re most likely already committed to buy from said brand.

Luckily, Solito gives you the flexibility to share a complete screen when you want to, but if for some reason you want a user on the web to see a variation of a page, you can do this as well. This allows you to differentiate the user experience between platforms, like how Strava does.

Embracing the native features of all platforms

Server-side rendering

SSR or server-side rendering means that the browser sends a request to the server, the server generates the HTML, and the browser then displays said HTML. While this sounds impressively unimpressive, this process has some advantages to it that are crucial to certain businesses.

If the HTML is not being rendered on the server, web crawlers (AKA search engines) have no clue what content can be found on what page, resulting in poor SEO rankings. When using Solito, which uses Next.js under the hood, it’s super easy to do this. SEO is not the only motivation for using SSR, mind you. In most cases it’s also faster for the user, which is something you should always strive towards.

Animations

When you want an app to feel smooth, snappy, luxurious, dynamic, fancy… Look into animations. Apple for examples, has spent a ton of time in making iOS feel as smooth as possible, and users have come to expect the same level of quality from third-party applications.

Well we're in luck once again, because there are a few libraries available that make implementing animations between states etc. a breeze. My personal favorite is Moti. This is a library that based its API on the popular design and prototyping tool Framer. This API is super developer-friendly and makes animating almost entirely painless. And, should you want more complex animations, you can always use React Native Reanimated. The learning curve is pretty steep, but the options are endless.

Semantic HTML

For SEO and accessibility reasons, on the web it’s important to write HTML that's "semantic”. This means that you need use the correct HTML tags for the correct parts of the website. For example, the main title of the page should be wrapped in an H1 tag.

This might sound super logical, but when using Solito, you’re not writing regular HTML. Here there's no explicit difference made between a title, subtitle or paragraph. Luckily the Expo team came up with a solution and created the “HTML Elements” package that solves this issue. Yet another win for Solito!

Conclusion

If there's anything you should take away from this, it's that cross-platform development has come a long way. A couple of years ago, it was totally normal to have at least 3 front end codebases laying around for your digital product: One for your iOS app, one for your Android app, and one for your website. The logic to add a product to a cart, navigate to the next screen, sign up for your service, register for your premium plan… It all had to be written and tested 3 separate times.

Now, cross-platform development has matured a ton and more and more people have started using tools like React Native to share most of their code between iOS and Android. I truly believe that tools like Solito will help evolve cross-platform development in a way that allows teams to ship products for multiple platforms from one and the same codebase.

Instead of reinventing the wheel and creating some new wacky framework, the creator of Solito decided to use what’s already been battle-tested and simply glued these things together.

Join Bothrs

Bundle forces and let’s create impactful experiences together!
See positions

Sign up for your free roadmapping workshop!

Thank you! Your submission has been received!
Oops! Something went wrong while submitting the form.