How to switch API hosts per environment in Capacitor + React + Next.js (Android)

Hi, I recently started using Capacitor.

I’m working on a single-page app built with React, Next.js, and Capacitor, and I’m trying to run it on Android.
I’ve successfully embedded the HTML/CSS/JS and made API requests to localhost:3000.

Now I want to prepare for production releases with continuous updates, so I plan to switch the API host based on the environment using code like this:

const host = process.env.NEXT_PUBLIC_API_HOST;
const request = async (path: string) => {
  const url = `${host}/${path}`;
  const response: HttpResponse = await CapacitorHttp.get({
    url: url,
  });
  if (response.status !== 200) throw new Error("failed");
  return await response.data;
};

The issue is that next build always uses NODE_ENV=production, so I can’t test the Android build with .env.development.

How can I test with a different API host (like development) on an Android device, without modifying the source code each time?

Any advice would be appreciated!

# package.json
  "scripts": {
    "android:build": "rm -rf out_capacitor && npm run build && cp -r out out_capacitor && rm -rf out_capacitor/api && npx cap sync",
    "reverse": "adb -s or8hifdyu4zp9hkv reverse tcp:3000 tcp:3000",

  "devDependencies": {
    "@capacitor/cli": "^7.4.1",

  "dependencies": {
    "@capacitor/android": "^7.4.1",
    "@capacitor/core": "^7.4.1",
    "@capacitor/status-bar": "^7.0.1",
    "next": "^15.3.1",
# next.config.ts
const isDev = process.env.NODE_ENV !== "production";

/** @type {import('next').NextConfig} */
module.exports = {
  output: "export",
  distDir: isDev ? "out_dev" : "out",
  trailingSlash: true,
};
# capacitor.config.ts
import type { CapacitorConfig } from "@capacitor/cli";

const config: CapacitorConfig = {
  appId: "com.toramilab.investool",
  appName: "investool",
  webDir: "./out_capacitor",
  plugins: {
    CapacitorHttp: {
      enabled: true,
    },
  },
};

export default config;

It looks like you can run (source):

NODE_ENV=development next build

Otherwise, please outline the exact commands you are running to build your app to Android.

Thanks for your reply. I followed your instructions and ran the command after making the following changes:

// next.config.js
module.exports = {
...
  experimental: {
    allowDevelopmentBuild: true,
  },
};

// .env.production
NEXT_PUBLIC_API_HOST=https://production.my.app

// .env.development
NEXT_PUBLIC_API_HOST=http://localhost

// index.tsx
<h1>{process.env.NODE_ENV} {process.env.NEXT_PUBLIC_API_HOST}</h1>

Then I ran:

$ NODE_ENV=development next build

It gave this warning:

⚠ You are using a non-standard "NODE_ENV" value in your environment. This creates inconsistencies in the project and is strongly advised against. Read more: https://nextjs.org/docs/messages/non-standard-node-env
▲ Next.js 15.3.1
- Environments: .env.production.local, .env.production
- Experiments (use with caution):
  ✓ allowDevelopmentBuild
$ npx cap sync
$ npx cap run android --target or8hifdyu4zp9hkv

On the Android app, it displayed h1 tag:
"development https://production.my.app"

So although NODE_ENV=development was accepted by the build, it still loaded variables from .env.production.

Interestingly, I found this Dockerfile in the official Next.js repo that builds a staging environment:
Next.js multi-env Docker example (staging)

# This will do the trick, use the corresponding env file for each environment.
COPY .env.staging.sample .env.production

It’s a bit tricky, but it seems like it could work for my use case.

What do you think in the context of Capacitor? Is there any best practice you’d recommend for handling different environments (like dev/staging/prod) when building for Android?

Thanks again!

Not really sure then. It’s really a Next.js problem. Capacitor just syncs (copies) whatever files have been built.

If you are just testing with a device you have (or emulator), you could use npx cap run with live reload and point it to your dev server running.

It’s really a Next.js problem

You’re absolutely right — the NODE_ENV and .env handling is a Next.js issue.
However, what made this particularly tricky for me is that, unlike native Android apps where we can use build variants to switch API hosts and other environment variables, Capacitor apps don’t have that mechanism out of the box.

you could use npx cap run with live reload and point it to your dev server running.

I actually tried this approach at first.
If you’re just testing an SSG app or a static website, using a dev server with live reload is often sufficient.
But for actual releases of Capacitor apps, we typically embed the HTML/CSS/JS into the app, run cap sync, and then build the Android app. In this setup, the app fetches data from a remote API server from within the embedded client — a very different runtime environment, which introduces many issues.

For example:

fetch("/api/some.json");

This works fine when the SPA is hosted on the same domain as the API. But in the production Android app, the SPA is embedded locally, and it needs to talk to a remote server — so the host can’t be resolved.

To handle that, I rewrote it like this:

fetch(`${host_each_env}/api/some.json`);

In many real-world cases, we want to internally distribute QA builds pointing to a staging API server, especially for testers.
But I couldn’t find much documentation or community guidance around this kind of use case, so maybe I’m misunderstanding some best practices here.

In any case, I’ll try going with the workaround of swapping out .env.production during the build process.

Thanks again for the discussion!