Fixing Flash of Unstyled Content (FOUC) in your Webpack 4 Build
The Issue
When I first deployed my Crypto Coaster project to production, I was serving the page's assets straight up from my Node/Express app. Most modern web pages use some type of bundler for numerous reasons which you can read aboue here. When I finally migrated the app to using a modern bundler, Webpack 4, I had run into an issue where the page would have a flash of unstyled content (FOUC). I hadn't run into this issue from serving the assets straight up and wanted to get rid of FOUC.
My initial webpack.config.js
file looked like this:
const webpack = require('webpack');
const HtmlWebpackPlugin = require('html-webpack-plugin');
let config = {
entry: './src/js/app.js',
output: {
filename: './bundle.js'
},
devServer: {
contentBase: './dist'
},
module: {
rules: [
{
test: /\.js$/,
exclude: /(node_modules)/,
use: {
loader: 'babel-loader',
options: {
presets: ['@babel/preset-env']
}
}
},
{
test: /\.(gif|png|jpe?g|svg)$/i,
use: [
'file-loader',
]
},
{
test: /\.scss$/,
use: [{
loader: "style-loader"
}, {
loader: "css-loader"
}, {
loader: "sass-loader"
}]
}
]
},
plugins: [
new HtmlWebpackPlugin({
template: 'src/index.html'
})
]
}
module.exports = config;
I want to focus on the style loading section of the config (as that is where the issue was arising). I was using 3 different loaders to bundle the .css
of the project:
- sass-loader - compiles
.scss
files into.css
- css-loader - reads in
css
files as a string - style-loader - takes the css string from the
css-loader
and creates a<style>
tag in the page's<head>
with those styles.
This was all well and good, the project would get bundled correctly. However, on load, the following page would flash for under a second:
The reason why this config setup caused a FOUC was because of the style-loader
. As the loader inlines all the css style within the bundle, the browser has to first parse the javascript and only after processing it can apply the styles to the page. This causes the FOUC.
The Soution
Explanation
The fix to the FOUC was to include a Webpack Plugin called Extract Text Loader. This plugin basically extracts all the css
that is required in the bundle but instead of inlining it (causing the FOUC), creates a separate css
file. By simply including this output css
file, the css
loads in parallel result in no FOUC!
Config Updates
I added the plugin to my dev dependencies for Webpack 4:
npm i -D extract-text-webpack-plugin@next
(Please note, as of 9/4/18 the @next versioning is required. This makes the plugin compatible with Webpack 4). And then included it within webpack.config.js
:
const ExtractTextPlugin = require("extract-text-webpack-plugin");
.
I then had to update the style config section from:
{
test: /\.scss$/,
use: [{
loader: "style-loader"
}, {
loader: "css-loader"
}, {
loader: "sass-loader"
}]
}
To:
{
test: /\.scss$/,
use: ExtractTextPlugin.extract({
use: [
'css-loader',
'sass-loader'
]
})
}
What basically changed was two things:
- Place the loaders within the
ExtractTextPlugin
- Removal of the
style-loader
(as that inlines the css into your bundle.js)
The final step was to add the ExtractTextPlugin
to the plugin section of the config:
plugins: [
new HtmlWebpackPlugin({
template: 'src/index.html',
filename: 'index.html',
}),
new ExtractTextPlugin("styles.css")
]
This will instruct ExtractTextPlugin
to output the css with the name styles.css
in our dist
directory.
I have also included the HtmlWebpackPlugin. This plugin works great with ExtractTextPlugin
! After placing your require
in your main js file (app.js
) in my case: require('../css/styles.scss');
, it will automatically detect the generated styles.css
file is required and automatically add a reference to it in the generated index.html
.
If you don't want to use the HtmlWebpackPlugin
, simply just add a reference to the output styles.css
.
Conclusion
After the above changes, the app now loads as expected with no FOUC and has reduced the amount of data required to load my application!