gatsbyjs / gatsby

The best React-based framework with performance, scalability and security built in.
https://www.gatsbyjs.com
MIT License
55.27k stars 10.32k forks source link

How to self-host fonts? #2583

Closed ooloth closed 6 years ago

ooloth commented 6 years ago

Self-hosting fonts included in https://github.com/kyleamathews/typefaces is working great for me. However, I'm having trouble self-hosting other fonts.

Following the advice of @KyleAMathews in this issue, I created a ./src/fonts/ directory and put the font files there. I then imported the font files in src/layouts/index.js like this:

import '../fonts/font-file.woff2'
import '../fonts/font-file.woff'

I'm expecting to see the files copied to the ./public/ directory, but that isn't happening.

Am I doing something wrong?

nealoke commented 6 years ago

Just use url() inside the css where you import the font. This should tell webpack to also include this in the build.

ooloth commented 6 years ago

I deleted my earlier replies that contained a few errors...

I added the following @font-face declaration to src/layouts/font-face.css (which I then imported into src/layouts/index.js):

@font-face {
  font-family: 'Font File';
  src: url('..fonts/font-file.woff2') format('woff2'),
    url('..fonts/font-file.woff') format('woff');
  font-weight: normal;
  font-style: normal;
}

This resolved the net::ERR_ABORTED error in the console, but the font files are still not being copied to /public/. Any idea why?

m-allanson commented 6 years ago

That looks very similar to the setup I have, which has been working OK. Are you missing a / near the beginning of the url paths? e.g. change this:

url('..fonts/font-file.woff') format('woff')

to

url('../fonts/font-file.woff') format('woff')

Also I believe that files are not copied to the public/ directory if you're running Gatsby's development mode (gatsby develop), that only happens in production mode (gatsby build). You can check the Network tab of your browser's developer tools to confirm that the font files are loaded when running in development mode.

ooloth commented 6 years ago

@m-allanson Apologies for the typo! I had the / in my code, but missed it here.

Thanks for clarifying that files are not copied to public/ in development mode! I see in the Network tab that the font files are being loaded, so everything appears to be working properly.

monsieurnebo commented 6 years ago

Does it works with styled-components?

I just tried the following code:

  @font-face {
    font-family: "RFRufo-Bold";
    font-style: normal;
    font-weight: normal;
    src: url("../../etc/fonts/RFRufo-Bold.woff") format("woff"), url("../../etc/fonts/RFRufo-Bold.ttf") format("ttf");
  }

Which load my font from /etc/foo/bar, outside of the /public/ directory --> works on my computer because there is the /etc/ directory, but it wont be preset on the production server.

Shouldn't Gatsby / Webpack load this file into my public dir?

KyleAMathews commented 6 years ago

Webpack file-loader doesn't work with styled components. Just traditional CSS.

monsieurnebo commented 6 years ago

@KyleAMathews Thanks for the confirmation. Could I declare my own file-loader webpack rule?

Something like this:

    config.loader("custom-fonts", {
      test    : /(fonts)\/(.*)\.(eot|svg|ttf|woff|woff2)$/i,
      loader  : "file-loader",
      query   :  {
        name: "fonts/[name].[ext]"
      }
    });
KyleAMathews commented 6 years ago

No — webpack has no way of knowing about styled components. Gatsby already has file-loader setup for fonts. It's just webpack doesn't see what you're declaring in styled components. Just copy the css to a fonts.css and import it into your layouts/index.js

monsieurnebo commented 6 years ago

Another solution would be to make a JS import as:

import RufoBold from "../../etc/fonts/RFRufo-Bold.ttf";

But it's not really viable with multiple fonts and extensions... I will stick with one single CSS file for fonts declarations then.

Thanks for your answers.

monsieurnebo commented 6 years ago

After some discussions, we finally picked up the JS import solution in a dedicated file:

// shared/css/fonts.js

import RufoBlackTTF from "fonts/RFRufo-Black.ttf";
import RufoBlackWOFF from "fonts/RFRufo-Black.woff";
import RufoBoldTTF from "fonts/RFRufo-Bold.ttf";
import RufoBoldWOFF from "fonts/RFRufo-Bold.woff";
import RufoSemiBoldTTF from "fonts/RFRufo-SemiBold.ttf";
import RufoSemiBoldWOFF from "fonts/RFRufo-SemiBold.woff";

import RobotoRegularTTF from "fonts/Roboto-Regular.ttf";
import RobotoBoldTTF from "fonts/Roboto-Bold.ttf";
import RobotoMediumTTF from "fonts/Roboto-Medium.ttf";

export {
  RufoBlackTTF,
  RufoBlackWOFF,
  RufoBoldTTF,
  RufoBoldWOFF,
  RufoSemiBoldTTF,
  RufoSemiBoldWOFF,

  RobotoRegularTTF,
  RobotoBoldTTF,
  RobotoMediumTTF
};

Then used from the @font-face declaration:

// shared/css/globalStyles.js

import { createGlobalStyle } from "styled-components";
import fontFiles from "./fonts";

export default createGlobalStyle `

  @font-face {
    font-family: "Rufo-Black";
    font-style: normal;
    font-weight: normal;
    src: local("Rufo Black"), local("Rufo-Black"), url(${fontFiles.RufoBlackTTF}) format("ttf"), url(${fontFiles.RufoBlackWOFF}) format("woff");
  }

// etc...

That seems like a lot of lines, but it's a one time thing to do for the project. It let us keep a CSS in JS direction, without a single CSS file out of place.

11/12/18: The example has been updated with the new createGlobalStyle syntax.

tlvenn commented 6 years ago

Is there a way currently to inline a given font in base64 inside the html file ?

deadcoder0904 commented 6 years ago

@monsieurnebo's answer worked but with one little change

I had to change

format("ttf")

to

format("truetype")
socksrust commented 6 years ago

@monsieurnebo tip worked like a charm here, thanks <3

ajmalafif commented 5 years ago

I tried @monsieurnebo solution but it seems like it didn't detect injectGlobal at all.

Repo: https://github.com/ajmalafif/gatsby-interUI/tree/master/src/shared/css Demo: https://gatsby-interui.netlify.com/

deadcoder0904 commented 5 years ago

@ajmalafif Bcz for v4 injectGlobal has been deprecated. Use createGlobalStyle instead.

ajmalafif commented 5 years ago

@deadcoder0904 been pulling my hair, thanks for the heads up! Hopefully that helps others too. Thanks again!

monsieurnebo commented 5 years ago

The example has been updated with the new createGlobalStyle syntax.

alfianridwan commented 5 years ago

@monsieurnebo Hi I tried your method, and imported the globalStyles.js file into a component, styled one of the elements with a font that I imported, but when I gatsby build and preview it on my github it doesn't work. The fonts aren't applied. Any suggestions?

alfianridwan commented 5 years ago

@monsieurnebo 3 hours passed! Although I'm a beginner to React and Gatsby, I just can't stop pulling my hair for this!

Just for context I followed exactly what you've given, except using my own fonts, but the browser does not even detect a font. Chrome's dev tool under the Network tab does not display any fonts loaded. /static do have the fonts, and console logging one of the fonts (eg. fontfiles.fontFileWOFF) do display the minified/edited font file WHICH MEANS it does go through Gatsby's build process. Since the globalStyles.js was exported, I imported it into header.js, which I use for my logo and links.

Logically, this means that header.js component has the @font-face component implemented, hence applying the font in any element in the component SHOULD work. But nope. I thought I succeeded but when I publish to netlify and view the website on my phone its Times News Roman.

How do you make use of the globalStyles.js and how do you (correctly) import them?

LekoArts commented 5 years ago

Maybe that helps some future Google users: I implemented Inter UI with these fonts and CSS and added it to the Layout component while using gatsby-plugin-layout

alfianridwan commented 5 years ago

@LekoArts Thank you so much! that fixed it. I think this should be the answer and anyone struggling with the earlier answer should follow that method. I think its clean and easy.

monsieurnebo commented 5 years ago

@alfianridwan createGlobalStyle must be used as a (styled) component:

Returns a StyledComponent that does not accept children. Place it at the top of your React tree and the global styles will be injected when the component is "rendered". Source

Just use it in your root component (probably your pages). Based on my example, it would looks like that:

// RootComponent.jsx
import GlobalStyles from "shared/css/globalStyles.js";

export default RootComponent extends React.Component {
  render() {
    return (
      <div>
        <GlobalStyles />
        {/* Your content... */}
      </div>
    );
  }
}

The GlobalStyles tag will inject the CSS into the whole project, making fonts accessible to further usage.

If you cannot manage to make it work, feel free to share a repository and I'll try to take a look at it.

alfianridwan commented 5 years ago

@monsieurnebo does that mean you have to add that tag for every page you create? What benefit does it have over the gatsby-plugin-layout method which automatically sets the global stylings as the parent to whatever code you've written? I'm only a week into React and Gatsby so any helpful and detailed answer or thought behind your previous answer will help :)

monsieurnebo commented 5 years ago

@alfianridwan The feature provided by this plugin (layouts) has been removed in Gatsby v2, so yes, my example implies that you add this tag in every page.

However, you can avoid the code duplication by using a layout component, or the gatsby-plugin-layout (that bring back the v1 feature). There is a explanation about this (and a little comparison between the two solutions) in the official migration guide from v1 to v2.

If you're really interested by the subject, you can read the related RFC (Request For Comments) that explains the whole reflexion.

elliotec commented 5 years ago

@LekoArts solution worked for me.

PolGuixe commented 5 years ago

Using @LekoArts approach... Would it be possible to configure Webpack to load the fonts using a <link rel=preload> in the <head>?

Currently getting this warning on lighthouse.

Screenshot 2019-07-10 at 20 44 35

bsgreenb commented 4 years ago

If you're using TypeScript make sure to add:

declare module "*.ttf";

mwskwong commented 3 years ago

After some discussions, we finally picked up the JS import solution in a dedicated file:

// shared/css/fonts.js

import RufoBlackTTF from "fonts/RFRufo-Black.ttf";
import RufoBlackWOFF from "fonts/RFRufo-Black.woff";
import RufoBoldTTF from "fonts/RFRufo-Bold.ttf";
import RufoBoldWOFF from "fonts/RFRufo-Bold.woff";
import RufoSemiBoldTTF from "fonts/RFRufo-SemiBold.ttf";
import RufoSemiBoldWOFF from "fonts/RFRufo-SemiBold.woff";

import RobotoRegularTTF from "fonts/Roboto-Regular.ttf";
import RobotoBoldTTF from "fonts/Roboto-Bold.ttf";
import RobotoMediumTTF from "fonts/Roboto-Medium.ttf";

export {
  RufoBlackTTF,
  RufoBlackWOFF,
  RufoBoldTTF,
  RufoBoldWOFF,
  RufoSemiBoldTTF,
  RufoSemiBoldWOFF,

  RobotoRegularTTF,
  RobotoBoldTTF,
  RobotoMediumTTF
};

Then used from the @font-face declaration:

// shared/css/globalStyles.js

import { createGlobalStyle } from "styled-components";
import fontFiles from "./fonts";

export default createGlobalStyle `

  @font-face {
    font-family: "Rufo-Black";
    font-style: normal;
    font-weight: normal;
    src: local("Rufo Black"), local("Rufo-Black"), url(${fontFiles.RufoBlackTTF}) format("ttf"), url(${fontFiles.RufoBlackWOFF}) format("woff");
  }

// etc...

That seems like a lot of lines, but it's a one time thing to do for the project. It let us keep a CSS in JS direction, without a single CSS file out of place.

11/12/18: The example has been updated with the new createGlobalStyle syntax.

@monsieurnebo This method will embed the font files into the js bundle, which is not ideal, especially for fonts that are low priority.

monsieurnebo commented 3 years ago

@monsieurnebo This method will embed the font files into the js bundle, which is not ideal, especially for fonts that are low priority.

What would you suggest then? 🤔

mwskwong commented 3 years ago

@monsieurnebo This method will embed the font files into the js bundle, which is not ideal, especially for fonts that are low priority.

What would you suggest then? 🤔

I'm using emotion (with material-ui v5). I end up simply create a CSS file and import it into my theme.js. That kind of breaks the idea of 100% CSS in JS, but at least I don't have to increase the bundle size for no benifits.

I'm also looking for suggestions for a better way to handle this. In Create React App, if we import the font like you did, the "actual" path of the font file will be returned, instead of the base64 encoding of the font. I guess there could be a feature request for that?

monsieurnebo commented 3 years ago

Not sure if loading an external CSS file from server is worth it in order to avoid some extra bundle size weight. Especially if it requires adding one (two?) more dependency.

I think the difference is so small between these two methods... That it's more a question of preference than anything else :)

Thanks for your feedback 👍

mwskwong commented 3 years ago

I mean the point is, Gatsby will already embed some of the fonts into the HTML files, given that the font file is small. Now if we import the fonts directly from JS. It will also embed the font into the JS bundle. So in some cases, the fonts are embedded in BOTH the JS bundle and the HTML.

hbj commented 3 years ago

After some discussions, we finally picked up the JS import solution in a dedicated file:

// shared/css/fonts.js

import RufoBlackTTF from "fonts/RFRufo-Black.ttf";
import RufoBlackWOFF from "fonts/RFRufo-Black.woff";
import RufoBoldTTF from "fonts/RFRufo-Bold.ttf";
import RufoBoldWOFF from "fonts/RFRufo-Bold.woff";
import RufoSemiBoldTTF from "fonts/RFRufo-SemiBold.ttf";
import RufoSemiBoldWOFF from "fonts/RFRufo-SemiBold.woff";

import RobotoRegularTTF from "fonts/Roboto-Regular.ttf";
import RobotoBoldTTF from "fonts/Roboto-Bold.ttf";
import RobotoMediumTTF from "fonts/Roboto-Medium.ttf";

export {
  RufoBlackTTF,
  RufoBlackWOFF,
  RufoBoldTTF,
  RufoBoldWOFF,
  RufoSemiBoldTTF,
  RufoSemiBoldWOFF,

  RobotoRegularTTF,
  RobotoBoldTTF,
  RobotoMediumTTF
};

Then used from the @font-face declaration:

// shared/css/globalStyles.js

import { createGlobalStyle } from "styled-components";
import fontFiles from "./fonts";

export default createGlobalStyle `

  @font-face {
    font-family: "Rufo-Black";
    font-style: normal;
    font-weight: normal;
    src: local("Rufo Black"), local("Rufo-Black"), url(${fontFiles.RufoBlackTTF}) format("ttf"), url(${fontFiles.RufoBlackWOFF}) format("woff");
  }

// etc...

That seems like a lot of lines, but it's a one time thing to do for the project. It let us keep a CSS in JS direction, without a single CSS file out of place. 11/12/18: The example has been updated with the new createGlobalStyle syntax.

@monsieurnebo This method will embed the font files into the js bundle, which is not ideal, especially for fonts that are low priority.

I tried this solution and it doesn't embed the font files, it copies the files to the 'public' folder adding a hash to their names and sets the values of the imports to the paths of the generated font files, so this doesn't add to the bundle size.

mwskwong commented 3 years ago

I tried this solution and it doesn't embed the font files, it copies the files to the 'public' folder adding a hash to their names and sets the values of the imports to the paths of the generated font files, so this doesn't add to the bundle size.

Perhaps, that because I was using woff2 files and it doesn't go above the threshold.

hbj commented 3 years ago

I tried this solution and it doesn't embed the font files, it copies the files to the 'public' folder adding a hash to their names and sets the values of the imports to the paths of the generated font files, so this doesn't add to the bundle size.

Perhaps, that because I was using woff2 files and it doesn't go above the threshold.

Makes sense, it depends on the file size whether an imported file is embedded or not. Perhaps there is a way to disable embedding font files by setting the threshold to 0 (just guessing, don't know how this works)?