I was racing through a small React prototype, eager to drop plain-old .css
files and start using CSS Modules. A quick visit to webpack docs, a snappy the loader string, and <br> BAM my terminal lit up red.
If you’ve landed here because you saw the same “Invalid options object” wall of text, stick with me. I’ll walk you through the exact misstep, the fix, and four bite-sized improvements you can bolt on once everything compiles.
The Original webpack.config.js
path = require('path');
const HtmlWebpackPlugin = require('html-webpack-plugin');
module.exports = {
entry: './src/index.js',
output: {
path: path.join(__dirname, '/dist'),
filename: 'index_bundle.js',
publicPath: '/'
},
module: {
rules: [
{
test: /\.css$/,
loader:
'style-loader!css-loader?modules&importLoaders=1&localIdentName=[name]__[local]___[hash:base64:5]'
},
{
test: /\.js$/,
exclude: /node_modules/,
use: { loader: 'babel-loader' }
}
]
},
devServer: { historyApiFallback: true },
plugins: [new HtmlWebpackPlugin({ template: './src/index.html' })]
};
The Scary Red Error
in ./src/index.css (...css-loader/dist/cjs.js?...!./src/index.css)
ValidationError: Invalid options object. CSS Loader has been initialised using an options object that does not match the API schema.
- options has an unknown property 'localIdentName'...
- options.importLoaders should be a boolean | number
What Those Cryptic Lines
What the validator said | What it’s telling me in plain English |
---|---|
unknown property localIdentName | That option must now live inside a modules object. |
importLoaders should be boolean | number | The query-string turns 1 into the string "1" , which the schema rejects. |
overall “Invalid options object” | My loader string is using webpack 4-era syntax, but I’m on css-loader v5 (or newer). |
Why It Happens
- css-loader v4+ switched from query strings to real objects.
Legacy shortcuts like?modules
still work, but most fine-grained flags were yanked. localIdentName
moved undermodules
.modules
can betrue
or an object that houses its own settings.- Strings vs numbers.
Query-string parameters are parsed as strings breaking theimportLoaders
type check.
The Clean Fix I Dropped In
{
test: /\.css$/,
use: [
'style-loader',
{
loader: 'css-loader',
options: {
importLoaders: 1, // 👉 number, not string
modules: {
localIdentName: '[name]__[local]___[hash:base64:5]'
}
}
}
]
}
I saved, restarted webpack-dev-server
, and the build went from red to 100 % green.
Upgrade I Tried After It Work
These are mini-mods you can paste in and test in five minutes each.
Sass + CSS Modules
{
test: /\.scss$/,
use: [
'style-loader',
{
loader: 'css-loader',
options: {
importLoaders: 2,
modules: { localIdentName: '[local]--[hash:6]' }
}
},
'sass-loader'
]
}
I created Button.scss
, imported it inside React, and confirmed the classes were still scoped and hashed.
Autoprefixer with PostCSS
{
test: /\.css$/,
use: [
'style-loader',
{
loader: 'css-loader',
options: { importLoaders: 1, modules: true }
},
{
loader: 'postcss-loader',
options: {
postcssOptions: { plugins: ['postcss-preset-env'] }
}
}
]
}
After adding display: grid
to a rule, Chrome DevTools showed automatic vendor prefixes nice!
Extract CSS for Production
MiniCssExtractPlugin = require('mini-css-extract-plugin');
{
test: /\.css$/,
use: [
process.env.NODE_ENV === 'production'
? MiniCssExtractPlugin.loader
: 'style-loader',
'css-loader'
]
}
plugins.push(
new MiniCssExtractPlugin({ filename: 'styles.[contenthash].css' })
);
Running NODE_ENV=production webpack
spit out a separate, cache-friendly CSS file.
Instant Style HMR
: {
hot: true,
historyApiFallback: true
}
With HMR on, I tweaked a colour in index.css
; the browser swapped styles in place—no full reload.
Exlain it Checklist
✅ | What I looked for | Command |
---|---|---|
Build passes—no ValidationError | npm run start | |
DOM shows hashed class names | Inspect element | |
PostCSS adds vendor prefixes | Check built CSS | |
Production build extracts CSS | npm run build |
Final Thoughts
The root cause of my meltdown was simple: “old query string syntax meets new schema validation.” The moment I swapped in the modern use: []
array with a neat options
object, webpack stopped shouting.
What I love about this little debugging detour is how many extras I picked up en route Sass, PostCSS, extraction, HMR. Each one slid neatly into the same rule pattern, reinforcing the muscle memory.