如何配置HtmlWebpackPlugin与CompressionPlugin返回.gz后缀资源?
Hey Harry, let's fix this gzipped asset serving issue step by step. The key here is that we don't need to change the filenames in Webpack (so HtmlWebpackPlugin can keep referencing .js files) — instead, we'll configure Express to serve the .gz version automatically when the browser supports gzip compression.
1. Fix Your Express Route Order & Add Proper Gzip Checks
Your first Express attempt was on the right track, but the route order was wrong. The express.static middleware was handling the .js requests before your custom route could intercept them. Also, we need to verify two critical things before serving the gzipped file:
- The browser actually supports gzip (via the
Accept-Encodingheader) - The corresponding .gz file exists on the filesystem to avoid 404 errors
Here's the corrected Express setup:
const express = require('express'); const path = require('path'); const fs = require('fs'); const app = express(); // Handle .js requests first: check for gzip support and existing .gz file app.get('*.js', (req, res, next) => { // Verify browser accepts gzip compression if (req.headers['accept-encoding']?.includes('gzip')) { const gzipFilePath = `${req.url}.gz`; const fullPath = path.resolve(__dirname, '../dist', gzipFilePath.slice(1)); // Strip leading slash // Check if the .gz file exists on disk if (fs.existsSync(fullPath)) { req.url = gzipFilePath; res.set('Content-Encoding', 'gzip'); res.set('Content-Type', 'application/javascript'); } } next(); // Pass to express.static to serve either .js or .js.gz }); // Serve static files AFTER the gzip route logic app.use(express.static(path.resolve(__dirname, '../dist'))); app.get('/healthz', (req, res) => res.send('OK')); app.get('*', (req, res) => res.sendFile(path.resolve(__dirname, '../dist/index.html'))); const PORT = process.env.SERVER_PORT || 3000; const HOST = process.env.SERVER_HOST || '127.0.0.1'; app.listen(PORT); console.log(`API started on ${HOST}:${PORT}`);
2. Simplify with a Dedicated Middleware (Recommended)
Instead of writing custom logic, use the express-static-gzip middleware — it handles all gzip checks, file existence verification, and header setting automatically, saving you from boilerplate code.
First install the package:
npm install express-static-gzip --save
Then update your Express code to replace express.static with this middleware:
const express = require('express'); const path = require('path'); const expressStaticGzip = require('express-static-gzip'); const app = express(); // Replace express.static with express-static-gzip for auto gzip serving app.use(expressStaticGzip(path.resolve(__dirname, '../dist'), { enableBrotli: false, // Disable if you don't use Brotli compression orderPreference: ['gzip'], setHeaders: (res, filePath) => { // Optional: Add long-term cache headers for static assets if (filePath.endsWith('.js') || filePath.endsWith('.css')) { res.setHeader('Cache-Control', 'public, max-age=31536000'); } } })); app.get('/healthz', (req, res) => res.send('OK')); app.get('*', (req, res) => res.sendFile(path.resolve(__dirname, '../dist/index.html'))); const PORT = process.env.SERVER_PORT || 3000; const HOST = process.env.SERVER_HOST || '127.0.0.1'; app.listen(PORT); console.log(`API started on ${HOST}:${PORT}`);
3. Keep Your Webpack Configuration As-Is
You don't need to modify your Webpack output filenames or HtmlWebpackPlugin setup. Your current CompressionPlugin is already correctly generating .gz versions of your assets alongside the original .js files. The server handles the rest: when a browser requests app.abc123.min.js, Express will serve app.abc123.min.js.gz if the browser supports gzip, without changing the URL referenced in index.html.
Why your second Express attempt failed:
You set the Content-Encoding: gzip header for all static files, but you were still serving the original uncompressed .js files. This caused browsers to try decompressing non-gzipped data, leading to parsing errors.
内容的提问来源于stack exchange,提问作者Harry Blue

