ClaudeExpoJul 21, 2026, 1:30 PM

Install dev and production side by side with app variants

A condensed section focused on the key takeaways first.

Original Post

Quick Digest

Summary

A condensed section focused on the key takeaways first.

claudeen

Install dev and production side by side with app variants Summary

Key Points

  • Point 1: So, you just shipped your app to production.
  • Point 2: Congrats!
  • Point 3: Users are installing it, but soon your first bug report comes in.

Summary

This is an English summary of "Install dev and production side by side with app variants" published on 2026-07-21.

Key Points

  • Point 1: So, you just shipped your app to production.
  • Point 2: Congrats!
  • Point 3: Users are installing it, but soon your first bug report comes in.

Full Translation

Translations

A translation section that keeps the flow of the original article.

claudeja

Install dev and production side by side with app variants(原文タイトル)

概要

公開日: 2026-07-21 翻訳生成に失敗したため、原文をそのまま保存しています。

原文

So, you just shipped your app to production. Congrats! Users are installing it, but soon your first bug report comes in. You open the production build on your phone, and yep, there it is. You draft a fix, install your development build, and dig in. You can only have one version of your app installed on your phone at a time. Sounds reasonable, until you’re uninstalling your production app for the third time this week to debug something in your dev build, then reinstalling it, then uninstalling it again. There has to be a better way. App variants fix this by giving each build its own identifier, so variants (for example, dev, preview, and production) can be installed side by side on one device, each treated as its own app. For me this is a genuine dev experience upgrade, and it is the first thing I set up on real projects. What an app variant is made of Think of every build as having two independent settings, an identity and an environment. The first is identity, as in the bundle identifier on iOS or package name on Android, such as com.myapp.app. An identity is baked into the native build. It decides what represents a unique app and whether a new install replaces an old one. A device keeps only one app per identifier, so if development and production builds share one, installing either replaces the other. Giving each variant its own identifier lets them stay installed side by side. Nothing you do at expo start time changes the identity. Because the native identity is baked into the native code of your app, this guide assumes custom builds you create on your machine or with EAS Build. It does not apply to Expo Go, which runs every project under a single identity. The second is environment. That means the set of variables that gets loaded when your app config is evaluated. EAS has three built-in environments, development, preview, and production, and the environment is where values like an API URL or an analytics key live. Environment decides how the app behaves once it is running. Step 1: config your app for different variants Chances are your project has a static app.json today. A static file is great for stable values. Like we already covered, for variants to install next to each other they need identifiers of their own. But the identifier is set in your app config, and a static file can only hold one. So you also need a config that can set its values based on a variable. That is a dynamic config, and I reach for app.config.ts so the config is typed, though app.config.js is fine if you would rather stay in plain JavaScript. The two config files When both files exist, Expo reads app.json first, hands it to your dynamic config as { config }, and uses whatever the dynamic config returns. Think of app.json as the base layer and app.config.ts as a thin override on top. Your dynamic config has to export a function for this to work. If it exports a plain object while an app.json exists, the static file is ignored. Your app.json holds the stable values and the defaults. { "expo": { "slug": "my-app", "owner": "your-org" } } And your app.config.ts holds the overrides. A simple example may look like this. import { ExpoConfig, ConfigContext } from "expo/config"; const appName = "MyApp"; export default ({ config }: ConfigContext): ExpoConfig => ({ ...config, name: appName, }); Switching on APP_VARIANT So, to create an app variant, we need to tell the config which one it is building. For that, we bring in APP_VARIANT, a plain environment variable your config reads. The name is only a convention, so you could call it anything. You can also create more, or different variants if you like. Switch on it to pick the identity. import { ExpoConfig, ConfigContext } from "expo/config"; const APP_ID_PREFIX = "com.myapp"; function getName(base: string) { switch (process.env.APP_VARIANT) { case "production": return base; case "preview": return ${base} (Preview); default: return ${base} (Dev); } } function getAppId() { switch (process.env.APP_VARIANT