Skip to content
Open
Show file tree
Hide file tree
Changes from 4 commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
62 changes: 61 additions & 1 deletion boilerplate/app.json
Original file line number Diff line number Diff line change
Expand Up @@ -35,7 +35,67 @@
},
"plugins": [
"expo-localization",
"expo-font",
[
"expo-font",
{
"fonts": [
"./assets/fonts/SpaceGrotesk-300Light.ttf",
"./assets/fonts/SpaceGrotesk-400Regular.ttf",
"./assets/fonts/SpaceGrotesk-500Medium.ttf",
"./assets/fonts/SpaceGrotesk-600SemiBold.ttf",
"./assets/fonts/SpaceGrotesk-700Bold.ttf"
],
"android": {
"fonts": [
{
"fontFamily": "SpaceGrotesk-300Light",
"fontDefinitions": [
{
"path": "./assets/fonts/SpaceGrotesk-300Light.ttf",
"weight": 300
}
]
},
{
"fontFamily": "SpaceGrotesk-400Regular",
"fontDefinitions": [
{
"path": "./assets/fonts/SpaceGrotesk-400Regular.ttf",
"weight": 400
}
]
},
{
"fontFamily": "SpaceGrotesk-500Medium",
"fontDefinitions": [
{
"path": "./assets/fonts/SpaceGrotesk-500Medium.ttf",
"weight": 500
}
]
},
{
"fontFamily": "SpaceGrotesk-600SemiBold",
"fontDefinitions": [
{
"path": "./assets/fonts/SpaceGrotesk-600SemiBold.ttf",
"weight": 600
}
]
},
{
"fontFamily": "SpaceGrotesk-700Bold",
"fontDefinitions": [
{
"path": "./assets/fonts/SpaceGrotesk-700Bold.ttf",
"weight": 700
}
]
}
]
}
Comment thread
ChristopherGabba marked this conversation as resolved.
Outdated
}
],
[
"expo-splash-screen",
{
Expand Down
15 changes: 12 additions & 3 deletions boilerplate/app/app.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@ if (__DEV__) {
import "./utils/gestureHandler"

import { useEffect, useState } from "react"
import { Platform } from "react-native"
import { useFonts } from "expo-font"
import * as Linking from "expo-linking"
import { KeyboardProvider } from "react-native-keyboard-controller"
Expand All @@ -29,7 +30,7 @@ import { initI18n } from "./i18n"
import { AppNavigator } from "./navigators/AppNavigator"
import { useNavigationPersistence } from "./navigators/navigationUtilities"
import { ThemeProvider } from "./theme/context"
import { customFontsToLoad } from "./theme/typography"
import { customFontsToLoadWebOnly } from "./theme/typography"
import { loadDateFnsLocale } from "./utils/formatDate"
import * as storage from "./utils/storage"

Expand Down Expand Up @@ -68,7 +69,11 @@ export function App() {
isRestored: isNavigationStateRestored,
} = useNavigationPersistence(storage, NAVIGATION_PERSISTENCE_KEY)

const [areFontsLoaded, fontLoadError] = useFonts(customFontsToLoad)
// We load fonts dynamically for web only, the rest are handled by
// the expo-font config plugin in `app.json`. If not using web,
// you can delete this permissive check along with associated
// code in `typography'.
const [areFontsLoadedWebOnly, fontLoadErrorWebOnly] = useFonts(customFontsToLoadWebOnly)
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

issue: we had been talking about this internally for a bit before your PR and were looking at an approach similar to how Evan Bacon and Expo are handling fonts cross platform. Rather than deleting for web, I think it would be ideal to gracefully handle web without having users make a choice.

One example @frankcalise found was this AsyncFont component from Evan: https://github.com/EvanBacon/expo-router-forms-components/blob/main/src/components/data/async-font.tsx

Used like this:

https://github.com/EvanBacon/expo-router-forms-components/blob/5aa311c507863faff99ad3853ac7607f487e7c71/src/app/_layout.tsx#L3

I'd prefer that implementation, myself.

Copy link
Copy Markdown
Author

@ChristopherGabba ChristopherGabba Oct 13, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Nice! I wouldn't have come up with that approach, no matter how much time I was given, I haven't studied the React 19 APIs very much.

This would probably include moving the i18n load permissive to the suspense fallback as well right? You want me to pursue that approach and see where it leads?

const [isI18nInitialized, setIsI18nInitialized] = useState(false)

useEffect(() => {
Expand All @@ -83,7 +88,11 @@ export function App() {
// In iOS: application:didFinishLaunchingWithOptions:
// In Android: https://stackoverflow.com/a/45838109/204044
// You can replace with your own loading component if you wish.
if (!isNavigationStateRestored || !isI18nInitialized || (!areFontsLoaded && !fontLoadError)) {
if (
!isNavigationStateRestored ||
!isI18nInitialized ||
(!areFontsLoadedWebOnly && !fontLoadErrorWebOnly && Platform.OS === "web")
) {
return null
}

Expand Down
53 changes: 40 additions & 13 deletions boilerplate/app/theme/typography.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@
// markdown file and add links from here

import { Platform } from "react-native"
import type { FontSource } from "expo-font"
import {
SpaceGrotesk_300Light as spaceGroteskLight,
SpaceGrotesk_400Regular as spaceGroteskRegular,
Expand All @@ -10,22 +11,48 @@ import {
SpaceGrotesk_700Bold as spaceGroteskBold,
} from "@expo-google-fonts/space-grotesk"

export const customFontsToLoad = {
spaceGroteskLight,
spaceGroteskRegular,
spaceGroteskMedium,
spaceGroteskSemiBold,
spaceGroteskBold,
}
export const customFontsToLoadWebOnly =
Platform.OS === "web"
? {
spaceGroteskLight,
spaceGroteskRegular,
spaceGroteskMedium,
spaceGroteskSemiBold,
spaceGroteskBold,
}
: ({} as Record<string, FontSource>)

const fonts = {
spaceGrotesk: {
// Cross-platform Google font.
light: "spaceGroteskLight",
normal: "spaceGroteskRegular",
medium: "spaceGroteskMedium",
semiBold: "spaceGroteskSemiBold",
bold: "spaceGroteskBold",
// The way expo-fonts config plugin applies
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

praise: thanks for figuring this out! I haven't tried out the new font loading technique so it's good to learn this.

// fonts to the individual platforms, the names come out different
// on ios and android. For web, we have to load fonts asynchronously
// using useFonts.
light: Platform.select({
ios: "SpaceGrotesk-Light",
android: "SpaceGrotesk-300Light",
web: "spaceGroteskLight",
}),
normal: Platform.select({
ios: "SpaceGrotesk-Regular",
android: "SpaceGrotesk-400Regular",
web: "spaceGroteskRegular",
}),
medium: Platform.select({
ios: "SpaceGrotesk-Medium",
android: "SpaceGrotesk-500Medium",
web: "spaceGroteskMedium",
}),
semiBold: Platform.select({
ios: "SpaceGrotesk-SemiBold",
android: "SpaceGrotesk-600SemiBold",
web: "spaceGroteskSemiBold",
}),
bold: Platform.select({
ios: "SpaceGrotesk-Bold",
android: "SpaceGrotesk-700Bold",
web: "spaceGroteskBold",
}),
},
helveticaNeue: {
// iOS only font.
Expand Down
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file added boilerplate/assets/fonts/SpaceGrotesk-700Bold.ttf
Binary file not shown.
15 changes: 10 additions & 5 deletions boilerplate/src/app/_layout.tsx
Original file line number Diff line number Diff line change
@@ -1,12 +1,13 @@
import { useEffect, useState } from "react"
import { Platform } from "react-native"
import { Slot, SplashScreen } from "expo-router"
import { useFonts } from "@expo-google-fonts/space-grotesk"
import { KeyboardProvider } from "react-native-keyboard-controller"
import { initialWindowMetrics, SafeAreaProvider } from "react-native-safe-area-context"

import { initI18n } from "@/i18n"
import { ThemeProvider } from "@/theme/context"
import { customFontsToLoad } from "@/theme/typography"
import { customFontsToLoadWebOnly } from "@/theme/typography"
import { loadDateFnsLocale } from "@/utils/formatDate"

SplashScreen.preventAutoHideAsync()
Expand All @@ -21,7 +22,11 @@ if (__DEV__) {
export { ErrorBoundary } from "@/components/ErrorBoundary/ErrorBoundary"

export default function Root() {
const [fontsLoaded, fontError] = useFonts(customFontsToLoad)
// We load fonts dynamically for web only, the rest are handled by
// the expo-font config plugin in `app.json`. If not using web,
// you can delete this permissive check along with associated
// code in `typography'.
const [fontsLoadedWebOnly, fontErrorWebOnly] = useFonts(customFontsToLoadWebOnly)
const [isI18nInitialized, setIsI18nInitialized] = useState(false)

useEffect(() => {
Expand All @@ -30,11 +35,11 @@ export default function Root() {
.then(() => loadDateFnsLocale())
}, [])

const loaded = fontsLoaded && isI18nInitialized
const loaded = Platform.OS === "web" ? fontsLoadedWebOnly && isI18nInitialized : isI18nInitialized

useEffect(() => {
if (fontError) throw fontError
}, [fontError])
if (fontErrorWebOnly && Platform.OS === "web") throw fontErrorWebOnly
}, [fontErrorWebOnly])

useEffect(() => {
if (loaded) {
Expand Down
2 changes: 1 addition & 1 deletion package.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "ignite-cli",
"version": "11.2.0",
"version": "11.2.1",
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
"version": "11.2.0",

suggestion: no need for this, I think CI will do it for us.

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

issue: let's undo this change before merge, please.

"description": "Infinite Red's hottest boilerplate for React Native.",
"bin": {
"ignite": "bin/ignite",
Expand Down