Working with Fonts with Webpack

Most web sites have a custom font or two. We try to keep them out by using font stacks, but eventually a title or an icon will need a custom font. Most of the time we can get away with loading the fonts from a CDN (like Google Fonts). My new website is going to use a custom font that I found on FontSpace. To be clear, adding a font to your build should be a last resort. Now, how do you do it?

Prepare the font

Most fonts are distributed as truetype fonts (or TTF format). We need a lot more than that. The best way to get the other formats is to use a font converter. I use for my conversion needs as it is a very minimal process. Just check the font-face box and it will pre-select all the formats you need. Click on Select Font(s), choose your file, click on Done and the site will convert the file to all the formats then allow us to download the result as a ZIP file. Not all truetype fonts can be converted unfortunately. You tend to get what you pay for and I’m using a “free for personal use” font, so I can’t really complain.

I’ve placed all the files I received into client/src/fonts except for the CSS file so that I can access them from anywhere. I’ve converted the CSS file to an SCSS file via a simple rename and placed that in the client/src directory. For reference, my selected font is called Darkenstone. Finally, I’ve added an @import statement to the app.scss file to bring the font into my application.

Update your scss files

I’ve created a new React component for the header – it’s got an associated scss file and I’ve linked it into my output. Now I want to apply the font I’ve just downloaded to the h1 element:

.header {
    display: flex;
    flex-flow: row nowrap;

    &-brand {
        order: 1;
        flex-grow: 1;

        > h1 {
            font: 16pt 'Darkenstone';

    &-menu {
        order: 2;

You will note that the webpack will fail if you try to run npm start now. That’s because webpack doesn’t know how to deal with fonts.

Configure Webpack to include font files

The first step to configuration is to handle the font files themselves. My webpack.config.js supports js, jsx and scss files right now. I need to add support for eot, woff, woff2, svg and ttf files. I want webpack to just copy the files to my public area. Fortunately, I can do that with the file-loader:

var ExtractTextPlugin = require('extract-text-webpack-plugin');

module.exports = {
    entry: {
        grumpywizards: './client/src/app.jsx'
    devtool: 'source-map',
    module: {
        loaders: [
                test: /\.jsx?$/,
                loader: 'babel',
                exclude: /node_modules/
                test: /\.scss$/,
                loader: ExtractTextPlugin.extract(
                    'style', // The backup style loader
                test: /\.(eot|svg|ttf|woff|woff2)$/,
                loader: 'file?name=public/fonts/[name].[ext]'
    externals: {
        'react': 'React',
        'react-dom': 'ReactDOM'
    output: {
        filename: 'public/[name].js'
    sassLoader: {
        includePaths: [ 'client/style' ]
    plugins: [
        new ExtractTextPlugin('public/grumpywizards.css')

When we run this webpack, we will see the files appear in the public area and then they are referenced by the grumpywizards.js file. The important thing to remember is that the src statements in the font should be relative to their eventual location. That’s why I place the font files in the same area as the app file and the fonts in a directory called fonts in the same directory.

Of course, we can also embed the fonts as a base-64 encoded blob, called a Data URI, inside the resulting CSS file. There are some caveats – IE8 will only support blobs up to 32K, for example. Opera 11 only supports 64K. I’ve set myself a browser support list that doesn’t include older browsers, so I should be good to go. To fix the configuration, we need to change the configuration to use the url-loader:

    module: {
        loaders: [
            // Javascript
            { test: /\.jsx?$/, loader: 'babel', exclude: /node_modules/ },
            // Stylesheets
            { test: /\.scss$/, loader: ExtractTextPlugin.extract('style', 'css?sourceMap!sass?sourceMap') },
            // Font Definitions
            { test: /\.svg$/, loader: 'url?limit=65000&mimetype=image/svg+xml&name=public/fonts/[name].[ext]' },
            { test: /\.woff$/, loader: 'url?limit=65000&mimetype=application/font-woff&name=public/fonts/[name].[ext]' },
            { test: /\.woff2$/, loader: 'url?limit=65000&mimetype=application/font-woff2&name=public/fonts/[name].[ext]' },
            { test: /\.[ot]tf$/, loader: 'url?limit=65000&mimetype=application/octet-stream&name=public/fonts/[name].[ext]' },
            { test: /\.eot$/, loader: 'url?limit=65000&mimetype=application/[name].[ext]' }

I’ve flattened the objects so that each type of file has exactly one line. I had to split each of the font types into its own line because the MIME type of each font is different:

  • EOT (Embedded Opentype) files are application/
  • SVG files are image/svg+xml
  • TTF (and OTF) files are application/octet-stream
  • WOFF/WOFF2 files are application/font-woff(2)

To make this work properly, I’ve changed how I store my files. The font files are stored in client/fonts/font-darkenstone and there is a darkenstone.scss file in the same directory with the following contents:

@font-face {
  font-family: 'Darkenstone';
  src: url('./Darkenstone.eot');
  src: url('./Darkenstone.eot?#iefix') format('embedded-opentype'),
       url('./Darkenstone.woff2') format('woff2'),
       url('./Darkenstone.woff') format('woff'),
       url('./Darkenstone.ttf') format('truetype'),
       url('./Darkenstone.svg#Darkenstone') format('svg');
  font-weight: normal;
  font-style: normal;

Note that the files are all relative to the scss file. I now bring this file into the app.jsx file:

import React from 'react';
import ReactDOM from 'react-dom';

// Components
import Application from './views/Application.jsx';

// Stylesheets

// Render the application

Doing it this way ensures that the rendered CSS is correct. Don’t try to import the SCSS file into another SCSS file unless you want to do major work on relative paths – it’s messy.

There are some promising developments in webpack for automatically converting fonts. Check out the font-loader project as an example. The project seems abandoned, but it may be worth resurrecting so that fonts can be distributed as webpack enabled npm packages.

You can, as always, find this addition to my project on my GitHub repository.