abordage/laravel-html-min
Laravel HTML Min is a lightweight middleware package that compresses your application's HTML output by stripping whitespace, comments, and redundant markup. Reduce page size and speed up load times with zero template changes—just install and enable it.
abordage/laravel-html-min sits in the response pipeline and shrinks the HTML your Blade views produce — collapsing whitespace, dropping comments, and trimming redundant markup. It is a "last mile" optimisation: your views stay readable, the bytes on the wire don't.
composer require abordage/laravel-html-min
php artisan vendor:publish --tag=html-min-config # confirm the tag in the README
Package discovery registers the service provider; you opt in by attaching the middleware. Laravel 11+:
// bootstrap/app.php
->withMiddleware(function (Middleware $middleware) {
$middleware->web(append: [
\Abordage\HtmlMin\Middlewares\HtmlMin::class,
]);
})
Older skeletons: append the same class to the web group in app/Http/Kernel.php.
Where to look first: config/html-min.php (this is the entire surface area — most issues are config, not code), then the middleware class to confirm how it inspects the response. The README's config table is the source of truth for key names; treat any snippet you copy from a blog post as suspect.
First use case: turn it on for public, server-rendered marketing/content pages only. Add the middleware to a route group rather than globally, confirm with curl -s https://your.app/ | head -40 that the output is actually minified and still renders, then widen the scope.
Scope by route group, not globally. A route-group or alias middleware gives you an escape hatch when a page breaks:
Route::middleware('html-min')->group(function () {
Route::get('/', HomeController::class);
Route::get('/pricing', PricingController::class);
});
Gate on environment. Minified output makes local debugging and diffing miserable, and it interferes with Debugbar/Telescope HTML injection. Prefer:
'enabled' => env('HTML_MIN_ENABLED', ! app()->isLocal()),
Exclude noisy paths. Admin panels, Horizon, Telescope, Debugbar routes, health checks, and anything returning files should be excluded via the config's exclude list (path/pattern based) rather than by splitting route files.
HTML5 void elements (new in 1.0.0). The release adds the ability to strip the self-closing slash from void elements — <br /> → <br>, <img /> → <img>. If you run HTML5 validators, Prettier/formatting checks, or diff markup against browser DevTools output, enable it:
'void_elements_trailing_slash' => false, // <br /> becomes <br>
Keep it true (or omit) if you emit XHTML-ish markup, feed the HTML through an XML parser, or inline SVG where self-closing syntax is significant. Verify the exact config key against the 1.0.0 README before committing.
Cache after minification. If you use a response cache or full-page cache, make sure the minifier runs before the cache write — otherwise you cache the fat version and pay the minify cost on every hit anyway.
Testing. Feature tests are the cheapest safety net. Assert on the minified shape deliberately:
$this->get('/')
->assertOk()
->assertSee('<br>', false) // proves the new option is active
->assertDontSee('<!--');
Beware assertSee without the $escape = false flag and any assertion on exact whitespace — those become brittle the moment you tune the config.
Whitespace-sensitive content is the #1 breakage. Inline-block elements (nav links, buttons, badges) rely on literal whitespace between tags for spacing. Collapsing it removes the gap and silently shifts your layout. Check your navigation and any display: inline-block lists after enabling.
Verify <pre>, <textarea>, <code>, and <script> survive intact. A minifier that isn't context-aware will eat significant whitespace inside <pre> or break a template literal in an inline script. Test a page containing each.
Comment removal can break frameworks. Knockout-style <!-- ko --> bindings, IE conditional comments, and some analytics snippets live in comments. If you rely on any of these, keep comment stripping off or verify the minifier is selective about which comments it removes.
Middleware ordering matters. Anything that appends to the response after the minifier (Debugbar, some profiling packages, third-party injectors) will re-introduce unminified markup. Put the minifier last in the web group, and disable the injectors in production where they shouldn't be running anyway.
Skip non-HTML and streaming responses. Confirm the middleware checks Content-Type and bails on BinaryFileResponse, StreamedResponse, and JSON. Inertia and API routes return JSON — you don't want a string-level HTML pass near them. If you see corrupted downloads or truncated streams, this is the suspect.
Treat 0.2.2 → 1.0.0 as potentially breaking despite the changelog. The notes list a single feature, but the major version bump usually signals finalised config keys, raised PHP/Laravel minimums, or a renamed namespace. Before upgrading:
composer why-not abordage/laravel-html-min 1.0.0 # check PHP/Laravel constraints
then republish the config with --force, diff it against your published copy, and re-apply your customisations. If you had inlined a config key that got renamed, it will now be silently ignored — the package will still run, just not the way you configured it. Assert on the new void-element behaviour in a test so a silent no-op becomes a red test.
Extension points. If you need per-route or per-tenant rules, wrap the middleware with your own that inspects the request and short-circuits, rather than forking the package. The minifier itself is typically resolvable from the container if you want to call it directly from a Blade directive, a response macro, or an artisan command that pre-renders static pages.
Don't expect miracles on Gzip/Brotli traffic. Minification composes with compression, not instead of it — the win is real but smaller than the raw byte delta suggests. Measure with Content-Length on a compressed response if you need to justify it.
How can I help you explore Laravel packages today?