Setting Up PostCSS with Autoprefixer and cssnano: Plugin Order, Config, and Verification
A practical PostCSS setup with autoprefixer and cssnano: correct plugin order, browserslist targets, CLI commands, and concrete checks that the output is actually prefixed and minified.
18 Feb 2026, 20:42 UTC

You want a CSS build step that adds vendor prefixes for your target browsers and shrinks the output for production, without hand-maintaining prefixes or shipping dead bytes. PostCSS does both through two mature plugins — autoprefixer and cssnano — but the result depends on plugin order and on how you declare your browser targets. This guide walks through a minimal, correct setup and how to verify it actually did what you asked.
What you need first
- Node.js 18+ and npm (or pnpm/yarn) installed.
- A project with plain CSS files you want to process (this setup also works as part of a larger bundler pipeline, but here we use the PostCSS CLI so the behavior is visible and testable).
- Basic familiarity with running npm scripts.
Version assumption: the examples below use PostCSS 8, autoprefixer 10, and cssnano 6/7. PostCSS 8 changed the plugin API, so mixing PostCSS 7-era plugins with PostCSS 8 is a common source of cryptic errors — check npm ls postcss if something fails to load.
Install the tools
Run this in your project root (no special permissions needed):
npm install --save-dev postcss postcss-cli autoprefixer cssnanopostcss-cli lets you run PostCSS from the command line or an npm script, which keeps this guide independent of any bundler.
Configure plugins and browser targets
Create postcss.config.js in the project root:
module.exports = {
plugins: [
require('autoprefixer'),
require('cssnano')({ preset: 'default' })
]
};Plugin order matters: autoprefixer runs first so cssnano minifies the final, prefixed CSS. If you minify first, you risk cssnano discarding or merging rules before prefixes are added, and the prefixes themselves never get optimized.
Autoprefixer decides which prefixes to add from your browserslist configuration. Without one, it uses defaults that may not match your audience. Add an explicit target to package.json:
"browserslist": [
"> 0.5%",
"last 2 versions",
"not dead"
]Adjust the query to your real support matrix. You can inspect what a query resolves to with npx browserslist.
Run the build
Add a script to package.json:
"scripts": {
"build:css": "postcss src/css/main.css -o dist/main.css"
}Run npm run build:css from the project root. The CLI reads postcss.config.js automatically. src/css/main.css is your source file; dist/main.css is the output.
Verify the output actually changed
Do not assume the pipeline worked — check it:
- Prefixes: put a property that historically needs prefixes in your source, e.g.
display: flexwith an old-browser-heavy browserslist, oruser-select: none. Confirm the output contains the expected-webkit-/-moz-variants for your targets. With a modern-only browserslist you may see no prefixes at all — that is correct behavior, not a failure. - Minification: compare file sizes with
wc -c src/css/main.css dist/main.css. The output should be a single line with comments stripped and whitespace removed. - Correctness: open a page using the built CSS in your target browsers (or BrowserStack equivalents) and confirm layout is unchanged. cssnano's default preset is safe, but aggressive custom presets can merge or reorder rules in ways that change cascade behavior.
Common failure modes
- Plugin order: if you add more plugins later (e.g.
postcss-import), keep imports first, prefixing near the end, minification last. - Version mismatch: "plugin is not a function" or silent no-ops usually mean a PostCSS 7/8 mismatch. Check
npm ls postcss autoprefixer cssnanoand align major versions. - Source maps: for debugging, add
--mapto the CLI command so minified output maps back to source in devtools.
Limitations
cssnano's default preset is conservative; enabling stronger options (e.g. zindex: false is off by default because reindexing z-index values can break stacking contexts across files) requires testing. Autoprefixer only handles properties it knows from Can I Use data; it will not polyfill missing features. And PostCSS processes one file's AST at a time — cross-file optimizations are outside its scope.
0 replies
A thoughtful contribution can make all the difference. Be the first to share one.