Enable text compression using React, Webpack and Apache
Asked Answered
S

3

18

Supposedly the compression-webpack-plugin is supposed to do it.

I installed the plugin with npm

npm install compression-webpack-plugin --save-dev

And edited my webpack.config.js file to include

const CompressionPlugin = require('compression-webpack-plugin');

...
  plugins: [
    new CompressionPlugin({
      filename: "[path].gz[query]",
      algorithm: "gzip",
      test: /\.(js|css)$/i,
    }),
...

When I used page insights to check how fast my webpage loads, it looks like my gz files aren't recognized, or at least one of them isn't recognized

enter image description here

This is my main directory

enter image description here


This question is pretty similar to my question. I am trying to avoid using .htaccess though because I heard somewhere that it's not the best to use with react and webpack. Perhaps this is wrong?

I tried using kushalvm's solution, but it is not working for me.

Seasick answered 15/8, 2019 at 3:54 Comment(0)
P
22

Short answer: the kushalvm's solution is not complete. In order to compress the page size with gzip/brotli, there are two steps:

  1. Create .gz/.br files in build time (or dynamically generate them by a server)
  2. Serve them (instead of .js files)

And you are doing the first part but not the second one. Because when you build a react project (in case of client-side rendering - CSR) you simply create one .html file importing some script tags (your react project). If you use brotli or compression plugin for webpack you have created some .gz/.br files, but still, the HTML file imports the old .js scripts.

So what are the different methods you can use to serve compressed files you need to configure your server (you cannot do this just by changing your React configs)

Solutions

1)If you are using client-side rendering, you can create a custom express server used to serve files, it is very simple and takes less than 10 lines of code, you can read the doc about this in the Official React Docs (with express-static-gzip) So your server file will look like this:

const express = require('express');
const path = require('path');
const app = express();

app.use(
  expressStaticGzip(path.join(__dirname, 'build'), {
  enableBrotli: true, // only if you have brotli files too
  }),
);

app.use(express.static(path.join(__dirname, 'build')));

app.get('/', function(req, res) {
  res.sendFile(path.join(__dirname, 'build', 'index.html'));
});

app.listen(9000);

This server, will read the files in the build directory, and if a request for some js assets arrives, it checks (if the browser supports the compression, and if compression file exists for it) then serves compressed files

2) If you use SSR (for example Next.js or React-Starter-kit) you can create a custom express server, and use the same approach above.

3) If you use Apache webserver, you can use Apache gzip/brotli compressions config file like this:

# enable the rewrite capabilities
RewriteEngine On

# prevents the rule from being overrided by .htaccess files in subdirectories
RewriteOptions InheritDownBefore

# provide a URL-path base (not a file-path base) for any relative paths in the rule's target
RewriteBase /

# GZIP
## allows you to have certain browsers uncompress information on the fly
AddEncoding gzip .gz
## serve gzip .css files if they exist and the client accepts gzip
RewriteCond %{HTTP:Accept-encoding} gzip
RewriteCond %{REQUEST_FILENAME}\.gz -s
RewriteRule ^(.*)\.css $1\.css\.gz [QSA]
## serve gzip .js files if they exist and the client accepts gzip
RewriteCond %{HTTP:Accept-encoding} gzip
RewriteCond %{REQUEST_FILENAME}\.gz -s
RewriteRule ^(.*)\.js $1\.js\.gz [QSA]
## serve gzip .html files if they exist and the client accepts gzip
RewriteCond %{HTTP:Accept-encoding} gzip
RewriteCond %{REQUEST_FILENAME}\.gz -s
RewriteRule ^(.*)\.html $1\.html\.gz [QSA]
## serve correct content types, and prevent mod_deflate double gzip
RewriteRule \.css\.gz$ - [T=text/css,E=no-gzip:1,E=is_gzip:1]
RewriteRule \.js\.gz$ - [T=text/javascript,E=no-gzip:1,E=is_gzip:1]
RewriteRule \.html\.gz$ - [T=text/html,E=no-gzip:1,E=is_gzip:1]
Header set Content-Encoding "gzip" env=is_gzip

4) If you use a third party JamStack/CDN provider like Netlify or AWS, they have some configs for you to enable dynamic gzip.

Porringer answered 29/8, 2019 at 10:36 Comment(5)
I believe I'm using apache webserver, I'm using apache. I edited my .htaccess to be the first answer in the link I provided. Is this what you meant?Seasick
Yeah, that should do the job. It does several things: 1)check if browser supports gz, 2) if we have gz file for .js/.css/.html 3) serve 4)disable deflatePorringer
Ok, the problem hasn't changed thoughSeasick
Thanks for editing your answer. I'm still getting the same issues however. Maybe I'm leaving out some important information? I know that I have httpd, I think that's the same as apache webserver. I was also under the impression that I had client side rendering though. Can you have both? I'm running amazon linux 2 also, in case that's relevant. Sorry my knowledge of apache and server stuff is really limited, I understand enough to get a webpage running from a list of instructions but I would say I don't really know what I'm doing with server; point being, assume I'm not doing the obvious.Seasick
Ok, I looked into it and I don't think I'm using client side rendering, maybe I'm wrong though? I plan on switching to it though. I still want to figure out the apache way though. I'm not sure why you're solution doesn't work.Seasick
H
2

You can also Build Time gzip. here is the process.

  1. Install the Webpack compression plugin

    npm install compression-webpack-plugin --save-dev

  2. Install and Import the plugin var CompressionPlugin = require('compression-webpack-plugin');

  3. Add it to plugins array

  plugins: [
    new webpack.DefinePlugin({ 
      'process.env': {
        'NODE_ENV': JSON.stringify('production')
      }
    }),
    new webpack.optimize.DedupePlugin(),
    new webpack.optimize.UglifyJsPlugin(),
    new webpack.optimize.AggressiveMergingPlugin(),
    new CompressionPlugin({   <-- Add this
      asset: "[path].gz[query]",
      algorithm: "gzip",
      test: /\.js$|\.css$|\.html$/,
      threshold: 10240,
      minRatio: 0.8
    })
  ]
  1. Finally, add this middleware to Express to return .js.gz so you can still load bundle.js from the html but will receive bundle.js.gz
app.get('*.js', function (req, res, next) {
  req.url = req.url + '.gz';
  res.set('Content-Encoding', 'gzip');
  next();
});
Hurless answered 15/7, 2020 at 17:28 Comment(0)
G
0

I was also struggling with serving the compressed version with my React SSR app; in my case the brotli type with js.br extension > "bundle.js.br". The following snippet helped me in the end to serve it properly. I reduced my client-side uncompressed bundle in development mode from 1.7 MB to 0.27 MB. Try and figure it out for you. There is no extra plugin needed to serve the file. Of course, the compressed version must be generated first with webpack. For webpack brotli compression I use the npmjs packages compression-webpack-plugin in conjunction with zlib.

var express = require('express');
var app = express();

app.get('*.js', (req, res, next) => {
  if (req.header('Accept-Encoding').includes('br')) {
    req.url = req.url + '.br';
    console.log(req.header('Accept-Encoding'));
    res.set('Content-Encoding', 'br');
    res.set('Content-Type', 'application/javascript; charset=UTF-8');
  }
  next();
});

app.use(express.static('public'));

Now let me explain my snippet above which I use for my SSR React app to serve the compressed file.

  • Using the argument '*.js' at the beginning means that this app.get works for every endpoint that is fired to fetch a JS file.
  • Inside the callback, I attach .br to the URL of the request. Furthermore, the Content-Encoding response header is set to br.
  • The Content-Type header is set to application/javascript; charset=UTF-8 to specify the MIME type.
  • At the end and very important, next() makes possible to continue to any callback that may be next.
  • Do not forget to provide the folder where your compressed bundle is placed. I used app.use(express.static('public')) in my case.
Guardafui answered 5/9, 2021 at 17:51 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.