danwoods
danwoods

Reputation: 4907

Webpack 5, Server-Side rendering, and FOUC

I'm upgrading an existing web site from Webpack 3 to Webpack 5.

The site uses server side rendering for the first load, the client side routing for any in-site navigation. All this worked fine with Webpack 3, but after migrating to Webpack 5 it looks like some of the styles are being applied via javascript and that's creating a FOUC during the first load (after that, in-site navigation works fine and looks correct). As a test, I turned off javascript in my browser; the old site loads fine and looks correct, but the upgraded site does not. It feels like I need style-loader in the server config somewhere, but when that's added, I get a "Cannot GET /" when trying to load the site. Any help is appreciated.

Server-side config

require('dotenv').config({ silent: true });
const path = require('path');
const webpack = require('webpack');
const MiniCssExtractPlugin = require('mini-css-extract-plugin');

const includePaths = [path.resolve(__dirname, 'stylesheets')];

module.exports = {
  bail: true,
  entry: {
    main: './src/entry-server',
  },
  output: {
    path: path.join(__dirname, 'build', 'prerender'),
    filename: '[name].js',
    publicPath: '/bundle/',
    libraryTarget: 'commonjs2',
  },
  plugins: [
    new webpack.DefinePlugin({
      'process.env': {
        NODE_ENV: JSON.stringify('production'),
        PRERENDER: true,
        ASSETS_CDN_PREFIX: JSON.stringify(process.env.ASSETS_CDN_PREFIX || ''),
      },
    }),
    // only load moment english locale: https://github.com/moment/moment/issues/2517
    new webpack.ContextReplacementPlugin(/moment[\\/]locale$/, /^\.\/(en)$/),
    new webpack.optimize.ModuleConcatenationPlugin(),
    new MiniCssExtractPlugin({
      ignoreOrder: true,
    }),
  ],
  module: {
    rules: [
      {
        test: /\.jsx?$/,
        include: path.join(__dirname, 'src'),
        use: 'babel-loader',
      },
      {
        test: /\.s?css$/,
        use: [
          MiniCssExtractPlugin.loader,
          {
            loader: 'css-loader',
            options: {
              modules: true,
            },
          },
          'postcss-loader',
          {
            loader: 'sass-loader',
            options: {
              sassOptions: {
                includePaths,
                data: `$assetprefix: "${process.env.ASSETS_CDN_PREFIX || ''}";`,
              },
            },
          },
        ],
      },
      {
        test: /\.svg$/,
        use: `svg-inline-loader?removeTags&removingTags[]=${['defs', 'style', 'title'].join(',removingTags[]=')}`,
      },
    ],
  },
  resolve: {
    extensions: ['.js', '.jsx', '.css', '.scss', '.json'],
  },
  target: 'node',
};

Server entry point

export default function (req, res, environmentConstants, callback) {
  // ...setup

  match({ routes, location: targetUrl }, (error, redirectLocation, renderProps) => {
    // ...setup
    fetchSomeData().then(() => renderToString(
        <Provider store={store}>
          <RouterContext {...renderProps} />
        </Provider>,
      ))
      .then((content) => {
        callback(null, {
          helmet: Helmet.renderStatic(),
          content,
          initialState: serialize(store.getState(), { isJSON: true }),
          env: serialize(someEnvConstants),
        });
      })

Client-side config

require('dotenv').config({ silent: true });
const AssetsPlugin = require('assets-webpack-plugin');
const CleanPlugin = require('clean-webpack-plugin');
const CompressionPlugin = require('compression-webpack-plugin');
const MiniCssExtractPlugin = require('mini-css-extract-plugin');
const path = require('path');
const webpack = require('webpack');
// const BundleAnalyzerPlugin = require('webpack-bundle-analyzer').BundleAnalyzerPlugin;

const includePaths = [path.resolve(__dirname, 'stylesheets')];

// Match all routes that we want to lazy load
const lazyRouteRegex = /route\/([^/]+\/?[^/]+)Route.jsx$/;

module.exports = {
  bail: true,
  entry: {
    main: './src/entry-client',
    vendor: [
      'react',
      'react-dom',
      'react-router',
      'redux',
      'react-redux',
      'xmldom',
    ],
  },
  output: {
    path: path.join(__dirname, 'build', 'public', '[fullhash]'),
    filename: '[name].js',
    chunkFilename: '[id].chunk.js',
    publicPath: `${process.env.ASSETS_CDN_PREFIX || ''}/build/public/[fullhash]/`,
  },
  plugins: [
    // only load moment english locale: https://github.com/moment/moment/issues/2517
    new webpack.ContextReplacementPlugin(/moment[\\/]locale$/, /^\.\/(en)$/),
    new MiniCssExtractPlugin({
      ignoreOrder: true,
    }),
    new webpack.DefinePlugin({
      'process.env': {
        NODE_ENV: JSON.stringify('production'),
        PRERENDER: false,
        ASSETS_CDN_PREFIX: JSON.stringify(process.env.ASSETS_CDN_PREFIX || ''),
      },
    }),
    new AssetsPlugin(),
    new CleanPlugin([path.join(__dirname, 'build', 'public')]),
    new CompressionPlugin(),
    // new BundleAnalyzerPlugin(),
  ],
  module: {
    rules: [
      {
        test: /\.jsx?$/,
        include: path.join(__dirname, 'src'),
        exclude: lazyRouteRegex,
        use: [
          {
            loader: 'babel-loader',
          },
        ],
      },
      {
        test: lazyRouteRegex,
        include: path.resolve(__dirname, 'src'),
        use: [
          {
            loader: 'bundle-loader',
            options: {
              lazy: true,
            },
          },
          {
            loader: 'babel-loader',
          },
        ],
      },
      {
        test: /swiper.*\.scss$/,
        use: [
          MiniCssExtractPlugin.loader,
          {
            loader: 'css-loader',
            options: {
              modules: false,
              importLoaders: 1,
            },
          },
          {
            loader: 'postcss-loader',
          },
          {
            loader: 'sass-loader',
            options: {
              sassOptions: {
                includePaths,
                data: `$assetprefix: "${process.env.ASSETS_CDN_PREFIX || ''}";`,
              },
            },
          },
        ],
      },
      {
        test: /\.s?css$/,
        exclude: /swiper.*\.scss$/,
        use: [
          MiniCssExtractPlugin.loader,
          {
            loader: 'css-loader',
            options: {
              modules: true,
            },
          },
          {
            loader: 'postcss-loader',
          },
          {
            loader: 'sass-loader',
            options: {
              sassOptions: {
                includePaths,
                data: `$assetprefix: "${process.env.ASSETS_CDN_PREFIX || ''}";`,
              },
            },
          },
        ],
      },
      {
        test: /\.svg$/,
        use: `svg-inline-loader?removeTags&removingTags[]=${['defs', 'style', 'title'].join(',removingTags[]=')}`,
      },
    ],
  },
  resolve: {
    extensions: ['.js', '.jsx', '.css', '.scss', '.json'],
  },
  target: 'web',
};

Upvotes: 1

Views: 439

Answers (0)

Related Questions