I'm using create-react-app and prefer not to eject
.
It's not clear where fonts imported via @font-face and loaded locally should go.
Namely, I'm loading
@font-face {
font-family: 'Myriad Pro Regular';
font-style: normal;
font-weight: normal;
src: local('Myriad Pro Regular'), url('MYRIADPRO-REGULAR.woff') format('woff');
}
Any suggestions?
-- EDIT
Including the gist to which Dan referring in his answer
➜ Client git:(feature/trivia-game-ui-2) ✗ ls -l public/static/fonts
total 1168
-rwxr-xr-x@ 1 maximveksler staff 62676 Mar 17 2014 MYRIADPRO-BOLD.woff
-rwxr-xr-x@ 1 maximveksler staff 61500 Mar 17 2014 MYRIADPRO-BOLDCOND.woff
-rwxr-xr-x@ 1 maximveksler staff 66024 Mar 17 2014 MYRIADPRO-BOLDCONDIT.woff
-rwxr-xr-x@ 1 maximveksler staff 66108 Mar 17 2014 MYRIADPRO-BOLDIT.woff
-rwxr-xr-x@ 1 maximveksler staff 60044 Mar 17 2014 MYRIADPRO-COND.woff
-rwxr-xr-x@ 1 maximveksler staff 64656 Mar 17 2014 MYRIADPRO-CONDIT.woff
-rwxr-xr-x@ 1 maximveksler staff 61848 Mar 17 2014 MYRIADPRO-REGULAR.woff
-rwxr-xr-x@ 1 maximveksler staff 62448 Mar 17 2014 MYRIADPRO-SEMIBOLD.woff
-rwxr-xr-x@ 1 maximveksler staff 66232 Mar 17 2014 MYRIADPRO-SEMIBOLDIT.woff
➜ Client git:(feature/trivia-game-ui-2) ✗ cat src/containers/GameModule.css
.GameModule {
padding: 15px;
}
@font-face {
font-family: 'Myriad Pro Regular';
font-style: normal;
font-weight: normal;
src: local('Myriad Pro Regular'), url('%PUBLIC_URL%/static/fonts/MYRIADPRO-REGULAR.woff') format('woff');
}
@font-face {
font-family: 'Myriad Pro Condensed';
font-style: normal;
font-weight: normal;
src: local('Myriad Pro Condensed'), url('%PUBLIC_URL%/static/fonts/MYRIADPRO-COND.woff') format('woff');
}
@font-face {
font-family: 'Myriad Pro Semibold Italic';
font-style: normal;
font-weight: normal;
src: local('Myriad Pro Semibold Italic'), url('%PUBLIC_URL%/static/fonts/MYRIADPRO-SEMIBOLDIT.woff') format('woff');
}
@font-face {
font-family: 'Myriad Pro Semibold';
font-style: normal;
font-weight: normal;
src: local('Myriad Pro Semibold'), url('%PUBLIC_URL%/static/fonts/MYRIADPRO-SEMIBOLD.woff') format('woff');
}
@font-face {
font-family: 'Myriad Pro Condensed Italic';
font-style: normal;
font-weight: normal;
src: local('Myriad Pro Condensed Italic'), url('%PUBLIC_URL%/static/fonts/MYRIADPRO-CONDIT.woff') format('woff');
}
@font-face {
font-family: 'Myriad Pro Bold Italic';
font-style: normal;
font-weight: normal;
src: local('Myriad Pro Bold Italic'), url('%PUBLIC_URL%/static/fonts/MYRIADPRO-BOLDIT.woff') format('woff');
}
@font-face {
font-family: 'Myriad Pro Bold Condensed Italic';
font-style: normal;
font-weight: normal;
src: local('Myriad Pro Bold Condensed Italic'), url('%PUBLIC_URL%/static/fonts/MYRIADPRO-BOLDCONDIT.woff') format('woff');
}
@font-face {
font-family: 'Myriad Pro Bold Condensed';
font-style: normal;
font-weight: normal;
src: local('Myriad Pro Bold Condensed'), url('%PUBLIC_URL%/static/fonts/MYRIADPRO-BOLDCOND.woff') format('woff');
}
@font-face {
font-family: 'Myriad Pro Bold';
font-style: normal;
font-weight: normal;
src: local('Myriad Pro Bold'), url('%PUBLIC_URL%/static/fonts/MYRIADPRO-BOLD.woff') format('woff');
}
%PUBLIC_URL%
can't work (and is unnecessary) in a CSS file. Also, as described in the guide, you should be using JS imports in almost all cases, not the public folder.
There are two options:
Using Imports
This is the suggested option. It ensures your fonts go through the build pipeline, get hashes during compilation so that browser caching works correctly, and that you get compilation errors if the files are missing.
As described in “Adding Images, Fonts, and Files”, you need to have a CSS file imported from JS. For example, by default src/index.js
imports src/index.css
:
import './index.css';
A CSS file like this goes through the build pipeline, and can reference fonts and images. For example, if you put a font in src/fonts/MyFont.woff
, your index.css
might include this:
@font-face {
font-family: 'MyFont';
src: local('MyFont'), url(./fonts/MyFont.woff) format('woff');
/* other formats include: 'woff2', 'truetype, 'opentype',
'embedded-opentype', and 'svg' */
}
Notice how we’re using a relative path starting with ./
. This is a special notation that helps the build pipeline (powered by Webpack) discover this file.
Normally this should be enough.
Using public Folder
If for some reason you prefer not to use the build pipeline, and instead do it the “classic way”, you can use the public
folder and put your fonts there.
The downside of this approach is that the files don’t get hashes when you compile for production so you’ll have to update their names every time you change them, or browsers will cache the old versions.
If you want to do it this way, put the fonts somewhere into the public
folder, for example, into public/fonts/MyFont.woff
. If you follow this approach, you should put CSS files into public
folder as well and not import them from JS because mixing these approaches is going to be very confusing. So, if you still want to do it, you’d have a file like public/index.css
. You would have to manually add <link>
to this stylesheet from public/index.html
:
<link rel="stylesheet" href="%PUBLIC_URL%/index.css">
And inside of it, you would use the regular CSS notation:
@font-face {
font-family: 'MyFont';
src: local('MyFont'), url(fonts/MyFont.woff) format('woff');
}
Notice how I’m using fonts/MyFont.woff
as the path. This is because index.css
is in the public
folder so it will be served from the public path (usually it’s the server root, but if you deploy to GitHub Pages and set your homepage
field to http://myuser.github.io/myproject
, it will be served from /myproject
). However fonts
are also in the public
folder, so they will be served from fonts
relatively (either http://mywebsite.example/fonts
or http://myuser.github.io/myproject/fonts
). Therefore we use the relative path.
Note that since we’re avoiding the build pipeline in this example, it doesn’t verify that the file actually exists. This is why I don’t recommend this approach. Another problem is that our index.css
file doesn’t get minified and doesn’t get a hash. So it’s going to be slower for the end users, and you risk the browsers caching old versions of the file.
Which Way to Use?
Go with the first method (“Using Imports”). I only described the second one since that’s what you attempted to do (judging by your comment), but it has many problems and should only be the last resort when you’re working around some issue.
Here are some ways of doing this:
1. Importing font
For example, for using Roboto, install the package using
yarn add typeface-roboto
or
npm install typeface-roboto --save
In index.js:
import "typeface-roboto";
There are npm packages for a lot of open source fonts and most of Google fonts. You can see all fonts here. All the packages are from that project.
2. For fonts hosted by Third party
For example Google fonts, you can go to fonts.google.com where you can find links that you can put in your public/index.html
https://i.stack.imgur.com/byTI5.png
It'll be like
<link href="https://fonts.googleapis.com/css?family=Montserrat" rel="stylesheet">
or
<style>
@import url('https://fonts.googleapis.com/css?family=Montserrat');
</style>
3. Downloading the font and adding it in your source code.
Download the font. For example, for google fonts, you can go to fonts.google.com. Click on the download button to download the font.
Move the font to fonts
directory in your src
directory
src
|
`----fonts
| |
| `-Lato/Lato-Black.ttf
| -Lato/Lato-BlackItalic.ttf
| -Lato/Lato-Bold.ttf
| -Lato/Lato-BoldItalic.ttf
| -Lato/Lato-Italic.ttf
| -Lato/Lato-Light.ttf
| -Lato/Lato-LightItalic.ttf
| -Lato/Lato-Regular.ttf
| -Lato/Lato-Thin.ttf
| -Lato/Lato-ThinItalic.ttf
|
`----App.css
Now, in App.css
, add this
@font-face {
font-family: 'Lato';
src: local('Lato'), url(./fonts/Lato-Regular.otf) format('opentype');
}
@font-face {
font-family: 'Lato';
font-weight: 900;
src: local('Lato'), url(./fonts/Lato-Bold.otf) format('opentype');
}
@font-face {
font-family: 'Lato';
font-weight: 900;
src: local('Lato'), url(./fonts/Lato-Black.otf) format('opentype');
}
For ttf
format, you have to mention format('truetype')
. For woff
, format('woff')
Now you can use the font in classes.
.modal-title {
font-family: Lato, Arial, serif;
font-weight: black;
}
4. Using web-font-loader package
Install package using
yarn add webfontloader
or
npm install webfontloader --save
In src/index.js
, you can import this and specify the fonts needed
import WebFont from 'webfontloader';
WebFont.load({
google: {
families: ['Titillium Web:300,400,700', 'sans-serif']
}
});
fonts
folder under src
, but under public
instead? I tried it, but it seems not allowed...
For ttf format, you have to mention format('truetype'). For woff, format('woff')
did it for me! thank you!
woff2
cli tool and use its woff2_compress
binary on Google Font TTF download files to compress them for Web. In your @font-face
rules use the font-display
rule to speed things up even more. Finally, you only need to ship WOFF2 files for Web.
Go to Google Fonts https://fonts.google.com/ Select your font as depicted in image below:
https://i.stack.imgur.com/ekX7F.png
Copy and then paste that url in new tab you will get the css code to add that font. In this case if you go to
https://fonts.googleapis.com/css?family=Spicy+Rice
It will open like this:
https://i.stack.imgur.com/GxjHx.png
4, Copy and paste that code in your style.css and simply start using that font like this:
<Typography
variant="h1"
gutterBottom
style={{ fontFamily: "Spicy Rice", color: "pink" }}
>
React Rock
</Typography>
Result:
https://i.stack.imgur.com/udJ77.png
Local fonts linking to your react js may be a failure. So, I prefer to use online css file from google to link fonts. Refer the following code,
<link href="https://fonts.googleapis.com/css?family=Roboto" rel="stylesheet">
or
<style>
@import url('https://fonts.googleapis.com/css?family=Roboto');
</style>
You can use the Web API FontFace constructor (also Typescript) without need of CSS:
export async function loadFont(fontFamily: string, url: string): Promise<void> {
const font = new FontFace(fontFamily, `local(${fontFamily}), url(${url})`);
// wait for font to be loaded
await font.load();
// add font to document
document.fonts.add(font);
// enable font with CSS class
document.body.classList.add("fonts-loaded");
}
import ComicSans from "./assets/fonts/ComicSans.ttf";
loadFont("Comic Sans ", ComicSans).catch((e) => {
console.log(e);
});
Declare a file font.ts
with your modules (TS only):
declare module "*.ttf";
declare module "*.woff";
declare module "*.woff2";
If TS cannot find FontFace type as its still officially WIP, add this declaration to your project. It will work in your browser, except for IE.
When using different font files for normal/italic font-style
the way you specify @font-face
might need to be different depending on entry point. See my answer here:
If you choose to link the css file directly to your public/index.html then you can use font-face normally with one font-family name and different font-style:
@font-face {
font-family: "FontName"; <---
font-style: normal; <---
font-weight: normal;
src: url("path-to-assets/fonts/FontName.ttf") format("truetype");
}
@font-face {
font-family: "FontName"; <---
font-style: italic; <---
font-weight: normal;
src: url("path-to-assets/fonts/FontName-Italic.ttf") format("truetype");
}
/* Usage */
.text {
font-family: FontName;
font-style: normal;
}
.text-italic {
font-family: FontName;
font-style: italic;
}
If you choose to link the css file via Js for bundling, then you need to have a different font-family name for all your italic fonts and use font-style normal.
@font-face {
font-family: "FontName"; <---
font-style: normal; <---
font-weight: normal; /* normal | 300 | 400 | 600 | bold | etc */
src: url("path-to-assets/fonts/FontName.ttf") format("truetype");
}
@font-face {
font-family: "FontNameItalic";
font-style: normal; <----
font-weight: normal; /* normal | 300 | 400 | 600 | bold | etc */
src: url("path-to-assets/fonts/FontName-Italic.ttf") format("truetype");
}
/* Usage */
.text {
font-family: FontName;
}
.text-italic {
font-family: FontNameItalic;
}
You can use the WebFont module, which greatly simplifies the process.
render(){
webfont.load({
custom: {
families: ['MyFont'],
urls: ['/fonts/MyFont.woff']
}
});
return (
<div style={your style} >
your text!
</div>
);
}
I was making mistakes like this.
@import "https://fonts.googleapis.com/css?family=Open+Sans:300,300i,400,400i,600,600i,700,700i&subset=cyrillic,cyrillic-ext,latin-ext";
@import "https://use.fontawesome.com/releases/v5.3.1/css/all.css";
It works properly this way
@import url(https://fonts.googleapis.com/css?family=Open+Sans:300,300i,400,400i,600,600i,700,700i&subset=cyrillic,cyrillic-ext,latin-ext);
@import url(https://use.fontawesome.com/releases/v5.3.1/css/all.css);
I spent the entire morning solving a similar problem after having landed on this stack question. I used Dan's first solution in the answer above as the jump off point.
Problem
I have a dev (this is on my local machine), staging, and production environment. My staging and production environments live on the same server.
The app is deployed to staging via acmeserver/~staging/note-taking-app
and the production version lives at acmeserver/note-taking-app
(blame IT).
All the media files such as fonts were loading perfectly fine on dev (i.e., react-scripts start
).
However, when I created and uploaded staging and production builds, while the .css
and .js
files were loading properly, fonts were not. The compiled .css
file looked to have a correct path but the browser http request was getting some very wrong pathing (shown below).
The compiled main.fc70b10f.chunk.css
file:
@font-face {
font-family: SairaStencilOne-Regular;
src: url(note-taking-app/static/media/SairaStencilOne-Regular.ca2c4b9f.ttf) ("truetype");
}
The browser http request is shown below. Note how it is adding in /static/css/
when the font file just lives in /static/media/
as well as duplicating the destination folder. I ruled out the server config being the culprit.
The Referer
is partly at fault too.
GET /~staging/note-taking-app/static/css/note-taking-app/static/media/SairaStencilOne-Regular.ca2c4b9f.ttf HTTP/1.1
Host: acmeserver
Origin: http://acmeserver
Referer: http://acmeserver/~staging/note-taking-app/static/css/main.fc70b10f.chunk.css
The package.json
file had the homepage
property set to ./note-taking-app
. This was causing the problem.
{
"name": "note-taking-app",
"version": "0.1.0",
"private": true,
"homepage": "./note-taking-app",
"scripts": {
"start": "env-cmd -e development react-scripts start",
"build": "react-scripts build",
"build:staging": "env-cmd -e staging npm run build",
"build:production": "env-cmd -e production npm run build",
"test": "react-scripts test",
"eject": "react-scripts eject"
}
//...
}
Solution
That was long winded — but the solution is to:
change the PUBLIC_URL env variable depending on the environment remove the homepage property from the package.json file
Below is my .env-cmdrc
file. I use .env-cmdrc
over regular .env
because it keeps everything together in one file.
{
"development": {
"PUBLIC_URL": "",
"REACT_APP_API": "http://acmeserver/~staging/note-taking-app/api"
},
"staging": {
"PUBLIC_URL": "/~staging/note-taking-app",
"REACT_APP_API": "http://acmeserver/~staging/note-taking-app/api"
},
"production": {
"PUBLIC_URL": "/note-taking-app",
"REACT_APP_API": "http://acmeserver/note-taking-app/api"
}
}
Routing via react-router-dom
works fine too — simply use the PUBLIC_URL
env variable as the basename
property.
import React from "react";
import { BrowserRouter } from "react-router-dom";
const createRouter = RootComponent => (
<BrowserRouter basename={process.env.PUBLIC_URL}>
<RootComponent />
</BrowserRouter>
);
export { createRouter };
The server config is set to route all requests to the ./index.html
file.
Finally, here is what the compiled main.fc70b10f.chunk.css
file looks like after the discussed changes were implemented.
@font-face {
font-family: SairaStencilOne-Regular;
src: url(/~staging/note-taking-app/static/media/SairaStencilOne-Regular.ca2c4b9f.ttf)
format("truetype");
}
Reading material
https://create-react-app.dev/docs/adding-custom-environment-variables#adding-development-environment-variables-in-env
https://create-react-app.dev/docs/deployment#serving-apps-with-client-side-routing
https://create-react-app.dev/docs/advanced-configuration this explains the PUBLIC_URL environment variable Create React App assumes your application is hosted at the serving web server's root or a subpath as specified in package.json (homepage). Normally, Create React App ignores the hostname. You may use this variable to force assets to be referenced verbatim to the url you provide (hostname included). This may be particularly useful when using a CDN to host your application.
this explains the PUBLIC_URL environment variable Create React App assumes your application is hosted at the serving web server's root or a subpath as specified in package.json (homepage). Normally, Create React App ignores the hostname. You may use this variable to force assets to be referenced verbatim to the url you provide (hostname included). This may be particularly useful when using a CDN to host your application.
I added
@font-face {
font-family: 'Sanchez-Regular';
src: local('Sanchez-Regular'),url(../assets/fonts/Sanchez/Sanchez-Regular.ttf) format('truetype');
}
and it worked awesomely just later use it like we use all other fonts after doing this in index.css
This is for people using an NX(nwrl) monorepo, I have tested this while using it there, may work for other CRA apps. First add the fonts in the assets/fonts folder, create a fonts folder, if not present.
Then in your main app.js/tsx Add this code, with your existing jsx code
<style type="text/css">{`
@font-face {
font-family: 'MaterialIcons';
src: url(${require('react-native-vector-icons/Fonts/MaterialIcons.ttf')}) format('truetype');
}
@font-face {
font-family: 'MaterialCommunityIcons';
src: url(${require('react-native-vector-icons/Fonts/MaterialCommunityIcons.ttf')}) format('truetype');
}
@font-face {
font-family: 'Mulish-Bold';
src: url(${require('../assets/fonts/Mulish-Bold.ttf')}) format('truetype');
}
@font-face {
font-family: 'Your Font Name';
src: url(${require('../assets/fonts/font-file-name.otf')}) format('truetype');
}
`}</style>
It should look something like this, wrap with Fragment tags -
<>
<style type="text/css">{`
@font-face {
font-family: 'MaterialIcons';
src: url(${require('react-native-vector-icons/Fonts/MaterialIcons.ttf')}) format('truetype');
}
@font-face {
font-family: 'MaterialCommunityIcons';
src: url(${require('react-native-vector-icons/Fonts/MaterialCommunityIcons.ttf')}) format('truetype');
}
@font-face {
font-family: 'Mulish-Bold';
src: url(${require('../assets/fonts/Mulish-Bold.ttf')}) format('truetype');
}
@font-face {
font-family: 'CircularStd-Book';
src: url(${require('../assets/fonts/CircularStd-Book.otf')}) format('truetype');
}
`}</style>
//Your JSX, or your main app level code
</>
There's another step, in your custom webpack config file, add these loaders, if you don't have one then, on your parent level, create a webpack.js file -
Your webpack js should look something like this -
const getWebpackConfig = require('@nrwl/react/plugins/webpack');
function getCustomWebpackConfig(webpackConfig) {
const config = getWebpackConfig(webpackConfig);
const isProduction = webpackConfig.mode === 'production';
if (!isProduction) {
config.resolve.alias = {
'react-native': 'react-native-web',
'react-native-linear-gradient': 'react-native-web-linear-gradient',
'react-native-localization': 'react-localization'
};
config.module.rules.push(
{
test: /\.ttf$/,
loader: require.resolve('file-loader'),
options: { esModule: false, name: 'static/media/[path][name].[ext]' },
},
{
test: /\.otf$/,
loader: require.resolve('file-loader'),
options: { esModule: false, name: 'static/media/[path][name].[ext]' },
},
{
test: /\.(js|jsx)$/,
exclude: function (content) {
return (
/node_modules/.test(content) &&
!/\/react-native-paper\//.test(content) &&
!/\/react-native-vector-icons\//.test(content) &&
);
},
use: {
loader: require.resolve('@nrwl/web/src/utils/web-babel-loader.js'),
options: {
presets: [
[
'@nrwl/react/babel',
{
runtime: 'automatic',
useBuiltIns: 'usage',
},
],
],
plugins: [
["module-resolver", {
"alias": {
"^react-native$": "react-native-web",
"react-native-linear-gradient": "react-native-web-linear-gradient",
"react-native-localization": "react-localization"
}
}]
],
},
},
},
);
}
return config;
}
module.exports = getCustomWebpackConfig;
Your babel loader, may be different but the ttf and otf rules are important to be added. I'm using this with react native for web functionality. You could remove the aliases if it isn't required by your project.
Thanks to this blog, which made me understand this concept better - https://blog.nrwl.io/step-by-step-guide-on-creating-a-monorepo-for-react-native-apps-using-nx-704753b6c70e
Success story sharing
ttf
font, you should givetruetype
instead ofttf
as the parameter to theformat
*.otf
you need to putopentype
.bold
,bold+italic
, ..)? This one should help: stackoverflow.com/a/2436830/3592419