30

I am getting FOUC when loading css inside of my entry point when using webpack. If I remove my css from being loaded by webpack and just include it in my html file as a normal link then the problem with FOUC goes away.

Note: This not just with bootstrap framework, I have tested with Foundation and Materialize with the same results

The code posted below is just a test example of my problem using Bootstrap.

Html code

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>Title</title>

</head>
<body>
<div class="container">
    <div class="jumbotron">
        <h1>Navbar example</h1>
    </div>
</div> <!-- /container -->

<script src="build/bundle.js"></script>
</body>
</html>

bootstrap.js main entry point

import "../node_modules/bootstrap/dist/css/bootstrap.css";
import bootstrap from 'bootstrap'

$(document).ready(function () {
   console.log('bootstrap loaded')
});

webpack.config.js

var path = require('path');
const ProvidePlugin = require('webpack/lib/ProvidePlugin');
const webpack = require("webpack");

module.exports = {
  entry: './src/bootstrap.js',
  output: {
    path: path.join(__dirname, 'build'),
    filename: 'bundle.js'
  },
    resolve: {
        extensions: ['', '.js']
    },
    plugins: [
        new webpack.ProvidePlugin({
            $: "jquery",
            jQuery: "jquery",
            'window.jQuery': 'jquery'
        })
    ],
  devtool: 'inline-source-map',
  module: {
      resolve: {
          modulesDirectories: ['node_modules']
      },
    loaders: [
      {
        test: path.join(__dirname, 'src'),
        loader: 'babel-loader',
          query: {
              presets: ['es2015']
          }
      },
        { test: /\.css?$/, loader: 'style!css'},
        { test: /\.html$/, loader: 'html' },
        { test: /\.(png|gif|jpg)$/, loader: 'url', query: { limit: 8192 } },
        { test: /\.woff2(\?v=[0-9]\.[0-9]\.[0-9])?$/, loader: 'url', query: { limit: 10000, mimetype: 'application/font-woff2' } },
        { test: /\.woff(\?v=[0-9]\.[0-9]\.[0-9])?$/, loader: 'url', query: { limit: 10000, mimetype: 'application/font-woff' } },
        { test: /\.(ttf|eot|svg)(\?v=[0-9]\.[0-9]\.[0-9])?$/, loader: 'file' },
    ]
  }
};
dan
  • 2,857
  • 6
  • 34
  • 60

3 Answers3

27

ExtractTextWebpackPlugin will allow you to output your CSS as a separate file rather than having it embedded in your JS bundle. You can then include this file in your HTML, which as you said, prevents the flash of unstyled content.

I'd recommend only using this in production environments, as it stops hot-loading from working and makes your compile take longer. I have my webpack.config.js set up to only apply the plugin when process.env.NODE_ENV === "production"; you still get the FOUC when you're doing a development build/running the dev server, but I feel like this is a fair trade off.

For more information on how to set this up, take a look at SurviveJS's guide.


Update: As noted in the comments, ExtractTextWebpackPlugin has now been superceded by mini-css-extract-plugin - you should use that instead.

Joe Clay
  • 33,401
  • 4
  • 85
  • 85
  • A more convenient approach, which works even with hot-reloading, is to hide the content until the stylesheet has been applied. With webpack controlling all the loading, I suppose it is harder to implement. – Luis Crespo Oct 08 '16 at 10:34
  • 1
    @LuisCrespo: That's definitely a possibility too! Personally though, I think a flash of no content can be just as bad as a flash of unstyled content. The approach in my answer should eliminate both, as the CSS can be loaded in the head of the page as normal, rather than waiting for the JS to load. – Joe Clay Oct 08 '16 at 22:58
  • Documentation on this plugin is inconsistent – Code Whisperer Mar 24 '17 at 14:44
  • @CodeWhisperer: How so? – Joe Clay Mar 24 '17 at 14:44
  • 2
    The page for Extract Text Plugin now says to use "mini-css-extract-plugin" instead, if you're on webpack 4 or later. I did, and it works well for me. Less complex than Extract Text Plugin, but does what it needs to. – Martin Omander Jun 15 '18 at 11:31
  • Yes, this is the same solution as recommended within Github issues: https://github.com/webpack-contrib/style-loader/issues/107 – Simon East Feb 19 '19 at 01:15
12

A bit late to the party, but here's how I do it.

While I recognize the merits of extract-text-plugin, it's plagued by a rebuild bug that messes up css order, and is a pain to set up. And setting timeouts in js is not something anyone should be doing (it's ugly and is not guaranteed 100% to prevent fouc)...

So my index.html is:

<!DOCTYPE html>
<html>
  <head>
    <meta charset="UTF-8"/>
    <style>
      #app { display: none }
    </style>
    <title></title>
  </head>
  <body>
    <div id="app"></div>
    <script src="scripts/bundle.js"></script>
  </body>
</html>

Then, in client.js at the very end I add:

include "./unhide.css";

...and unhide.css contains a single line:

#app { display: block }

Voila, you see nothing until the whole app is loaded.

user3361481
  • 121
  • 1
  • 3
  • For my dark background app, I combined this with ``. – jchook Apr 14 '18 at 21:38
  • I have tried this (still experimenting), and the FOUC still happens. At least I think it is FOUC, it's more like a janky wipe towards the middle of the screen. Regardless, we use angular (switching to react soon), and I have a suspicion that has more to do with it. – trysis May 03 '18 at 13:47
  • Ok, so now the issue is the user would be staring at a blank space for some moment(s)... in my case 3 seconds, +/- 1 second. Well, of course now it's an opportunity for a loading animation. Ok, but for the loading animation to not look janky, it must have a 3second minimum (at least one cycle of the animation loop). So we went from FOUC to a 3 second loading animation. Well these are subject choices taken case-by-case. I prefer showing some structure early on, and no jumping interface as things load. – Kalnode Sep 21 '19 at 18:03
3

It's janky, but I wrap ReactDom.render() in a setTimeout() in my root index.js file.

setTimeout(ReactDOM.render(...), 0)

bergie3000
  • 1,091
  • 1
  • 13
  • 21
wkoutre
  • 194
  • 3
  • 5