Webpack’s default behavior is to treat font files like any other asset, meaning it will bundle them up and emit them to your output directory. The real magic comes from how you tell it to find and process them, and how you leverage its optimization capabilities to keep your site fast.
Here’s a common setup to load and optimize web fonts using Webpack:
1. Configure webpack.config.js
You’ll primarily use the module.rules and output.publicPath configurations.
// webpack.config.js
const path = require('path');
module.exports = {
// ... other configurations
module: {
rules: [
{
test: /\.(woff|woff2|eot|ttf|otf)$/i,
type: 'asset/resource', // Or 'asset' for automatic type selection
generator: {
filename: 'fonts/[name].[hash:8][ext]', // Output path and filename for fonts
},
},
// ... other rules
],
},
output: {
path: path.resolve(__dirname, 'dist'),
filename: 'bundle.js',
publicPath: '/', // Crucial for correctly linking assets
},
// ... other configurations
};
test: /\.(woff|woff2|eot|ttf|otf)$/i: This regular expression tells Webpack to look for files with these common font extensions.type: 'asset/resource': This tells Webpack to treat these files as separate assets and emit them to the output directory.assetis a newer, more flexible option that automatically chooses betweenasset/resourceandasset/inlinebased on file size.generator.filename: 'fonts/[name].[hash:8][ext]': This defines the output path and filename for your font files.fonts/places them in afontssubdirectory within your output.[name]is the original filename,[hash:8]adds an 8-character hash for cache busting, and[ext]is the file extension.output.publicPath: '/': This is absolutely critical. It specifies the base path for all assets served from your Webpack build. If your application is hosted athttps://example.com/my-app/, you’d set this to/my-app/. If it’s at the root,/is correct. Without this, your CSS (or JS) might not be able to find the font files because the paths will be incorrect.
2. Import Fonts in Your CSS/SCSS
You can then import your fonts directly into your CSS or SCSS files, and Webpack will handle them.
/* style.css */
@font-face {
font-family: 'MyCustomFont';
src: url('./fonts/my-custom-font.woff2') format('woff2'),
url('./fonts/my-custom-font.woff') format('woff');
font-weight: normal;
font-style: normal;
}
body {
font-family: 'MyCustomFont', sans-serif;
}
When Webpack processes this CSS, it will see the url() references. Because of the rule you defined in webpack.config.js, it will pick up these font files, process them according to the generator.filename pattern, and update the url() paths in the generated CSS to point to the correct output location (e.g., /fonts/my-custom-font.a1b2c3d4.woff2).
3. Using asset type for automatic optimization
If you use type: 'asset' instead of asset/resource, Webpack will automatically decide whether to emit the font as a separate file or inline it as a data URL based on a default size limit (8KB). For fonts, you almost always want them as separate files for better caching and performance, so asset/resource or configuring parser.dataUrlCondition.maxSize for asset is usually preferred.
// webpack.config.js (using 'asset' and configuring maxSize)
// ...
module: {
rules: [
{
test: /\.(woff|woff2|eot|ttf|otf)$/i,
type: 'asset', // Let Webpack decide
parser: {
dataUrlCondition: {
maxSize: 0 // Force all fonts to be emitted as separate resources
}
},
generator: {
filename: 'fonts/[name].[hash:8][ext]',
},
},
// ...
],
},
// ...
Setting maxSize: 0 effectively forces Webpack to treat all fonts as resources to be emitted separately, regardless of their size. This ensures you get the caching benefits of separate files.
The Mental Model:
Think of Webpack as a factory. You feed it raw materials (your source files: JS, CSS, fonts, images). You give it instructions (the webpack.config.js) on how to process each type of material. For fonts, the instruction is: "When you see a font file referenced, take it, give it a unique name (with a hash), put it in the fonts folder in the final output, and make sure any CSS or JS that needs it knows exactly where to find it." The publicPath is the factory’s shipping address – it tells everything else where to expect the finished goods.
The One Thing Most People Don’t Know:
The generator.filename option isn’t just for fonts; it applies to any asset processed by that rule. You can have different generator.filename patterns for images, fonts, or other assets within the same module.rules section by using separate rules with different test regexps. This allows for fine-grained control over where different types of assets end up in your build output.
The next logical step is to explore how to load and optimize other asset types, like images.