All articles
Mobile DevelopmentSeptember 5, 2026 7 min read

Background Tasks in React Native 2026: What Actually Runs When the App Is Closed

Background execution on iOS and Android has never been forgiving, and RN adds its own quirks. Here's what actually runs when the user swipes your app away — and what silently doesn't.

Background Tasks in React Native 2026: What Actually Runs When the App Is Closed

Every mobile team eventually files the same ticket: "sync should keep running when the app is closed." And every mobile lead eventually has to explain that "closed" means four different things, and only two of them let you run code. In React Native this conversation is worse, because the JS runtime adds a layer of confusion on top of platform rules that are already hostile to background work.

Here's what actually executes when your RN or Expo app isn't in the foreground in 2026, what the current APIs give you, and where teams keep burning weeks.

The four states people call "closed"

Before any code discussion, get the vocabulary right. On both iOS and Android, an app can be in roughly one of these states:

  • Foreground — user is looking at it. Everything works.
  • Backgrounded — user switched to another app or locked the screen. Process is alive, JS runtime may be paused.
  • Suspended / cached — OS has frozen the process to reclaim resources. No code runs, but state is preserved.
  • Terminated — process is gone. This happens when the user force-quits (swipe up on iOS, swipe away on Android), when the OS reaps memory, or after a reboot.

The critical rule: on iOS, force-quit is essentially a death sentence for background work. Apple treats a user swipe-up as "I don't want this app doing anything." Most background APIs stop firing until the user opens the app again. Android is more permissive, but OEM battery optimisers (Xiaomi, Oppo, Samsung's aggressive modes) will kill you anyway.

Any background feature you ship has to be honest about which of these four states it survives.

What React Native actually gives you

The JS thread does not run in the background by default. When the app is backgrounded, iOS pauses the JS runtime almost immediately, and Android does the same shortly after. To run JS off the foreground, you need one of a small set of escape hatches.

Expo TaskManager + BackgroundFetch

If you're on Expo (managed or dev client), expo-task-manager plus expo-background-fetch is the path of least resistance. You register a named task at the native level, and the OS wakes your JS to run it on a schedule it chooses.

import * as BackgroundFetch from 'expo-background-fetch';
import * as TaskManager from 'expo-task-manager';

const SYNC_TASK = 'app.sync.pull';

TaskManager.defineTask(SYNC_TASK, async () => {
  try {
    const changed = await pullDelta();
    return changed
      ? BackgroundFetch.BackgroundFetchResult.NewData
      : BackgroundFetch.BackgroundFetchResult.NoData;
  } catch {
    return BackgroundFetch.BackgroundFetchResult.Failed;
  }
});

export async function registerSync() {
  await BackgroundFetch.registerTaskAsync(SYNC_TASK, {
    minimumInterval: 15 * 60, // seconds — a *hint*, not a promise
    stopOnTerminate: false,   // Android only
    startOnBoot: true,        // Android only
  });
}

Read the comments carefully. minimumInterval is a hint. iOS may fire it every 30 minutes, every 6 hours, or never that day, based on usage patterns it learns per user. In our experience, apps the user opens daily get frequent wakeups; apps they open weekly get almost none. Do not build features that assume a fixed cadence.

stopOnTerminate: false and startOnBoot: true do nothing on iOS. On iOS, terminated means terminated.

Headless JS (Android only)

When you need real work triggered by a system event on Android — a push arriving, a geofence crossing, a boot completion — Headless JS lets you spin up the JS runtime without any UI. It pairs naturally with Android's WorkManager if you're in the bare workflow, or with libraries like react-native-background-actions that wrap a foreground service.

The iOS equivalent doesn't exist. iOS gives you a handful of specific background modes (audio, location, VoIP, BLE, downloads, silent push) and expects you to use them for their stated purpose. Trying to run a general "sync worker" through, say, the location mode will get you rejected on review — we've watched teams try, and Apple catches it.

Silent push as a wakeup

The most reliable cross-platform wakeup in 2026 is still a silent push (content-available: 1 on APNs, high-priority data-only on FCM). It gives you a short window — roughly 30 seconds on both platforms in practice — to do work before the OS suspends you again.

Caveats worth internalising:

  • iOS throttles silent pushes aggressively. Send too many and APNs will start dropping them without telling you.
  • On iOS, silent pushes do not wake a force-quit app. Nothing does, except a user-visible notification the user taps.
  • On Android, a data-only FCM message wakes a terminated app and lets you run a Headless JS task, provided the OEM hasn't put you in a restricted bucket.

If your product requirement is "sync within 60 seconds of a server-side event," silent push is your primary tool and background fetch is your fallback. Not the other way round.

The state matrix people wish existed

Here is the honest version of what runs where, for a typical RN app in 2026:

TriggeriOS ForegroundiOS BackgroundediOS Force-QuitAndroid ForegroundAndroid BackgroundedAndroid Force-Quit
Timer in JSYesPausedNoYesPausedNo
BackgroundFetch taskYesOccasionalNoYesOccasionalDepends on OEM
Silent pushYesYes (throttled)NoYesYesUsually yes
Visible push tapYesYesYesYesYesYes
Geofence / significant locationYesYesYes (relaunch)YesYesDepends on OEM
Foreground service notificationN/AN/AN/AYesYesYes (until user dismisses)

The two green rows across all six columns are visible notifications and location events. That's not a coincidence — those are the only categories where the user has explicitly opted into background behaviour they can see.

Native comparison: is this worth going native for?

A fair question. Native Swift and Kotlin do not magically escape these OS rules — Apple and Google impose them at the platform level, not the framework level. What you get by going native is:

  • Slightly faster wakeup, because you skip the JS runtime bootstrap (typically 200–800 ms on cold start, in our measurements).
  • Direct access to newer APIs the day they ship, instead of waiting for a library maintainer. BGContinuedProcessingTaskRequest and similar recent additions to BackgroundTasks sometimes land in Expo modules months later.
  • Cleaner integration with WorkManager, especially for chained work and constraints.

What you don't get is more background time. If your team's argument for going native is "we need to run in the background more," that argument is wrong. Rewrite the feature, not the app.

Where teams actually lose weeks

A few patterns we see over and over in code reviews and audits:

Assuming background fetch is a cron

Product writes "sync every 15 minutes." Engineer sets minimumInterval: 900. QA tests on a dev device that's plugged in and used constantly — works great. Ships to production, user with a 2-year-old iPhone who opens the app twice a week gets synced maybe twice a month. Complaints pile up.

Fix: treat background fetch as opportunistic. Show a "last synced" timestamp. Sync eagerly on foregrounding. Use silent push for events that genuinely matter.

Doing too much in the wakeup window

You get seconds, not minutes. If your background task pulls a full dataset, decrypts it, runs migrations, and writes to SQLite, it will be killed mid-flight and you'll have corrupt state. Design tasks to be idempotent, resumable, and finishable in under 10 seconds.

Ignoring OEM battery managers on Android

Stock Android and Pixel devices behave close to spec. Xiaomi, Huawei, Oppo, Vivo, and older Samsung devices do not. They have their own battery optimisation UIs that put apps into "restricted" buckets by default, and no amount of WorkManager configuration overrides that. If your Android user base skews toward these OEMs, you need in-app education that walks users to the right settings screen. It's ugly, but there is no API fix.

Using foreground services for the wrong reason

Android foreground services (with a persistent notification) will keep your app alive indefinitely, and RN libraries make them trivial to start. Google Play's review team is now much stricter about foreground service types since the Android 14 rules tightened, and they will reject apps that declare dataSync or specialUse types without a clear user-facing justification. Don't reach for this unless the user explicitly started a long-running action they can see.

Where we'd start

If you're spec'ing background behaviour for a new RN app in 2026, start by writing down the user-visible outcome you need — "new messages appear within a minute," "the workout keeps recording after screen lock," "offline edits sync when connectivity returns." Then pick the narrowest OS mechanism that delivers that outcome: silent push for server-driven freshness, a foreground service for user-initiated long work, background fetch only for nice-to-have staleness reduction.

Build a small internal debug screen that logs every background wakeup with a timestamp and reason. Ship it to your own team for a week before you ship the feature. You'll learn more about iOS scheduling in seven days of real usage than in any documentation. And when product asks why sync didn't run at 2 a.m., you'll have receipts.

If you'd like a hand designing this layer for a production app, our mobile team has scars from most of the pitfalls above.

#React Native#Expo#iOS#Android#Background Tasks

Want a team like ours?

72Technologies builds production software for the kind of teams who actually read this blog.

Start a project