You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

Node 18+React+Express渲染Gzip静态资源时内容解码失败200

问题:升级Node 18与Webpack 5后静态资源加载异常(net::Err_content_decoding_failed)

将React+Express应用升级至Node 18和Webpack 5后,执行yarn dev时控制台出现错误failed to load resource: net::Err_content_decoding_failed,应用CSS无法正常加载。

移除代码中的etag header,将原配置:

app.use(express.static('public', {...config.cacheConfig, setHeaders}));

改为:

app.set('Content-Encoding', 'gzip')
app.use(express.static('public' ))

后错误消失,但CSS/JS仍无法正常工作。

附当前Webpack配置代码:

const { merge } = require('webpack-merge');
const webpack = require('webpack');
const { DefinePlugin, IgnorePlugin } = require('webpack');
const CopyWebpackPlugin = require('copy-webpack-plugin');
const LoadablePlugin = require('@loadable/webpack-plugin');
const CompressionPlugin = require('compression-webpack-plugin');

function getEnvironmentConfig(envConfig, env) {
  return  {};
}

const copyPlugin = new CopyWebpackPlugin(
  { patterns: [{ from: './src/public/favicon.ico' }] },
  {
    copyUnmodified: true,
  },
);
const reactLoadablePlugin = new LoadablePlugin({
  filename: '../loadable.json',
});

const DashboardPlugin = require('webpack-dashboard/plugin');

const { BundleAnalyzerPlugin } = require('webpack-bundle-analyzer');
const commonConfig = require('./webpack.base');

/**
 * Gets the Webpack configuration for the React bundle.
 *
 * @param {{ ENV: string }} env
 *   The Webpack environment.
 */
const compressionPlugin = new CompressionPlugin({
  test: /\.(js|map|css|json|html)$/i,
  algorithm: 'gzip',
  // [path] is a path relative to output.path
  exclude: '../loadable-stats.json',
  minRatio: Number.MAX_SAFE_INTEGER,
});

function reactConfig(env) {
  return merge(
    commonConfig(env),
    {
      target: 'web',
      devtool: 'source-map',
      entry: {
        main: path.resolve(__dirname, 'src/client.js'),
        landingPage: path.resolve(
          __dirname,
          'src/mycomponent.js',
        ),
        Wrapper: path.resolve(__dirname, 'src/wrapper.js'),
        ),
      },
      output: {
        filename: '[name].js',
        path: path.resolve(__dirname, 'public'),
        publicPath: '',
      },
      module: {
        rules: [
          {
            test: /\.css$/,
            use: [
              {
                loader: 'style-loader',
              },
              {
                loader: 'css-loader',
                options: {
                  url: false,
                },
              },
            ],
          },
        ],
      },
      plugins: [
        new webpack.ProvidePlugin({
          process: 'process/browser',
        }),
        reactLoadablePlugin,
        new DefinePlugin({
          'process.env.WEBPACK_TARGET': JSON.stringify('web'),
          'process.env.FLAVOR': JSON.stringify(env.BUILD_ENV),
        }),
        new IgnorePlugin({
          resourceRegExp: /^\.\/cpme\/content/,
          contextRegExp: /@some-library\/component\/dist$/,
        }),
        compressionPlugin,
      ],
      optimization: {
        splitChunks: {
          chunks: 'initial',
          maxInitialRequests: 1,
        },
      },
    },
    getEnvironmentConfig(
      {
        prod: {
          plugins: [copyPlugin],
        },
        dev: {
          plugins: [
            copyPlugin,
            new DashboardPlugin(),
            new BundleAnalyzerPlugin(),
          ],
        },
      },
      env,
    ),
  );
}

module.exports = (env = { BUILD_ENV: 'dev' }) => {
  if (!env.BUILD_ENV || env.BUILD_ENV === '$FLAVOR') {
    env.BUILD_ENV = 'dev';
  }
  const config = [reactConfig(env)];
  if (env.WEBPACK_LOG_LEVEL === 'VERBOSE') {
    console.log(
      'Final config',
      JSON.stringify(
        config,
        (key, value) =>
          value instanceof RegExp ? `[RegExp(${value.toString()})]` : value,
        2,
      ),
    );
  }
  return config;
};

解决方案

1. 开发环境禁用CompressionPlugin

CompressionPlugin主要用于生产环境预生成压缩文件,开发环境启用会导致Express静态资源服务无法正确匹配原始文件与.gz文件,引发解码错误。修改Webpack配置,仅在生产环境加载该插件:

// 修改reactConfig中的plugins数组
plugins: [
  new webpack.ProvidePlugin({
    process: 'process/browser',
  }),
  reactLoadablePlugin,
  new DefinePlugin({
    'process.env.WEBPACK_TARGET': JSON.stringify('web'),
    'process.env.FLAVOR': JSON.stringify(env.BUILD_ENV),
  }),
  new IgnorePlugin({
    resourceRegExp: /^\.\/cpme\/content/,
    contextRegExp: /@some-library\/component\/dist$/,
  }),
  // 仅在生产环境添加compressionPlugin
  ...(env.BUILD_ENV === 'prod' ? [compressionPlugin] : []),
],

2. 用express-static-gzip修复静态资源响应头

如果需要保留压缩功能,使用express-static-gzip替代原生express.static,它会自动识别.gz文件并设置正确的响应头:

首先安装依赖:

yarn add express-static-gzip

然后修改Express配置:

const expressStaticGzip = require('express-static-gzip');

// 替换原express.static调用
app.use(expressStaticGzip('public', {
  ...config.cacheConfig,
  enableBrotli: false, // 不需要brotli压缩可关闭
  customCompressions: [{
    encodingName: 'gzip',
    fileExtension: '.gz'
  }]
}));

3. 修复原setHeaders函数(保留ETag场景)

如果原setHeaders函数导致ETag与压缩文件冲突,调整逻辑确保为.gz文件添加对应响应头:

const setHeaders = (res, path) => {
  // 保留原缓存配置的ETag逻辑
  if (config.cacheConfig.setHeaders) {
    config.cacheConfig.setHeaders(res, path);
  }
  // 为.gz文件设置正确的编码与类型
  if (path.endsWith('.gz')) {
    res.setHeader('Content-Encoding', 'gzip');
    if (path.endsWith('.css.gz')) {
      res.setHeader('Content-Type', 'text/css');
    } else if (path.endsWith('.js.gz')) {
      res.setHeader('Content-Type', 'application/javascript');
    }
  }
};

app.use(express.static('public', {...config.cacheConfig, setHeaders}));

4. 校验Webpack路径配置

确保Webpack的output.publicPath与Express静态资源访问路径一致,当前publicPath: ''为相对路径,需确认Express服务的静态资源根目录正确指向public文件夹。


内容的提问来源于stack exchange,提问作者uidev76

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.07.13 05:47:03