freedomlife: The Tech Behind The Scenes

19 June 2024  •  --------

Available in .

Previously, I've covered behind-the-scenes of freedomlife in general. Now, I write this to cover the technical parts. This post will focus on how the freedomlife app was built and the technologies behind it.

Tech Stack


In the beginning, when I built the first version of freedomlife (2019-2020), the app was a Next.js application that was only accessible from the web. Back then, material design was also a hot trend, so I decided to use Material-UI as the UI component library. All the guide and Bible data used by the app were stored in a MongoDB database.

Tema Basic
Tema Valentine
Tema Natal


The freedomlife app is growing; now the app is not only available on the web but also available on iOS & Android. The tech stack has also evolved to accommodate these changes. I'm now using a combination of Next.js & Expo app.

The foundation of those 2 frameworks is React. React is known as the UI library for the web, but React also can be rendered on multiple platforms outside the web. Expo uses React Native to make React can be rendered natively on iOS & Android (or even macOS & Windows).

Due to the changing tech stack, I also need to use a more universal styling system for it. The main feature I require is the ability to work on web, iOS & Android. There are several options, but I chose NativeWind. Since NativeWind uses Tailwind CSS under the hood, it is easier for me, as I'm already familiar with Tailwind's atomic class system.

Because of its growth, freedomlife also requires more Bible versions. Previously there was only one version available to read, but now there are multiple versions to read in both Indonesian & English.

That made me change the database to a relational database that also has cloud service available. I chose Supabase, which not only offers a PostgreSQL database but also a storage service that perfectly fits freedomlife's use case.



Initially, the app architecture was simple since it was only a Next.js application that was deployed to Vercel. Next.js enabled me to build this full-stack app by combining the frontend and backend in the same codebase using its API Routes feature.


The freedomlife app is now a monorepo app. I use yarn workspaces and Turborepo to manage this monorepo.

I chose yarn workspaces because Expo provides first-class support for issues that may arise when using Expo with yarn workspaces (Yarn v1 classic).

Here is a simple overview of the freedomlife architecture:

Monorepo Pros

  • Reusability — Components that are written can be reused on 2 different platforms (web & native). In my opinion, this makes it easier for me to manage them.
  • One Dependency — Using one version of a dependency across different platforms simplifies debugging, fixing issues, and even replacing dependencies.
  • Isolated Changes — This architecture encourages smaller, atomic changes rather than merging large chunks of code. Atomic and isolated changes also make it easier to roll back if something goes wrong.

Monorepo Cons

  • Complexity — The hassle of building monorepo lies in the initial setup. That's why I use a tool like Turborepo to make the setup easier and straightforward.
  • Tooling Overhead — Although there are many templates or boilerplates available, customizing them to fit our needs takes a lot of time & energy. Apart from that, I also need to learn how to manage dependencies within the monorepo and change the way I code to be more careful so it won't cause circular dependency issues.


Currently, freedomlife only has 2 data schemas: Bible and guides. All data is stored in the Supabase database. To access this data, I built API endpoints: /api/guide is for the guides and /api/bible is for the Bible.

As I covered in the previous post, the guide data is sourced from Gereja Kristen Kemah Daud Yogyakarta (which in English translates literally to "David's Tabernacle Church"). The Bible data is sourced from and YouVersion bible. The Bible data retrieval process is inspired by the Alkitab API project created by Sonny Lazuardi.

To make the Bible data downloadable, I also store the Bible data in a JSON format in Supabase storage.

Code Building Blocks

Handle Platform Differences

In a monorepo project, there are times when I need to handle platform differences. Some code needs to be run exclusively on the web, while others should only run on natives. To handle these differences, Expo provides a webpack configuration that we can integrate with Next.js, which can be found here. I also need to install react-native-web library, maintained by Necolas (Meta) to enable React Native to run on the web and handle platform differences.

For example, when I want to create a component with different implementations on web and native, I can use the .web suffix to the file name like this:

export default function Text() {
  return (
      <Text>iOS & Android</Text>
export default function Text() {
  return (
      <p>Web only!</p>

This approach can also be applied outside of components. For instance, in freedomlife codebase, there is a hook called use-safe-area that measures the safe area for rendering content. This hook is only needed on the native app and is not used on the web. So, I can write it like this:

import { useSafeAreaInsets } from 'react-native-safe-area-context'

const useSafeArea = useSafeAreaInsets

// Name and the export method must be the same.
// If one uses `export default`, the other one should follow.
export { useSafeArea }
// Name and the export method must be the same.
// If one uses `export default`, the other one should follow.
export function useSafeArea() {
  return {
    bottom: 0,
    left: 0,
    right: 0,
    top: 0,

You can see the full code implementation of use-safe-area mentioned above, here.


I group all pages/screens inside a features folder. This approach is inspired by the Solito library, which is also used by freedomlife. All entry points whether on the web and the native app, will import the pages from this folder. Here's an example:

./apps/web/pages/index.tsx & ./apps/native/app/(tabs)/index.tsx
// Entry point
import HomeScreen from '@repo/app/features/home'

export default HomeScreen
export default function HomeScreen() {
  return (
      <Text>Home Screen</Text>

If needed, all the pages inside the features folder can also handle platform differences by using the approach I explained previously.


In freedomlife, I group all the components that are small building blocks with minimal or no logic at all inside of them — only semi-pure and pure components. These components are reusable across both web and native platforms. You can see the components inside the codebase here.


Lastly, all the code that functions as a helper or utility is placed in this folder. This includes the use-safe-area hook that I covered in the previous section.



I use Vercel as the deployment platform for the web version. As I mentioned earlier, I have been using Vercel since the first version of freedomlife. Vercel automates the CI/CD process so I can focus on building the project. For database & storage, I use the Supabase cloud service.

iOS & Android

When I needed to perform native app deployment, I searched for a service with features similar to Vercel. I needed something that could start for free and automates the processes, given that the native app deployment process is complex and time-consuming. After some research, I discovered EAS (Expo Application Services) offers all these capabilities.

  • EAS Build — Automates the build process for iOS & Android apps.
  • EAS Submit — Automates the submission process to the Apple App Store & Google Play Store.
  • EAS Update — Allows me to deploy over-the-air updates (critical or trivial bug fixes) to the app installed on users' devices.

EAS makes native app deployment feel like web deployment — easy, fast, and efficient. I highly recommend it if you want to give it a try.


If you are interested in exploring further, the freedomlife source code is open-source and can be accessed on GitHub. If you have any suggestions or ideas for the app, feel free to drop it on freedomlife's GitHub issue page.

In the end, for me, there is no perfect technology. So, I choose to use technologies that I can be happy with and tolerate the trade-offs. 😄