How to best optimize initial page load for react web app with images especially for mobile?

1.1k Views Asked by At

I have a fairly complex web app written in React/Redux and webpack for compilation. Its landing page consists of 2 images and the main app module. All the other modules are lazy-loaded depending on what the user wants to do. When I audit using google devtools, I get a performance score of 74, which isn't bad.

But the initial page loads in over 15 seconds on the iphones! And I need to optimize it.

Images One of the images is the background of html body, so that it shows when other pages are loading. The other one is the background of the Home page component. The home page image is non-negotiable, it must be there. The one in the body I'm more flexible about, but it looks cool.

Right now the Home page image is imported into the react component using the webpack url-loader and is therefore in the app bundle. Is that the best way? The other image is loaded in the index.html on the body element directly. I'm not sure which is the fastest way.

I'm not an image expert either, so maybe is there something else I can do to compress or optimize the images? Is there a "best size" for use cross-platform? Also what tools to use to change? I have GIMP on my system, but can use something else.

Splash It would be nice if the user sees "something" when it's loading right away, so they can be more patient. Right now they only see a blank white screen. I have following all the favicon generator and have them all setup according to directions. But the splash is not showing. Is there something I can do there? I have even tried to alter right in the HTML a different color background, but that doesn't show up either.

CSS To organize my project code, I built everything very componentized. My stylesheets mostly sit alongside each component and are imported into where it's used. These also get bundled by webpack using miniCssExtractLoader and css-loader. I attach my webpack configs -- is there something I can do there?

Webpack What else can I do to get the initial load time down? Here are my webpack.common and webpack.prod setups. Any ideas will be appreciated!

webpack.common.js

const path = require('path');
var webpack = require('webpack');
const CleanWebpackPlugin = require('clean-webpack-plugin');
const HtmlWebpackPlugin = require('html-webpack-plugin');
const MiniCssExtractPlugin = require("mini-css-extract-plugin");
const sourcePath = path.join(__dirname, './src');
const autoprefixer = require('autoprefixer');
const CopyWebpackPlugin = require('copy-webpack-plugin');


module.exports = {
  entry: {
      app: './src/index.js'
  },
  output: {
      filename: '[name].[chunkhash:4].js',
      chunkFilename: '[name].[chunkhash:4].js', //name of non-entry chunk files
      path: path.resolve(__dirname, 'dist'),  //where to put the bundles
      publicPath: "/" // This is used to generate URLs to e.g. images
    },
  module: {
      rules: [
        {
          test: /\.(js|jsx)$/,
          exclude: /node_modules/,
          use: {
            loader: "babel-loader"
          }
        },
        {
          test: /\.html$/,
          use: [
            {
              loader: "html-loader",
              options: { minimize: true }
            }
          ]
        },

        {
          test: /\.(scss|sass|css)$/,
          use: [
              MiniCssExtractPlugin.loader,
              { loader: 'css-loader' },
              { loader: 'postcss-loader',
                  options: {
                    plugins: () => [autoprefixer({ grid: false})]
                  }
              },
              {
                loader: 'fast-sass-loader',
                options: {
                  includePaths: [  path.resolve(__dirname, 'src'), path.resolve(__dirname, 'src','styles') ,'./node_modules', '/node_modules/materialize-css/sass/components'],
                  sourceMap: true
                }
              }
          ]

        },
        {
          test: /\.(jpg|png)$/,
          loader: 'url-loader',
          options: {
            limit: 8192 // inline base64 URLs for <=8k images, direct URLs for the rest
          },
        },
        {    
          test: /\.svg/,
          use: {
            loader: 'svg-url-loader',
            options: {}
          }
        }

      ]
    },

    resolve: {
      alias: {
        components:  path.resolve(__dirname, 'src', 'components'),
        navigation:  path.resolve(__dirname, 'src', 'navigation'),
        reducers:    path.resolve(__dirname, 'src', 'reducers'),
        actions:     path.resolve(__dirname, 'src', 'actions'),
        routes:      path.resolve(__dirname, 'src', 'routes'),
        utils:       path.resolve(__dirname, 'src', 'utils'),
        styles:      path.resolve(__dirname, 'src', 'styles'),
        images:      path.resolve(__dirname, 'public', 'images'),
        public:      path.resolve(__dirname, 'public'),
        test:        path.resolve(__dirname, 'src', 'test'),
        materialize: path.resolve(__dirname, 'node_modules', 'materialize-css', 'sass', 'components')
      },
      // extensions: ['.webpack-loader.js', '.web-loader.js', '.loader.js', '.js', '.jsx'],
      modules: [
        path.resolve(__dirname, 'node_modules'),
        sourcePath
      ]
    },
    optimization: {
          splitChunks: {
              cacheGroups: {
                  js: {
                      test: /\.js$/,
                      name: "commons",
                      chunks: "all",
                      minChunks: 7,
                  },
                  styles: {
                    test: /\.(scss|sass|css)$/,
                    name: "styles",
                    chunks: "all",
                    enforce: true
                  }
              }
          }
    },
  plugins: [
    new CleanWebpackPlugin(['dist']),
    new CopyWebpackPlugin([ { from: __dirname + '/public', to: __dirname + '/dist/public' } ]),
    new MiniCssExtractPlugin({filename: "[name].css"}),
    new webpack.NamedModulesPlugin(),
    new HtmlWebpackPlugin({
       "template": "./src/template.html",
       "filename": "index.html",
       "hash": false,
       "inject": true,
       "compile": true,
       "favicon": false,
       "minify": true,
       "cache": true,
       "showErrors": true,
       "chunks": "all",
       "excludeChunks": [],
       "title": "ShareWalks",
       "xhtml": true,
       "chunksSortMode": 'none' //fixes bug
       })
   ]
};

webpack.prod.js

 const merge = require('webpack-merge');
 const common = require('./webpack.common.js');
const WorkboxPlugin = require('workbox-webpack-plugin');


 module.exports = merge(common, {
   mode: 'production',
   devtool: 'source-map',
   plugins: [
          new WorkboxPlugin.GenerateSW({ 
          // these options encourage the ServiceWorkers to get in there fast     
           // and not allow any straggling "old" SWs to hang around     
           clientsClaim: true,     
           skipWaiting: true
      }),
   ]
});
1

There are 1 best solutions below

0
On

Your question is too broad for SO and will be closed :) Lets concentrate on "how to make bundle smaller" optimization path.

1.try babel loose compilation (less code)

module.exports = {
  "presets": [
    ["@babel/preset-env", {
      // ...
      "loose": true
    }]
  ],
}

2.also review your polyfills, use minification, learn webpack null-loader techique.

3.there is a hope that more aggresive chunking could give some positive effect (if not all is used on each your app page, then it can be lazy loaded).

optimization: {
    runtimeChunk: 'single',
    splitChunks: {
      chunks: 'all',
      maxInitialRequests: infinity,
      cacheGroups: {
        vendor: {
          test: /[\\/]node_modules[\\/]/,
          vendorname(v) {
            var name = v.context.match(/[\\/]node_modules[\\/](.*?)([\\/]|$)/)[1];
            return `npm.${name.replace('@', '_')}`;
          },
        },
      },