2026년 7월 23일 · 읽는 데 약 8분

React Native 안드로이드 커스텀 폰트, 문서대로 해도 안 될 때

React Native 안드로이드 커스텀 폰트, 문서대로 해도 안 될 때

에러가 한 줄이라도 났다면 오히려 쉬웠을 겁니다. React Native에 커스텀 폰트를 넣는 절차는 어느 가이드를 검색해도 비슷합니다. 폰트 파일을 assets/fonts 폴더에 두고, react-native.config.js에 경로를 적고, npx react-native-asset을 실행하고, 스타일에 fontFamily를 적으면 끝. 그대로 했는데 안드로이드에서만 폰트가 시스템 기본으로 나오거나, 폰트는 나오는데 fontWeight: '500' 같은 굵기가 무시됩니다. 빌드는 성공하고 콘솔은 조용합니다. 화면만 다릅니다.

원인은 폴더에 있습니다. 안드로이드에서 React Native가 폰트를 찾는 길은 두 갈래인데, 가이드들이 폰트를 넣어주는 assets/fonts 길은 구조상 중간 굵기를 전달하지 못합니다. 굵기까지 온전히 쓰려면 폰트를 res/font에 두고 앱이 시작할 때 등록하는 길로 옮겨야 합니다. 지금 프로젝트가 어느 길 위에 있는지는 10초면 확인됩니다.

먼저 확인: 폰트 파일이 지금 어느 폴더에 있습니까

android/app/src/main 아래를 봅니다. 폰트는 둘 중 한 자리에 있을 겁니다.

  • assets/fonts/ — 대부분의 가이드대로 npx react-native-asset을 돌렸다면 여기에 복사돼 있습니다.
  • res/font/ — 폰트 파일과 XML이 여기에 있고 MainApplication에 등록 코드까지 있다면, 이 글의 처방은 이미 적용된 상태입니다.

폰트가 assets/fonts에만 있다면 증상은 이렇게 정리됩니다. fontFamily 이름이 파일명과 정확히 일치하면 기본 굵기 하나는 나옵니다. 여기에 fontWeight를 주면 100~600은 전부 기본 파일로, 700~900은 전부 _bold 파일로 갑니다. 굵기별 파일을 아무리 넣어도 500과 600은 400과 똑같이 나온다는 뜻입니다. 그리고 이름이 어긋나면 에러 없이 시스템 폰트로 대체됩니다. 빌드도 실행도 멀쩡해서, 화면을 뚫어지게 보기 전까지는 틀렸다는 사실 자체를 모릅니다. 그래서 폰트가 아예 안 나온다면, 우선 fontFamily에 적은 이름이 확장자를 뺀 파일명과 대소문자까지 똑같은지 확인하세요. 그것만 맞으면 기본 굵기 하나는 살아납니다. 중간 굵기까지 살리는 게 이 글의 나머지입니다.

왜 이런가: 폰트를 찾는 길이 두 갈래입니다

안드로이드 쪽에서 폰트를 불러오는 코드는 ReactFontManager라는 클래스 하나입니다. 소스를 열어 보면 동작이 두 갈래로 갈립니다. 이 구조는 2026년 7월의 main 브랜치 기준이고, 오래전부터 같았습니다.

첫째 갈래는 assets 탐색입니다. 등록되지 않은 이름이면 assets/fonts/에서 파일명으로 폰트를 찾는데, 이 갈래가 아는 이름 형식이 네 개뿐입니다. 이름.ttf, 이름_bold.ttf, 이름_italic.ttf, 이름_bold_italic.ttf(.otf도 됩니다). 요청한 숫자 굵기는 파일을 고르기 전에 700 기준으로 기본과 볼드 둘로만 나뉩니다. 그래서 이 갈래에는 미디엄(500)이라는 선택지가 아예 없습니다. 나뉜 결과에 해당하는 파일명 하나를 .ttf와 .otf로 시도해 보고, 없으면 예외를 삼킨 뒤 같은 이름의 시스템 폰트를 만들어 돌려줍니다. 이름이 틀려도 에러가 안 나는 이유가 바로 이 마지막 동작입니다.

둘째 갈래는 등록된 폰트입니다. 앱이 시작할 때 addCustomFont로 등록해 둔 이름이면 res/font 리소스에서 폰트를 불러오고, 안드로이드 9(API 28) 이상에서는 요청한 숫자 굵기를 그대로 넘깁니다. 실제로 그 굵기로 나오려면 XML에 해당 굵기의 파일이 등록돼 있어야 하고, 등록하지 않은 굵기는 등록된 것 중 가까운 쪽으로 대체됩니다. 안드로이드 9 미만에서는 이 갈래도 기본과 볼드 둘뿐입니다. React Native 저장소의 데모 앱 RNTester도 Rubik과 FiraCode를 이 방식으로 등록해 씁니다.

처방: res/font에 넣고, XML로 묶고, 시작할 때 등록합니다

급하면 도구부터 쓰세요. @callstack/react-native-asset 3.x가 아래 절차를 자동으로 해줍니다. 함정이 하나 있어서 마지막 섹션에 적었습니다. 손으로 하면 다섯 단계입니다. 작년 늦가을 사이드 프로젝트 앱에 Pretendard를 얹으면서 제가 밟은 순서 그대로 적습니다.

  1. 폰트 파일을 android/app/src/main/res/font/에 넣습니다. 안드로이드 리소스 파일명은 소문자·숫자·언더바만 허용하니 Pretendard-Bold.ttfpretendard_bold.ttf로 바꿉니다.

  2. 같은 폴더에 XML 파일을 만듭니다. 이름은 pretendard.xml처럼 소문자로 짓고, 쓸 굵기를 전부 등록합니다. 여기에 등록한 굵기만 요청한 대로 나오고, 빠뜨린 굵기는 가까운 쪽으로 대체됩니다. 안드로이드가 API 26에서 도입한 Fonts in XML 형식인데, React Native는 AndroidX 호환 라이브러리로 불러오기 때문에 그보다 낮은 안드로이드 버전에서도 동작합니다.

    <?xml version="1.0" encoding="utf-8"?>
    <font-family xmlns:app="http://schemas.android.com/apk/res-auto">
        <font app:fontStyle="normal" app:fontWeight="400" app:font="@font/pretendard_regular"/>
        <font app:fontStyle="normal" app:fontWeight="500" app:font="@font/pretendard_medium"/>
        <font app:fontStyle="normal" app:fontWeight="600" app:font="@font/pretendard_semibold"/>
        <font app:fontStyle="normal" app:fontWeight="700" app:font="@font/pretendard_bold"/>
    </font-family>
  3. MainApplication(android/app/src/main/java/<패키지 경로>/MainApplication.kt)의 onCreate에서 등록합니다. 여기까지 해야 React Native가 이 이름을 등록된 폰트로 인식합니다. 옛 버전을 쓰고 있어서 아래 임포트가 안 잡히면 com.facebook.react.views.text 아래에 같은 클래스가 있으니 그쪽을 확인하세요.

    import com.facebook.react.common.assets.ReactFontManager
    
    override fun onCreate() {
        super.onCreate()
        ReactFontManager.getInstance().addCustomFont(this, "Pretendard", R.font.pretendard)
        // ...
    }
  4. 자바스크립트에서는 등록한 이름으로 씁니다. 이제 굵기는 fontWeight가 맡습니다.

    <Text style={{ fontFamily: 'Pretendard', fontWeight: '600' }}>본문</Text>
  5. assets/fonts에 남은 같은 폰트를 지우고 다시 빌드합니다. 등록된 이름이 먼저 잡히기 때문에 남아 있어도 동작은 하지만, 같은 폰트가 앱 용량을 두 번 먹습니다.

지웠는데 되살아난다면 react-native.config.js를 보세요. assets에 폰트 경로가 남아 있으면 npx react-native-asset 같은 링크 도구를 돌릴 때마다 안드로이드 쪽 복사본이 다시 생깁니다. 그렇다고 경로를 통째로 지우면 iOS 링크까지 풀리니, 폰트 경로를 assets 대신 iosAssets로 옮기세요. iOS 등록은 유지되고 안드로이드 복사만 빠집니다.

가변 폰트 이야기도 여기에 걸립니다. 저도 처음에는 가변 폰트 한 파일로 굵기 전부를 해결하고 싶었지만, 안드로이드 쪽은 굵기별 정적 파일을 XML로 묶는 방식이 확실했습니다. Pretendard처럼 굵기별 파일을 함께 배포하는 폰트라면 그쪽을 쓰세요.

손이 많이 가면 도구에 맡깁니다

아까 말한 도구의 함정입니다. @callstack/react-native-asset 3.x는 res/font에 XML을 만들고 MainApplication에 등록 코드까지 넣어 줍니다. 그런데 npx react-native-asset이 설치하는 원조 패키지(글 쓰는 시점 최신 2.3.0)는 assets 폴더에 복사만 합니다. 이름이 거의 같아서 헷갈리기 쉽습니다. XML 방식을 원하면 스코프까지 붙여 @callstack/react-native-asset을 지정해야 합니다.

iOS는 이 문제와 무관합니다. 폰트가 Info.plist에 등록되는 별도 경로라, 안드로이드에서만 화면이 다른 것이 오히려 이 문제의 증거가 됩니다.

폰트가 안 먹으면 스타일 코드를 의심하기 전에 폴더를 여세요. assets/fonts에 있으면 중간 굵기는 애초에 전달되지 않습니다. 처방은 위의 다섯 단계가 전부입니다. 다음에 안드로이드에서만 글자가 이상하면, 먼저 폰트가 어느 길 위에 있는지부터 확인해 보세요.


여백 — 이 절차를 알고 난 뒤에도 급한 일정에서는 fontFamily: 'Pretendard-Bold'처럼 굵기마다 패밀리 이름을 바꿔 쓰는 옛 방식을 쓴 적이 있습니다. 굵기 두세 개만 쓰는 화면에서는 그게 더 빠릅니다. 대신 디자이너가 세미볼드를 하나 추가하던 날, 앱 전체의 폰트 스타일을 검색해서 바꿨습니다. 지름길 요금은 후불이었습니다.

참고 — React Native Android에서 가변 폰트 사용하기 (NY64), react-native-font-demo (jsamr, GitHub)

이 글이 울렸다면 눌러 주세요

댓글

  • 불러오는 중…

← 전체 글 목록