Experiencing Extended Build Times (2hrs+) for AAB Package Using Custom Dev Client

Summary

We are currently working on an app that originally utilized Expo Go, but as the project evolved, we transitioned to a custom dev client. Building with the dev client has been efficient, taking less than 11 minutes. Similarly, using Expo Go for APK or AAB (Android App Bundle) builds also stayed within an 11-minute timeframe.

However, we recently encountered a significant increase in build times when attempting to build an AAB package for submission to the Google Play Store. This process now takes over 2hrs and times out, which is considerably longer than expected. We are subscribed to the Expo Pay-as-You-Go plan.

Furthermore, it came to our attention that the build process utilizes the Android medium worker by default, which has an estimated build time of 2 hours. Even more puzzling, the build timed out at the 2-hour mark.

Our primary concerns are as follows:

Is the 2-hour build time typical for a production build with a custom dev client? This is our first experience with deploying to production using the dev client, and we are uncertain if the lengthy build time is expected.

Could there be a configuration issue or any other factor causing this extended build time? We want to ensure that we are optimizing our build process and addressing any potential bottlenecks.

If a 2-hour build time is standard, how can we switch to the Android large worker? We understand that the Android large worker may offer better performance. Could you provide guidance on switching to this configuration and clarify any potential timeout limits associated with it?

Managed or bare workflow?

managed

What platform(s) does this occur on?

Android

Package versions

{
  "scripts": {
    "start": "expo start",
    "dev": "expo start --clear",
    "android": "expo start --android",
    "ios": "expo start --ios",
    "web": "expo start --web"
  },
  "dependencies": {
    "@expo/config-plugins": "~7.2.2",
    "@hookform/resolvers": "^3.3.1",
    "@miblanchard/react-native-slider": "^2.3.1",
    "@notifee/react-native": "^7.8.0",
    "@react-native-async-storage/async-storage": "1.18.2",
    "@react-native-firebase/app": "^18.4.0",
    "@react-native-firebase/messaging": "^18.4.0",
    "@shopify/flash-list": "1.4.3",
    "@tanstack/react-query": "^4.35.3",
    "@types/lodash.result": "^4.5.7",
    "@ventlio/tanstack-query": "0.2.78",
    "axios": "^1.5.0",
    "expo": "^49.0.9",
    "expo-clipboard": "~4.3.1",
    "expo-constants": "~14.4.2",
    "expo-crypto": "^12.6.0",
    "expo-dev-client": "~2.4.8",
    "expo-file-system": "^15.4.3",
    "expo-font": "~11.4.0",
    "expo-image": "~1.3.2",
    "expo-image-picker": "^14.5.0",
    "expo-linear-gradient": "~12.3.0",
    "expo-linking": "^5.0.2",
    "expo-location": "~16.1.0",
    "expo-modules-autolinking": "~1.5.0",
    "expo-router": "2.0.0",
    "expo-secure-store": "^12.5.0",
    "expo-status-bar": "~1.6.0",
    "expo-updates": "~0.18.12",
    "lodash.debounce": "^4.0.8",
    "lodash.result": "^4.5.2",
    "moti": "^0.25.3",
    "nativewind": "next",
    "react": "18.2.0",
    "react-dom": "18.2.0",
    "react-hook-form": "^7.46.1",
    "react-native": "0.72.4",
    "react-native-checkbox-reanimated": "^0.1.1",
    "react-native-date-picker": "^4.3.3",
    "react-native-document-picker": "^9.0.1",
    "react-native-gesture-handler": "~2.12.0",
    "react-native-logs": "^5.0.1",
    "react-native-otp-textinput": "^1.1.3",
    "react-native-reanimated": "3.4.2",
    "react-native-reanimated-carousel": "^3.5.1",
    "react-native-reanimated-viewer": "^1.3.1",
    "react-native-safe-area-context": "4.6.3",
    "react-native-screens": "~3.22.0",
    "react-native-select-dropdown": "^3.4.0",
    "react-native-svg": "13.13.0",
    "react-native-toast-message": "^2.1.6",
    "react-native-uuid": "^2.0.1",
    "react-native-web": "~0.19.6",
    "socket.io": "^4.7.2",
    "socket.io-client": "^4.7.2",
    "toggle-switch-react-native": "^3.3.0",
    "yup": "^1.2.0",
    "zustand": "^4.3.9"
  },
  "devDependencies": {
    "@babel/core": "^7.19.3",
    "@babel/plugin-proposal-export-namespace-from": "^7.18.9",
    "@expo/metro-config": "~0.10.0",
    "@expo/metro-runtime": "^2.2.11",
    "@expo/webpack-config": "^19.0.0",
    "@types/expo": "^33.0.1",
    "@types/react": "~18.2.14",
    "eslint-config-react-native-wcandillon": "^3.10.0",
    "expo-module-scripts": "^3.1.0",
    "tailwindcss": "^3.3.2",
    "ts-node": "^10.9.1",
    "typescript": "^5.1.3"
  },
  "resolutions": {},
  "overrides": {
    "metro": "0.76.0",
    "metro-resolver": "0.76.0"
  },
  "name": "evone-dating-app",
  "version": "1.0.0",
  "private": true
}

Environment

expo-env-info 1.0.5 environment info:
    System:
      OS: Linux 5.15 Linux Mint 21.1 (Vera)
      Shell: 5.8.1 - /usr/bin/zsh
    Binaries:
      Node: 18.16.1 - /usr/bin/node
      Yarn: 1.22.19 - /usr/bin/yarn
      npm: 9.5.1 - /usr/bin/npm
    npmPackages:
      @expo/metro-config: ~0.10.0 => 0.10.7 
      @expo/webpack-config: ^19.0.0 => 19.0.0 
      expo: ^49.0.9 => 49.0.9 
      react: 18.2.0 => 18.2.0 
      react-dom: 18.2.0 => 18.2.0 
      react-native: 0.72.4 => 0.72.4 
      react-native-web: ~0.19.6 => 0.19.7 
    npmGlobalPackages:
      eas-cli: 5.2.0
    Expo Workflow: managed

We are using two metro configs, one in js and the other in typescript, the js is just importing the typescript version

// metro.config.js
require("ts-node/register");
module.exports = require("./metro.config.ts");

// metro.config.ts
import { getDefaultConfig } from '@expo/metro-config';
const withNativewind = require('nativewind/metro');

const config = withNativewind(getDefaultConfig(__dirname));

module.exports = config;

Stacktrace (if a crash is involved)

No response

Hi @osmaxin

I’m having a bit of trouble with the terminology you’re using.

I’m not sure exactly what you mean by “building with the dev client”.

Also, I’m not sure what you mean by “using Expo Go for APK or AAB builds”.

What commands are you running to create these builds?

So in other words, before that it was building in a reasonable amount of time, and then without making major changes to the app it suddenly started taking over two hours to build?

I’m not 100% sure what you mean by this :slight_smile: Where did you see this estimated build time?

As per the pricing page:

Build timeout 45 minutes for the Free plan
2 hours for paid priority builds
The maximum amount of time for which a build job may run before it is stopped.

Again, I’m not sure what you mean by “with a custom dev client” here. A custom dev client is basically the opposite of a production build. It is another name for a development build. So basically a custom version of Expo Go that you use with npx expo start.

But, no. If it was building in 11 minutes before, then 2 hours is definitely not expected.

Do the build logs give you any indication as to what part of the process is taking a long time?

You can specify the resourceClass in your build profile in eas.json. You can do this at the top level of the build profile if you want it to apply to both Android and iOS, or else you can put it only inside the android.

e.g.:

{
  "build": {
    "production": {
      "android": {
        "resourceClass": "large"
      }
    }
  }
}

The timeout should still be two hours.

I’ve had a brief look at your dependencies. I believe if you run npx expo-doctor it will complain like this:

$ npx expo-doctor
✔ Check Expo config for common issues
✔ Check package.json for common issues
✔ Check dependencies for packages that should not be installed directly
✔ Check for common project setup issues
✔ Check npm/ yarn versions
✔ Check Expo config (app.json/ app.config.js) schema
✔ Check for legacy global CLI installed locally
✔ Check that native modules do not use incompatible support packages
✖ Check that packages match versions required by installed Expo SDK
✔ Check that native modules use compatible support package versions for installed Expo SDK

Detailed check results:

Some dependencies are incompatible with the installed expo version:
  react-native@0.72.4 - expected version: 0.72.6
Your project may not work correctly until you install the correct versions of the packages.
Fix with: npx expo install --fix
Found outdated dependencies
Advice: Use 'npx expo install --check' to review and upgrade your dependencies.

One or more checks failed, indicating possible issues with the project.

These days (if you’re on Expo SDK 47 or higher) you should probably not have this as a dependency. Any config plugins should import from expo/config-plugins (without the “@”). (e.g. see here.)

Do you need this? I think it would normally be installed as a dependency of something else.

I’m unfamiliar with this, but if I look it up I see this:

This proposal has been merged to the ECMAScript standard and thus this plugin is no longer maintained. Please use @babel/plugin-transform-export-namespace-from instead.

Also, if I go here: @babel/plugin-transform-export-namespace-from · Babel
I see this:

:exclamation: INFO
This plugin is included in @babel/preset-env, in ES2020

so it seems like you should be able to uninstall @babel/plugin-proposal-export-namespace-from.

For Metro, these days you should import from expo/metro-config (without the @). See here: Metro bundler - Expo Documentation

Do you need @expo/metro-runtime?

Do you need this?

Why do you need this?

I am unfamiliar with this. Is this just so that you can use TypeScript for as much as possible?