HTML Minifier Next

HTML Minifier Next (HMN) is a super-configurable, well-tested, JavaScript-based HTML minifier.
The project was based on HTML Minifier Terser, which in turn had been based on Juriy “kangax” Zaytsev’s HTML Minifier. HMN offers additional features, but is backwards-compatible with both. The project was set up because as of 2025, both HTML Minifier Terser and HTML Minifier had been unmaintained for a few years. As the project seems maintainable [to me, Jens, an HTML optimizer]—even more so with community support—, it’s being updated, extended, and documented further in this place.
Installation
From npm for use as a command line app:
npm i -g html-minifier-next
From npm for programmatic use:
npm i html-minifier-next
General usage
CLI
Use html-minifier-next --help to check all available options:
--input-dir <dir> | Specify an input directory (best restricted with --file-ext) | --input-dir=src |
--ignore-dir <patterns> | Exclude directories—relative to input directory—from processing (comma-separated, overrides config file setting) | --ignore-dir=libs, --ignore-dir=libs,vendor,node_modules |
--output-dir <dir> | Specify an output directory | --output-dir=dist |
--file-ext <extensions> | Specify file extension(s) to process (comma-separated, overrides config file setting) | --file-ext=html, --file-ext=html,htm,php, --file-ext="html, htm, php" |
-o <file>, --output <file> | Specify output file (reads from file arguments or STDIN) | File to file: html-minifier-next input.html -o output.html Pipe to file: cat input.html | html-minifier-next -o output.html File to STDOUT: html-minifier-next input.html |
-c <file>, --config-file <file> | Use a configuration file | --config-file=html-minifier.json |
--preset <name> | Use a preset configuration (conservative or comprehensive) | --preset=conservative |
-v, --verbose | Show detailed processing information (active options, file statistics) | html-minifier-next --input-dir=src --output-dir=dist --verbose --collapse-whitespace |
-d, --dry | Dry run: Process and report statistics without writing output | html-minifier-next input.html --dry --collapse-whitespace |
-V, --version | Output the version number | html-minifier-next --version |
Configuration file
You can also use a configuration file to specify options. The file can be either JSON format or a JavaScript module that exports the configuration object:
JSON configuration example:
{
"collapseWhitespace": true,
"removeComments": true,
"fileExt": "html,htm",
"ignoreDir": "libs,vendor"
}
JavaScript module configuration example:
module.exports = {
collapseWhitespace: true,
removeComments: true,
fileExt: "html,htm",
ignoreDir: ["libs", "vendor"]
};
Using a configuration file:
html-minifier-next --config-file=html-minifier.json --input-dir=src --output-dir=dist
html-minifier-next --config-file=html-minifier.json --file-ext=xml --input-dir=src --output-dir=dist
Node.js
ESM with Node.js ≥16.14:
import { minify } from 'html-minifier-next';
const result = await minify('<p title="blah" id="moo">foo</p>', {
removeAttributeQuotes: true,
});
console.log(result);
CommonJS:
const { minify } = require('html-minifier-next');
(async () => {
const result = await minify('<p title="blah" id="moo">foo</p>', {
removeAttributeQuotes: true,
});
console.log(result);
})();
See the original blog post for details of how it works, description of each option, testing results, and conclusions.
For lint-like capabilities, take a look at HTMLLint.
Presets
HTML Minifier Next provides presets for common use cases. Presets are pre-configured option sets that can be used as a starting point:
conservative: Safe minification suitable for most projects. Includes whitespace collapsing, comment removal, and doctype normalization.
comprehensive: Aggressive minification for maximum file size reduction. Includes relevant conservative options plus attribute quote removal, optional tag removal, and more.
To review the specific options set, presets.js lists them in an accessible manner.
Using presets:
html-minifier-next --preset conservative input.html
html-minifier-next --config-file=html-minifier.json input.html
html-minifier-next --preset conservative --remove-empty-attributes input.html
Priority order: Presets are applied first, then config file options, then CLI flags. This allows you to start with a preset and customize as needed.
Options quick reference
Most of the options are disabled by default. Experiment and find what works best for you and your project.
Options can be used in config files (camelCase) or via CLI flags (kebab-case with -- prefix). Options that default to true use --no- prefix in CLI to disable them.
caseSensitive
--case-sensitive | Treat attributes in case-sensitive manner (useful for custom HTML elements) | false |
collapseAttributeWhitespace
--collapse-attribute-whitespace | Trim and collapse whitespace characters within attribute values | false |
collapseBooleanAttributes
--collapse-boolean-attributes | Omit attribute values from boolean attributes | false |
collapseInlineTagWhitespace
--collapse-inline-tag-whitespace | Don’t leave any spaces between display: inline; elements when collapsing—use with collapseWhitespace: true | false |
collapseWhitespace
--collapse-whitespace | Collapse whitespace that contributes to text nodes in a document tree | false |
conservativeCollapse
--conservative-collapse | Always collapse to one space (never remove it entirely)—use with collapseWhitespace: true | false |
continueOnMinifyError
--no-continue-on-minify-error | Continue on minification errors; when false, minification errors throw and abort processing | true |
continueOnParseError
--continue-on-parse-error | Handle parse errors instead of aborting | false |
customAttrAssign
--custom-attr-assign | Arrays of regexes that allow to support custom attribute assign expressions (e.g., <div flex?="{{mode != cover}}"></div>) | [] |
customAttrCollapse
--custom-attr-collapse | Regex that specifies custom attribute to strip newlines from (e.g., /ng-class/) | |
customAttrSurround
--custom-attr-surround | Arrays of regexes that allow to support custom attribute surround expressions (e.g., <input {{#if value}}checked="checked"{{/if}}>) | [] |
customEventAttributes
--custom-event-attributes | Arrays of regexes that allow to support custom event attributes for minifyJS (e.g., ng-click) | [ /^on[a-z]{3,}$/ ] |
customFragmentQuantifierLimit
--custom-fragment-quantifier-limit | Set maximum quantifier limit for custom fragments to prevent ReDoS attacks | 200 |
decodeEntities
--decode-entities | Use direct Unicode characters whenever possible | false |
html5
--no-html5 | Parse input according to the HTML specification; when false, enforces legacy inline/block nesting rules that may restructure modern HTML | true |
ignoreCustomComments
--ignore-custom-comments | Array of regexes that allow to ignore certain comments, when matched | [ /^!/, /^\s*#/ ] |
ignoreCustomFragments
--ignore-custom-fragments | Array of regexes that allow to ignore certain fragments, when matched (e.g., <?php … ?>, {{ … }}, etc.) | [ /<%[\s\S]*?%>/, /<\?[\s\S]*?\?>/ ] |
includeAutoGeneratedTags
--no-include-auto-generated-tags | Insert elements generated by HTML parser; when false, omits auto-generated tags | true |
inlineCustomElements
--inline-custom-elements | Array of names of custom elements which are inline | [] |
keepClosingSlash
--keep-closing-slash | Keep the trailing slash on void elements | false |
maxInputLength
--max-input-length | Maximum input length to prevent ReDoS attacks (disabled by default) | undefined |
maxLineLength
--max-line-length | Specify a maximum line length; compressed output will be split by newlines at valid HTML split-points | |
minifyCSS
--minify-css | Minify CSS in style elements and style attributes (uses Lightning CSS) | false (could be true, Object, Function(text, type)) |
minifyJS
--minify-js | Minify JavaScript in script elements and event attributes (uses Terser) | false (could be true, Object, Function(text, inline)) |
minifyURLs
--minify-urls | Minify URLs in various attributes (uses relateurl) | false (could be String, Object, Function(text), async Function(text)) |
noNewlinesBeforeTagClose
--no-newlines-before-tag-close | Never add a newline before a tag that closes an element | false |
partialMarkup
--partial-markup | Treat input as a partial HTML fragment, preserving stray end tags (closing tags without opening tags) and preventing auto-closing of unclosed tags at end of input | false |
preserveLineBreaks
--preserve-line-breaks | Always collapse to one line break (never remove it entirely) when whitespace between tags includes a line break—use with collapseWhitespace: true | false |
preventAttributesEscaping
--prevent-attributes-escaping | Prevents the escaping of the values of attributes | false |
processConditionalComments
--process-conditional-comments | Process contents of conditional comments through minifier | false |
processScripts
--process-scripts | Array of strings corresponding to types of script elements to process through minifier (e.g., text/ng-template, text/x-handlebars-template, etc.) | [] |
quoteCharacter
--quote-character | Type of quote to use for attribute values (' or ") | Auto-detected (uses the quote requiring less escaping; defaults to " when equal) |
removeAttributeQuotes
--remove-attribute-quotes | Remove quotes around attributes when possible | false |
removeComments
--remove-comments | Strip HTML comments | false |
removeEmptyAttributes
--remove-empty-attributes | Remove all attributes with whitespace-only values | false (could be true, Function(attrName, tag)) |
removeEmptyElements
--remove-empty-elements | Remove all elements with empty contents | false |
removeEmptyElementsExcept
--remove-empty-elements-except | Array of elements to preserve when removeEmptyElements is enabled; accepts simple tag names (e.g., ["td"]) or HTML-like markup with attributes (e.g., ["<span aria-hidden='true'>"]); supports double quotes, single quotes, and unquoted attribute values | [] |
removeOptionalTags
--remove-optional-tags | Remove optional tags | false |
removeRedundantAttributes
--remove-redundant-attributes | Remove attributes when value matches default | false |
removeScriptTypeAttributes
--remove-script-type-attributes | Remove type="text/javascript" from script elements; other type attribute values are left intact | false |
removeStyleLinkTypeAttributes
--remove-style-link-type-attributes | Remove type="text/css" from style and link elements; other type attribute values are left intact | false |
removeTagWhitespace
--remove-tag-whitespace | Remove space between attributes whenever possible; note that this will result in invalid HTML | false |
sortAttributes
--sort-attributes | Sort attributes by frequency | false |
sortClassName
--sort-class-name | Sort style classes by frequency | false |
trimCustomFragments
--trim-custom-fragments | Trim whitespace around ignoreCustomFragments | false |
useShortDoctype
--use-short-doctype | Replaces the doctype with the short (HTML) doctype | false |
Sorting attributes and style classes
Minifier options like sortAttributes and sortClassName won’t impact the plain‑text size of the output. However, using these options for more consistent ordering improves the compression ratio for gzip and Brotli used over HTTP.
CSS minification with Lightning CSS
When minifyCSS is set to true, HTML Minifier Next uses Lightning CSS to minify CSS in <style> elements and style attributes. Lightning CSS provides excellent minification by default.
You can pass Lightning CSS configuration options by providing an object:
const result = await minify(html, {
minifyCSS: {
targets: {
chrome: 95,
firefox: 90,
safari: 14
},
unusedSymbols: ['unused-class', 'old-animation']
}
});
Available Lightning CSS options when passed as an object:
targets: Browser targets for vendor prefix optimization (e.g., { chrome: 95, firefox: 90 }).
unusedSymbols: Array of class names, IDs, keyframe names, and CSS variables to remove.
errorRecovery: Boolean to skip invalid rules instead of throwing errors. This is disabled by default in Lightning CSS, but enabled in HMN when the continueOnMinifyError option is set to true (the default). Explicitly setting errorRecovery in minifyCSS options will override this automatic behavior.
sourceMap: Boolean to generate source maps.
For advanced usage, you can also pass a function:
const result = await minify(html, {
minifyCSS: function(text, type) {
return yourCustomMinifier(text);
}
});
Minification comparison
How does HTML Minifier Next compare to other minifiers? (All with the most aggressive settings, though without hyper-optimization. Minimize does not minify CSS or JS.)
(Last updated: Dec 17, 2025)
Examples
CLI
Sample command line:
html-minifier-next --collapse-whitespace --remove-comments --minify-js --input-dir=. --output-dir=example
Process specific files and directories:
html-minifier-next --collapse-whitespace --input-dir=src --output-dir=dist --file-ext=html
html-minifier-next --collapse-whitespace --input-dir=src --output-dir=dist --file-ext=html,htm,php
html-minifier-next --config-file=html-minifier.json --input-dir=src --output-dir=dist
html-minifier-next --collapse-whitespace --input-dir=src --output-dir=dist
Exclude directories from processing:
html-minifier-next --collapse-whitespace --input-dir=src --output-dir=dist --ignore-dir=libs
html-minifier-next --collapse-whitespace --input-dir=src --output-dir=dist --ignore-dir=libs,vendor,node_modules
html-minifier-next --collapse-whitespace --input-dir=src --output-dir=dist --ignore-dir=static/libs
Dry run mode (preview outcome without writing files):
html-minifier-next input.html -o output.html --dry --collapse-whitespace
html-minifier-next --input-dir=src --output-dir=dist --dry --collapse-whitespace
Verbose mode (show detailed processing information):
html-minifier-next --input-dir=src --output-dir=dist --verbose --collapse-whitespace
html-minifier-next --input-dir=src --output-dir=dist --dry --collapse-whitespace
Special cases
Ignoring chunks of markup
If you have chunks of markup you would like preserved, you can wrap them with <!-- htmlmin:ignore -->.
Minifying JSON content
JSON script types are minified automatically without configuration, including application/json, application/ld+json, application/manifest+json, application/vnd.geo+json, application/problem+json, application/merge-patch+json, application/json-patch+json, importmap, and speculationrules. Malformed JSON is preserved by default (with continueOnMinifyError: true).
Note: The processScripts option is only for script types containing HTML templates (e.g., text/ng-template, text/x-handlebars-template), not for JSON.
Preserving SVG and MathML elements
SVG and MathML elements are automatically recognized as foreign elements, and when they are minified, both case-sensitivity and self-closing slashes are preserved, regardless of the minification settings used for the rest of the file. This ensures valid output for these namespaced elements.
Working with invalid or partial markup
By default, HTML Minifier Next parses markup into a complete tree structure, then modifies it (removing anything that was specified for removal, ignoring anything that was specified to be ignored, etc.), then creates markup from that tree and returns it.
Input markup (e.g., <p id="">foo) → Internal representation of markup in a form of tree (e.g., { tag: "p", attr: "id", children: ["foo"] }) → Transformation of internal representation (e.g., removal of id attribute) → Output of resulting markup (e.g., <p>foo</p>)
For partial HTML fragments (such as template includes, SSI fragments, or closing tags without opening tags), use the partialMarkup: true option. This preserves stray end tags (closing tags without corresponding opening tags) and prevents auto-closing of unclosed tags at the end of input. Note that normal HTML auto-closing rules still apply during parsing—for example, a closing parent tag will still auto-close its unclosed child elements.
To validate complete HTML markup, use the W3C validator or one of several validator packages.
Security
ReDoS protection
This minifier includes protection against regular expression denial of service (ReDoS) attacks:
-
Custom fragment quantifier limits: The customFragmentQuantifierLimit option (default: 200) prevents exponential backtracking by replacing unlimited quantifiers (*, +) with bounded ones in regular expressions.
-
Input length limits: The maxInputLength option allows you to set a maximum input size to prevent processing of excessively large inputs that could cause performance issues.
-
Enhanced pattern detection: The minifier detects and warns about various ReDoS-prone patterns including nested quantifiers, alternation with quantifiers, and multiple unlimited quantifiers.
Important: When using custom ignoreCustomFragments, ensure your regular expressions don’t contain unlimited quantifiers (*, +) without bounds, as these can lead to ReDoS vulnerabilities.
Custom fragment examples
Safe patterns (recommended):
ignoreCustomFragments: [
/<%[\s\S]{0,1000}?%>/,
/<\?php[\s\S]{0,5000}?\?>/,
/\{\{[^}]{0,500}\}\}/
]
Potentially unsafe patterns (will trigger warnings):
ignoreCustomFragments: [
/<%[\s\S]*?%>/,
/<!--[\s\S]*?-->/,
/\{\{.*?\}\}/,
/(script|style)[\s\S]*?/
]
Template engine configurations:
ignoreCustomFragments: [/\{\{[\s\S]{0,1000}?\}\}/]
ignoreCustomFragments: [/\{%[\s\S]{0,500}?%\}/, /\{\{[\s\S]{0,500}?\}\}/]
ignoreCustomFragments: [/\{\{[\s\S]{0,500}?\}\}/]
ignoreCustomFragments: [/\{\{[\s\S]{0,500}?\}\}/]
Important: When using custom ignoreCustomFragments, the minifier automatically applies bounded quantifiers to prevent ReDoS attacks, but you can also write safer patterns yourself using explicit bounds.
Running HTML Minifier Next locally
Local server
npm run serve
Benchmarks
Benchmarks for minified HTML:
cd benchmarks;
npm i;
npm run benchmarks
(In case of dependency conflicts, run npm i with the --legacy-peer-deps flag.)
Acknowledgements
With many thanks to all the previous authors of HTML Minifier, especially Juriy “kangax” Zaytsev, and to everyone who helped make this new edition better, particularly Daniel Ruf and Jonas Geiler.