◉RORK LABJP
●GPT6.1 — GPT-6.1 Sol joined Rork's model menu on Sep 29. It costs the same as GPT-6 Sol and reads a 1M-token context●SDK58 — Expo SDK 58 Beta is out, bundling a React Native 0.88 release candidate. A stable date has not been confirmed yet●10/12 — 5 days until the planned React Native 0.88.x release. The Expo Go update that follows drops SDK 57 support●TESTFLIGHT — A Zenn post covers putting an iPhone app on TestFlight with no Mac, Windows only, and the four places it snagged●NEW — I picked between Sonnet 5.5, GPT-6.1 Sol and Opus 5.5 by the kind of fix, and logged where my credits went for a week●EXPO — As Shopify moves back to native, a Zenn post explains staying on Expo as a solo developer. The one person who verifies is the real point●GPT6.1 — GPT-6.1 Sol joined Rork's model menu on Sep 29. It costs the same as GPT-6 Sol and reads a 1M-token context●SDK58 — Expo SDK 58 Beta is out, bundling a React Native 0.88 release candidate. A stable date has not been confirmed yet●10/12 — 5 days until the planned React Native 0.88.x release. The Expo Go update that follows drops SDK 57 support●TESTFLIGHT — A Zenn post covers putting an iPhone app on TestFlight with no Mac, Windows only, and the four places it snagged●NEW — I picked between Sonnet 5.5, GPT-6.1 Sol and Opus 5.5 by the kind of fix, and logged where my credits went for a week●EXPO — As Shopify moves back to native, a Zenn post explains staying on Expo as a solo developer. The one person who verifies is the real point
Articles/Dev Tools
⬡ Dev Tools/2026-10-07Intermediate

Japanese Headings Won't Turn Bold on Android: How to Trace and Fix a fontWeight That Does Nothing

A troubleshooting note for Rork apps where headings look bold on iPhone but stay thin on Android. How to tell the symptoms apart, pick weights through fontFamily, and verify the fix.

Rork576Expo213expo-fontfontWeightNoto Sans JPAndroid53troubleshooting67

I check a generated screen on an iPhone, nod, and close it. Android can wait — that's the order I fall into more often than I'd like.

Then "later" arrives, and on an Android device the headings look oddly thin. The code clearly says fontWeight: "700". It was bold on the iPhone. Sometimes the Latin letters are bold while the Japanese characters next to them stay light.

This note is for anyone standing in front of that screen. I'll go in this order: tell the symptoms apart, trace the cause, fix it, then verify.

First, sort the symptom into one of three

"It won't go bold" can look slightly different each time, and each look points somewhere else.

What you seeWhere to look
Bold on iPhone, everything thin on AndroidThe combination of fontFamily and fontWeight
Latin letters are bold, Japanese stays thinNo Japanese-capable font loaded (fallback in play)
Bold gives a clearly different-looking fontPer-weight font names mixed up

The first is by far the most common, and it's the center of this note. The other two usually surface during the trace below.

Trace the cause in this order

Each step is short. Do them top to bottom.

1. Find which fontFamily the bold Text is actually using.

Search the generated code for every place fontWeight appears.

grep -rn "fontWeight" app components | head -30

Look near each hit for a fontFamily. The typical culprit is a line that still points at the Regular name, such as fontFamily: "NotoSansJP_400Regular", with fontWeight: "700" simply added on top.

2. Check whether only Regular is loaded.

Open the list you pass to useFonts. If Bold isn't in it, Android has nothing to use as the bold face in the first place.

3. Check the spelling of the name.

The key in useFonts and the string in fontFamily must match exactly. When they don't — an extension, a hyphen versus an underscore — you often get no error, just the system font, which is why it's easy to miss. The loading basics are covered in loading custom fonts in a Rork app without flicker.

Most cases get a name by the end of these three steps.

The idea: give each weight its own name

In short: iPhone often picks a close weight from a family plus a weight value, whereas Android leans toward looking the face up by family name, so adding fontWeight to a Regular face doesn't always switch you to the Bold face. If you want the same result on both platforms, load each weight under its own name and choose the weight with fontFamily, not fontWeight.

That way you stop depending on how each OS interprets the weight.

The fix: let the name carry the weight

Here's a setup that loads Noto Sans JP from its Google Fonts package.

npx expo install expo-font @expo-google-fonts/noto-sans-jp

What this solves: it loads Regular, Medium, and Bold under separate names, so a screen only has to pass a meaningful weight.

// components/AppText.tsx
import { Text as RNText, TextProps } from "react-native";
 
type Weight = "regular" | "medium" | "bold";
 
const FAMILY: Record<Weight, string> = {
  regular: "NotoSansJP_400Regular",
  medium: "NotoSansJP_500Medium",
  bold: "NotoSansJP_700Bold",
};
 
type Props = TextProps & { weight?: Weight };
 
export function AppText({ weight = "regular", style, ...rest }: Props) {
  return (
    <RNText
      {...rest}
      style={[
        { fontFamily: FAMILY[weight] },
        style,
        // The weight is chosen by fontFamily; reset it here to avoid double-specifying
        { fontWeight: "normal" },
      ]}
    />
  );
}
// app/_layout.tsx (loading side)
import { useFonts } from "expo-font";
import {
  NotoSansJP_400Regular,
  NotoSansJP_500Medium,
  NotoSansJP_700Bold,
} from "@expo-google-fonts/noto-sans-jp";
import * as SplashScreen from "expo-splash-screen";
import { useEffect } from "react";
import { Stack } from "expo-router";
 
SplashScreen.preventAutoHideAsync();
 
export default function RootLayout() {
  const [loaded, error] = useFonts({
    NotoSansJP_400Regular,
    NotoSansJP_500Medium,
    NotoSansJP_700Bold,
  });
 
  useEffect(() => {
    if (loaded || error) SplashScreen.hideAsync();
  }, [loaded, error]);
 
  if (!loaded && !error) return null;
  return <Stack />;
}

Why it's written this way. Resetting fontWeight at the end of AppText means that if someone — or an AI — later adds fontWeight: "700", it can't drift back into "Regular face plus a weight value." With one entry point for weight, there's only one place to suspect when you reread generated code.

Verify with a three-line check screen

Before touching real screens, line the three weights up on a scratch screen. Mixing Japanese, Latin letters, and digits lets you catch the second symptom (Japanese stays thin) at the same time.

// app/font-check.tsx
import { View } from "react-native";
import { AppText } from "../components/AppText";
 
export default function FontCheck() {
  return (
    <View style={{ padding: 24, gap: 12 }}>
      <AppText weight="regular" style={{ fontSize: 20 }}>標準 Regular 日本語ABC123</AppText>
      <AppText weight="medium" style={{ fontSize: 20 }}>中太 Medium 日本語ABC123</AppText>
      <AppText weight="bold" style={{ fontSize: 20 }}>太字 Bold 日本語ABC123</AppText>
    </View>
  );
}

There's one thing to look at: on both iPhone and Android, do the three lines step up in weight? If the Japanese characters get heavier in step with the Latin ones, you're done.

Pitfalls that tend to remain

  • File size. Each extra weight of a Japanese font adds to what ships in the app. If Regular and Bold are enough, adding Medium later is a perfectly good decision.
  • How to ask the AI. When you ask Rork to fix this, spelling it out helps: "Don't use fontWeight; specify a separate fontFamily per weight. Only Regular and Bold are needed."
  • If it still isn't fixed. Log the error returned by useFonts once, to rule out a failed load. A name mismatch just quietly shows another font on screen, so the log tells you faster.

Your next step

In your own project, run grep -rn "fontWeight" app components once and count the hits. That number is your estimate of how much there is to fix.

Let the name decide the weight instead of leaving it to each OS's guess. As an indie developer shipping to both stores, that's a line I intend to keep holding.

Share

Thank You for Reading

Rork Lab is ad-free, supported entirely by members like you. We publish practical guides daily with implementation code, benchmarks, and production-ready patterns. If you've found it useful, we'd love to have you on board.

  • ✦Copy-paste ready implementation code
  • ✦New advanced guides published daily
  • ✦$5/mo or $15 for lifetime access
View Membership →

If you found this article helpful, a small tip ($1.50) would mean a lot to us. Your support helps keep this site ad-free and covers server and hosting costs.

Related Articles

⬡ Dev Tools2026-08-21
The four acceptance checks I still run after the build turns green
A successful build does not mean a shippable build. Here are the four failure classes an AI build loop cannot see, and a dependency-free script that inspects the artifact itself before you submit.
⬡ Dev Tools2026-09-22
Four Numbers to Record Before R8 Becomes the Default — Android Measurements While We Wait for Expo SDK 58
Expo SDK 58 turns on R8 by default for Android release builds. Here is how I capture four numbers — download size, DEX total, cold start, and build time — so the before and after can be compared with the same ruler.
⬡ Dev Tools2026-09-21
A build that stops with exit code 0 — the flag that silenced the log, and the check that let an empty file through
Two reports landed in the same week: eas build --local stopping with exit code 0 but produced no further output, and a zero-byte stub that never got embedded. Both came down to settings that reduce output. Here is how to audit your own scripts.
📚RECOMMENDED BOOKS
Build a Large Language Model (From Scratch)
Sebastian Raschka
LLM Dev
Prompt Engineering for LLMs
Berryman & Ziegler
Prompting
AI Engineering
Chip Huyen
AI Eng
* Contains affiliate links