TL;DR
html-minifier-terser is the most popular HTML minifier — feature-rich, handles inline CSS/JS, highly configurable, maintained fork of html-minifier. htmlnano is the modular HTML minifier — PostCSS-style plugin architecture, integrates with PostHTML, safe defaults, used by Parcel. minify-html is the Rust-based HTML minifier — blazing fast native performance, WASM and Node.js bindings, aggressive minification. In 2026: html-minifier-terser for maximum configurability, htmlnano for PostHTML/Parcel integration, minify-html for maximum speed.
Key Takeaways
- html-minifier-terser: ~10M weekly downloads — feature-rich, configurable, handles inline CSS/JS
- htmlnano: ~2M weekly downloads — modular plugins, PostHTML ecosystem, used by Parcel
- minify-html: ~500K weekly downloads — Rust/WASM, fastest performance, aggressive defaults
- All three reduce HTML file sizes by removing whitespace, comments, and redundant attributes
- html-minifier-terser is the most battle-tested and widely used
- minify-html is 5-10x faster than JS-based minifiers
html-minifier-terser
html-minifier-terser — feature-rich HTML minifier:
Basic usage
import { minify } from "html-minifier-terser"
const html = `
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>PkgPulse</title>
<style>
body {
margin: 0;
padding: 0;
}
</style>
</head>
<body>
<!-- Main content -->
<div class="container">
<h1>Compare npm packages</h1>
</div>
<script>
console.log("Hello");
</script>
</body>
</html>
`
const minified = await minify(html, {
collapseWhitespace: true,
removeComments: true,
minifyCSS: true,
minifyJS: true,
removeRedundantAttributes: true,
removeEmptyAttributes: true,
removeOptionalTags: true,
})
// → <!DOCTYPE html><html lang=en><meta charset=UTF-8><title>PkgPulse</title><style>body{margin:0;padding:0}</style><div class=container><h1>Compare npm packages</h1></div><script>console.log("Hello")</script>
Full configuration
import { minify } from "html-minifier-terser"
const result = await minify(html, {
// Whitespace:
collapseWhitespace: true,
conservativeCollapse: false, // Don't preserve single space
collapseInlineTagWhitespace: false,
// Comments:
removeComments: true,
removeCommentsFromCDATA: true,
// Attributes:
removeRedundantAttributes: true, // Remove type="text" from input
removeEmptyAttributes: true, // Remove class=""
removeOptionalTags: true, // Remove optional closing tags
removeAttributeQuotes: true, // Remove quotes when safe
collapseBooleanAttributes: true, // disabled="disabled" → disabled
sortAttributes: true,
sortClassName: true,
// Inline CSS/JS:
minifyCSS: true, // Uses clean-css
minifyJS: true, // Uses terser
minifyURLs: true,
// HTML5:
html5: true,
useShortDoctype: true,
// Custom processing:
processScripts: ["application/ld+json"], // Minify JSON-LD
})
With build tools
// Webpack — html-webpack-plugin uses html-minifier-terser:
const HtmlWebpackPlugin = require("html-webpack-plugin")
module.exports = {
plugins: [
new HtmlWebpackPlugin({
template: "./src/index.html",
minify: {
collapseWhitespace: true,
removeComments: true,
minifyCSS: true,
minifyJS: true,
},
}),
],
}
// Vite — vite-plugin-html:
import { createHtmlPlugin } from "vite-plugin-html"
export default {
plugins: [
createHtmlPlugin({
minify: true, // Uses html-minifier-terser internally
}),
],
}
htmlnano
htmlnano — modular HTML minifier:
Basic usage
import htmlnano from "htmlnano"
const { html } = await htmlnano.process(`
<div class="container">
<!-- Comment -->
<h1>Hello World</h1>
<img src="photo.jpg" alt="" />
</div>
`)
// → <div class="container"><h1>Hello World</h1><img src="photo.jpg" alt></div>
Preset configurations
import htmlnano from "htmlnano"
// Safe preset (default) — only safe transformations:
const safe = await htmlnano.process(html, {}, htmlnano.presets.safe)
// Aggressive preset — maximum minification:
const max = await htmlnano.process(html, {}, htmlnano.presets.max)
// Custom — cherry-pick modules:
const custom = await htmlnano.process(html, {
removeComments: "safe", // Remove non-conditional comments
collapseWhitespace: "conservative", // Safe whitespace collapse
minifyCss: true, // Minify inline CSS (uses cssnano)
minifyJs: true, // Minify inline JS (uses terser)
minifySvg: true, // Minify inline SVGs (uses svgo)
removeRedundantAttributes: true,
collapseBooleanAttributes: true,
removeEmptyAttributes: true,
deduplicateAttributeValues: true,
removeOptionalTags: false, // Keep optional tags (safer)
})
With PostHTML
import posthtml from "posthtml"
import htmlnano from "htmlnano"
// Use as PostHTML plugin:
const result = await posthtml([
// Other PostHTML plugins first:
require("posthtml-expressions")({ locals: { title: "PkgPulse" } }),
require("posthtml-include")(),
// Then minify:
htmlnano({
removeComments: "safe",
collapseWhitespace: "conservative",
minifyCss: true,
}),
]).process(html)
console.log(result.html)
With Parcel
// .htmlnanorc.js (Parcel uses htmlnano automatically):
module.exports = {
removeComments: "safe",
collapseWhitespace: "conservative",
minifyCss: true,
minifyJs: true,
minifySvg: true,
removeRedundantAttributes: true,
}
// Parcel applies htmlnano during production builds automatically
Available modules
htmlnano modules:
Whitespace:
collapseWhitespace: "conservative" | "all" | false
collapseAttributeWhitespace: true/false
Comments:
removeComments: "safe" | "all" | false
Attributes:
removeRedundantAttributes: true/false
removeEmptyAttributes: true/false
collapseBooleanAttributes: true/false
deduplicateAttributeValues: true/false
removeOptionalTags: true/false
normalizeAttributeValues: true/false
Inline code:
minifyCss: true/false (uses cssnano)
minifyJs: true/false (uses terser)
minifySvg: true/false (uses svgo)
minifyJson: true/false
Other:
mergeStyles: true/false
mergeScripts: true/false
sortAttributes: true/false
sortAttributesWithLists: true/false
removeUnusedCss: true/false (uses uncss)
minify-html
minify-html — Rust-based HTML minifier:
Node.js usage
import { minify } from "@nicolo-ribaudo/minify-html"
const html = Buffer.from(`
<!DOCTYPE html>
<html>
<head>
<title>PkgPulse</title>
</head>
<body>
<!-- Comment -->
<div class="container">
<h1>Compare packages</h1>
</div>
</body>
</html>
`)
const minified = minify(html, {
do_not_minify_doctype: false,
ensure_spec_compliant_unquoted_attribute_values: false,
keep_closing_tags: false,
keep_html_and_head_opening_tags: false,
keep_spaces_between_attributes: false,
keep_comments: false,
minify_css: true,
minify_js: true,
remove_bangs: false,
remove_processing_instructions: false,
})
console.log(minified.toString())
// → Very aggressively minified output
Configuration options
import { minify } from "@nicolo-ribaudo/minify-html"
const result = minify(htmlBuffer, {
// Keep certain elements:
keep_closing_tags: true, // Preserve </p>, </li>, etc.
keep_html_and_head_opening_tags: true,
keep_comments: false,
keep_spaces_between_attributes: false,
// Inline minification:
minify_css: true, // Minify <style> and style attributes
minify_js: true, // Minify <script> content
// Safety:
do_not_minify_doctype: true,
ensure_spec_compliant_unquoted_attribute_values: true,
})
Performance comparison
Benchmark: Minifying a 100KB HTML file (average of 100 runs)
html-minifier-terser: 45ms
htmlnano (safe): 38ms
htmlnano (max): 52ms
minify-html: 4ms ← ~10x faster
For large-scale static sites (1000+ pages):
html-minifier-terser: ~45 seconds
minify-html: ~4 seconds
minify-html is written in Rust and compiled to native code,
making it significantly faster for batch processing.
Build tool integration
// Vite plugin:
import { minify } from "@nicolo-ribaudo/minify-html"
import { Plugin } from "vite"
function htmlMinifyPlugin(): Plugin {
return {
name: "html-minify",
enforce: "post",
apply: "build",
transformIndexHtml(html) {
return minify(Buffer.from(html), {
minify_css: true,
minify_js: true,
keep_closing_tags: false,
}).toString()
},
}
}
// vite.config.ts:
export default {
plugins: [htmlMinifyPlugin()],
}
Feature Comparison
| Feature | html-minifier-terser | htmlnano | minify-html |
|---|---|---|---|
| Language | JavaScript | JavaScript | Rust (WASM/native) |
| Speed | Medium | Medium | Fast (10x) |
| Configurability | High (30+ options) | High (modular) | Medium |
| Inline CSS minify | ✅ (clean-css) | ✅ (cssnano) | ✅ (built-in) |
| Inline JS minify | ✅ (terser) | ✅ (terser) | ✅ (built-in) |
| SVG minification | ❌ | ✅ (svgo) | ❌ |
| PostHTML integration | ❌ | ✅ | ❌ |
| Plugin system | ❌ | ✅ | ❌ |
| Used by | Webpack, Vite plugins | Parcel | Custom builds |
| WASM support | N/A | N/A | ✅ |
| Weekly downloads | ~10M | ~2M | ~500K |
When to Use Each
Use html-minifier-terser if:
- Need the most battle-tested HTML minifier
- Want fine-grained control over every optimization
- Using Webpack or html-webpack-plugin
- Need to minify inline CSS/JS with terser/clean-css
Use htmlnano if:
- Using PostHTML for HTML processing
- Using Parcel (built-in)
- Want modular, plugin-based minification
- Need SVG minification in inline SVGs
Use minify-html if:
- Need maximum performance (large sites, many pages)
- Building a static site generator or custom build tool
- Want Rust-native speed for batch processing
- Fine with fewer configuration options for faster output
Methodology
Download data from npm registry (weekly average, February 2026). Feature comparison based on html-minifier-terser v7.x, htmlnano v2.x, and @nicolo-ribaudo/minify-html v0.15.x.
Inline CSS and JavaScript Minification Differences
One of the most impactful — and risky — features of HTML minifiers is their ability to process inline <style> blocks and <script> tags as part of the HTML minification pass. All three libraries support this, but they use different underlying tools and have different safety guarantees.
html-minifier-terser delegates inline CSS to clean-css and inline JavaScript to terser — the same terser used for standalone JavaScript minification. This means you get production-grade JS minification inside your HTML: dead code elimination, constant folding, variable name mangling. The downside is that mangling variable names in inline scripts can break code that relies on global variable names being preserved. The minifyJS: { mangle: false } option disables name mangling if you need safety over compression.
htmlnano uses cssnano for CSS (which is the PostCSS-based CSS minifier, offering more PostCSS-aware transformations than clean-css) and also delegates to terser for JavaScript. The key difference is that htmlnano's plugin architecture lets you swap out the underlying tools — you can configure cssnano options directly through htmlnano's minifyCss option, giving you access to cssnano's full preset system. The minifySvg module (using svgo) is unique to htmlnano and is meaningful for pages with inline SVG icons or illustrations, which can often be reduced by 30-50% in size.
minify-html handles CSS and JS minification internally with its own Rust implementation rather than delegating to external tools. This makes it faster but less configurable — you cannot pass custom terser options or use cssnano presets. The tradeoff is predictability: minify-html's built-in minifier is more conservative by default, reducing the risk of broken inline scripts at the cost of slightly less aggressive compression.
Static Site Generator Integration Patterns
For static site generators (SSGs), HTML minification sits at the end of the build pipeline and processes every generated HTML file. The choice of minifier affects both build time and the risk of broken output pages, making it a higher-stakes decision than for single-page apps.
Next.js, Nuxt, and Astro do not expose HTML minifier configuration directly — they handle it internally. Next.js uses html-minifier-terser internally (configurable via compress options in next.config.js). Astro compiles and minifies HTML as part of its Vite build. For custom SSGs or build scripts using tools like Eleventy, custom Vite plugins, or Metalsmith, you control the minifier choice directly.
For Eleventy specifically, html-minifier-terser is the most commonly documented choice and is used in Eleventy's official starter templates. A common pattern is an Eleventy transform that runs html-minifier-terser on every HTML output file during production builds, skipping minification in development for faster reloads. minify-html is an increasingly popular alternative for large Eleventy sites with hundreds of pages, where the 10x speed difference measurably shortens build times. The key consideration is testing the output: minify-html's aggressive removal of optional closing tags and unquoted attributes can reveal HTML that depends on lenient browser parsing, making a thorough test pass essential before deploying to production.
Compare build tooling and performance utilities on PkgPulse →
Measuring Actual Savings and Avoiding Over-Minification
Before committing to an HTML minifier in a build pipeline, it's worth measuring what savings you're actually getting — and whether aggressive minification introduces rendering bugs. HTML minification savings are not uniform: a marketing page with heavy inline CSS will see much better compression ratios than a data-heavy table-rendered page with minimal markup.
The most reliable way to measure minification impact is to run your minifier against a sample of representative HTML files and compare sizes before and after, then compare against your gzip/Brotli-compressed output. The key insight is that HTML minification and content encoding are not independent: gzip and Brotli use LZ-based compression that is very effective at compressing repetitive whitespace patterns, so the whitespace that HTML minifiers remove often compresses well already. Real-world measurements show that HTML minification (whitespace removal only) typically reduces gzip-compressed HTML by 3-8%, while removing whitespace plus collapsing attributes and removing optional tags adds another 2-5% on top of that.
Where HTML minification provides more meaningful gains is for HTML that is served without content encoding — for example, server-side rendered HTML served from edge functions or CDN edges that don't apply Brotli compression, or HTML embedded in JSON API responses as string values (common in some headless CMS architectures).
Testing minified output for rendering correctness is non-negotiable before deploying to production. The most common regressions come from three sources: inline JavaScript that relies on preserved whitespace in string literals (minifying <script>var x = " hello " can alter the string content), CSS that uses whitespace-sensitive properties like white-space: pre in combination with dynamically generated content, and conditional HTML comments (<!--[if IE]>) that should be preserved but get removed by removeComments: true. Run your full test suite against minified output in CI rather than only testing against unminified development builds.
When to Use Each
Use html-minifier-terser if:
- You are minifying HTML in a Node.js build script or CLI pipeline
- You want fine-grained control: remove specific whitespace, inline JS/CSS, collapse attributes
- You need a widely-adopted package with extensive configuration options and community knowledge
Use htmlnano if:
- You are using PostHTML as your HTML transformation pipeline
- You want modular minification where each optimization is a separate PostHTML plugin
- You need to selectively enable/disable specific minification transforms
Use minify-html (vise/minify-html) if:
- Performance is critical — it is 40-100× faster than html-minifier-terser on large HTML files
- You are processing many large HTML files in CI (static site generation)
- You have Rust or WASM in your toolchain (it ships as a compiled binary or WASM module)
In 2026, the right choice is contextual. For most webpack/Vite build pipelines, html-minifier-terser remains the default via html-webpack-plugin and similar tools. For custom build pipelines or static site generators processing many files, minify-html's performance advantage justifies the additional dependency complexity.
A practical benchmark note: HTML minification savings vary significantly by content type. For content-heavy HTML (blog posts, documentation), savings are typically 5-15%. For attribute-heavy component templates, savings can reach 20-30%. Run minification benchmarks on your actual HTML before assuming a specific savings target.
Methodology
Download data from npm registry (weekly average, February 2026). Feature comparison based on html-minifier-terser v5.x, htmlnano v2.x, and minify-html v0.15.x. html-minifier-terser is a maintained fork of the unmaintained html-minifier, created to keep security patches current. htmlnano is the HTML minification step in PostHTML pipelines and is used by Parcel's HTML optimizer. minify-html is a Rust-based binary with Node.js WASM and native addon bindings — it achieves its performance advantage by avoiding JavaScript regex operations entirely and using a hand-written parser in Rust.
See also: cac vs meow vs arg 2026 and Ink vs @clack/prompts vs Enquirer, acorn vs @babel/parser vs espree.