The missing guide to fonts in React Router 7 and Remix

Learn how to self-host fonts, integrate Fontsource, style with Tailwind CSS, and boost performance using preloading.
Ever since I began to use Remix and its successor React Router 7, I have been on the lookout for a guide on how to handle fonts in the framework properly. Seeing the reactions to the Fonts Guide proposal I am certainly not the only one. So, this post aims to fill that gap and to provide you with some tips to get you started more quickly with your font setup.
If you want to get going quickly, you can try out my React Router 7 starter template ReTail, which comes with a preconfigured font setup using Fontsource and some other useful features.
#Self-hosted fonts
In my opinion, you should self-host your web fonts whenever possible. This avoids issues with third-party services, yet another network request and potential GDPR violations.
#Using a local font file
Let's suppose you bought a font from MyFonts or some other vendor. You now have a local font file that is not distributed in some kind of font package and you have to integrate it in your website or application by hand. Here is how you can do that:
-
Add your font file
Create a
fonts
directory in yourpublic
folder (e.g.,public/fonts/
) and place your font file (e.g.,Savate-Regular.ttf
) in there.Info Because Remix and React Router 7 leverage Vite under the hood, you can use the
public
directory to serve static assets like fonts. -
Register the font in your CSS
Add a
@font-face
rule to your global CSS file (e.g.,app.css
):app/app.css@font-face { font-family: "Savate"; src: url("/fonts/Savate-Regular.ttf") format("truetype"); font-weight: normal; font-style: normal; font-display: swap; }
Warning Note how the
src
does not start with a./
or/public/
. This is because thepublic
directory is automatically served at the root of your domain, so you should reference it using the root absolute path with a leading slash like/fonts/Savate-Regular.ttf
. The font will then be accessible athttps://your-domain.com/fonts/Savate-Regular.ttf
. -
Use the font in your components
Reference the font in your CSS:
app/app.cssh1 { font-family: "Savate", sans-serif; }
And in your component (e.g.,
index.tsx
):app/routes/index.tsxexport default function Index() { return <h1>This heading uses the Savate font</h1>; }
Try to use modern font formats If possible, use modern font formats like WOFF2 or WOFF instead of TTF or OTF. They are generally smaller, more efficient and widely supported by modern browsers. Consider using a tool like Transfonter to convert your font files to these formats.
#Using Fontsource
With Fontsource you not only get the benefits of self-hosting, but also the convenience of using a package manager instead of manually downloading and managing font files. The library provides a wide range of fonts, including Google Fonts and other popular open-source fonts.
-
Find and install your font
Search for your font on https://fontsource.org/, click on the desired font, go to the "Install" tab and copy the installation command. For example, to use
Inter
as a variable font, you would run:npm install @fontsource-variable/inter
-
Import the font in your app
Import the font at the top of your Root Layout (e.g.,
root.tsx
):app/root.tsximport "@fontsource-variable/inter";
How does the import work? The import statement will automatically include the necessary CSS for the font in your application. Fontsource packages come with a pre-defined CSS file that sets up the
@font-face
rules for you, so you don't have to write them manually. -
Use the font in your CSS
Reference the font in your CSS:
app/app.cssh1 { font-family: "Inter Variable", sans-serif; font-weight: 700; font-size: 1.5rem; }
And in your component (e.g.,
index.tsx
):app/routes/index.tsxexport default function Index() { return <h1>Inter is the best!</h1>; }
This translates to:
Inter is the best!
#Registering Fonts in Tailwind CSS
If you use Tailwind CSS (I am using v4 here), you can use either method above, then:
-
Add the typeface name to your
@theme
configurationapp/app.css@import "tailwindcss"; /* ... */ @theme { --font-sans: "Inter Variable", "sans-serif"; }
-
Use the font in your components
Use the
font-sans
class or your custom class in your JSX, typically in your Root Layout:app/root.tsxexport function Layout({ children }: { children: React.ReactNode }) { return ( <html lang="en" className="font-sans"> <head> <meta charSet="utf-8" /> <meta name="viewport" content="width=device-width, initial-scale=1" /> <Meta /> <Links /> </head> <body> {children} <ScrollRestoration /> <Scripts /> </body> </html> ); }
For more details, see the Tailwind docs on custom fonts.
#Preloading Fonts
Preloading fonts can help to improve website performance and FOUT by loading them early, before they’re needed.
To keep load times efficient, only preload the fonts and subsets that are actually critical for the initial render.
To preload fonts in Remix and React Router 7, you can export a links
function from your Root Layout.
import type { Route } from "./+types/root";
export const links: Route.LinksFunction = () => [
{
rel: "preload",
href: "/fonts/Inter.woff2",
as: "font",
type: "font/woff2",
crossOrigin: "anonymous",
},
];
#Using preloading with Fontsource
For preloading Fontsource fonts, you can use the same approach, but you need to find the correct URL for the font file, which you typically do by leveraging Vite's URL imports. Refer to the Fontsource documentation for more information.
If you want to see a working example of how to set up fonts with Fontsource and preloading in React Router 7, check out my starter template ReTail.