CORE JSC

International Technology Partnership

React Native

Fixing Custom Fonts That Don't Render on Android in React Native

A custom font renders perfectly on iOS and falls straight back to the system default on Android — sometimes only in a release build, never in debug. The font file is linked correctly; Android is just resolving the fontFamily name against something other than what the app is actually asking for.

Core JSC Team·August 9, 2026
React NativeAndroidFontsTypographyBuild

The Problem

A custom font is added to a React Native project, referenced via fontFamily in styles, and renders correctly on iOS — but on Android, the same text falls back to the system default font, as if the font were never linked at all. The behavior can be inconsistent: sometimes it works in a debug build off Metro but breaks in a signed release build, or works for the regular weight but not for a bold or italic variant referenced through fontWeight.

Why It Happens

Android resolves fontFamily against the font file's internal PostScript name, not its filename or fontWeight

iOS matches a fontFamily string somewhat flexibly, but Android's native font resolution is stricter: it expects the fontFamily value used in JS to exactly match the actual internal font name embedded in the font file itself (not necessarily its filename on disk), and it treats each linked font file as a single, complete family with no separate handling for a fontWeight or fontStyle variant unless a proper Android font family XML resource is set up. A regular weight file linked as "MyFont" and referenced with fontWeight: "bold" in styles will typically just render the regular weight, faux-bolded or not at all, because Android has no separately linked bold font file to actually switch to.

Fonts were linked once, but the native project was never rebuilt from a clean state

Font linking on Android (whether via npx react-native-asset or manually placing files under android/app/src/main/assets/fonts) is a native build-time step, not something Metro's JS bundler picks up on a hot reload. If the fonts were added after the last native build, or an incremental build reused cached resources, the compiled APK may simply not contain the font files at all — a state that's invisible from the JS side, since nothing throws an error; text just silently falls back.

A release build's resource shrinking removed what looked like an unused asset

Android's release build resource shrinker can, under certain configurations, treat font files as removable if it can't statically trace their usage the way it can for referenced drawables or layouts — which is consistent with the "works in debug, breaks in release" pattern specifically, since debug builds skip that shrinking step entirely.

The Fix

1. Confirm the exact fontFamily string against the font file, not by filename alone

# macOS/Linux — inspect a font file's actual internal name
fc-scan --format "%{family}
" MyFont-Regular.ttf

Use the family name reported here as the fontFamily value in styles — not necessarily the filename minus its extension, since those can differ, especially for fonts exported from certain design tools.

2. Set up a proper Android font family resource for weight/style variants instead of relying on fontWeight alone

<!-- android/app/src/main/res/font/my_font.xml -->
<font-family xmlns:app="http://schemas.android.com/apk/res-auto">
  <font app:font="@font/my_font_regular" app:fontStyle="normal" app:fontWeight="400" />
  <font app:font="@font/my_font_bold" app:fontStyle="normal" app:fontWeight="700" />
</font-family>

Without an explicit family resource like this mapping each weight to its own font file, Android has no way to switch to a genuinely bold font file just because a style says fontWeight: "bold" — it either fakes a bold effect on the regular glyphs or ignores the weight entirely, depending on the OEM and Android version.

3. Rebuild the native project from a clean state after adding or changing fonts

npx react-native-asset
cd android && ./gradlew clean && cd ..
npx react-native run-android

Font linking is a native asset-merging step, so a hot reload or even a normal incremental build can miss newly added files. A clean Gradle build forces Android's resource pipeline to re-scan and re-bundle the fonts directory from scratch, eliminating stale or missing font resources as a cause.

4. Explicitly exempt font resources from release-build shrinking if they're being stripped

<!-- android/app/src/main/res/raw/keep.xml -->
<resources xmlns:tools="http://schemas.android.com/tools"
    tools:keep="@font/*" />

If a font only fails in release builds specifically, this confirms the resource shrinker is the cause — explicitly marking font resources as required with tools:keep stops the shrinker from removing files it can't trace a static reference to.

Why This Works

Each fix targets a distinct point where Android's font handling diverges from what a developer coming from iOS or web CSS would reasonably assume: matching the font's real internal name (not filename) satisfies Android's stricter resolution; an explicit font family XML resource gives Android an actual bold/italic file to switch to instead of expecting fontWeight alone to do that work; a clean native rebuild guarantees newly added fonts are actually compiled into the app rather than assumed to be there; and exempting fonts from shrinking stops a release-only optimization from silently discarding files the shrinker couldn't prove were in use.

Conclusion

Custom fonts falling back to the system default on Android in React Native is almost always a font-resolution or build-linking gap specific to Android's native font system, not a React Native bug — confirm the fontFamily string matches the font's real internal name, set up an explicit Android font family resource for weight and style variants, do a clean native rebuild after any font change, and exempt font resources from release-build resource shrinking if the failure is release-only.