Weave Code
Weaver
Helps Laravel developers discover, compare, and choose open-source packages. See popularity, security, maintainers, and scores at a glance to make better decisions.
Feedback
Share your thoughts, report bugs, or suggest improvements.
Subject
Message

Laravel Compress Html Laravel Package

akankov/laravel-compress-html

Laravel middleware that minifies HTML responses on the fly, removing whitespace, comments, and redundant markup to shrink page size and speed up delivery with zero template changes.

View on GitHub
Deep Wiki
Context7

Getting Started

v2.1.1 is a patch release: no API changes, no config schema changes, nothing to migrate. The substantive changes are an improved boot-time error for stale published configs, plus repo-internal quality tooling (Infection, Dependabot, composer audit). You can upgrade in place.

  1. Install

    composer require akankov/laravel-compress-html
    
  2. Publish the config — you want config/htmlmin.php in your repo so the minification rules are code-reviewed like everything else:

    php artisan vendor:publish --tag=htmlmin
    # if the tag doesn't match, run `php artisan vendor:publish` and pick the package from the list
    

    Read the file top to bottom before touching it. The keys are snake_case and map 1:1 onto the underlying engine's options; the defaults are conservative and are the right starting point.

  3. Register the middleware. In Laravel 11/12, append it to the web group — not globally — in bootstrap/app.php:

    ->withMiddleware(function (Middleware $middleware) {
        $middleware->web(append: [
            \Akankov\CompressHtml\Middleware\CompressHtml::class, // confirm exact FQCN in the README
        ]);
    })
    

    On Laravel 10 and below, add the same class to the end of $middlewareGroups['web'] in app/Http/Kernel.php.

  4. First use case — prove it does something. Load any HTML route and check the payload, not the page:

    curl -s https://myapp.test/ | wc -c      # with middleware
    # comment out the middleware line, repeat, compare
    

    Or in a feature test: $this->get('/')->assertOk() and assert the response body contains no <!-- comments. That's the 60-second sanity check; everything else in this assessment is about not breaking pages while you chase that byte count.

Where to look first: config/htmlmin.php (what gets stripped), then the package's middleware class in src/ (how it decides which responses to touch — this is where you verify the Content-Type guard).

Implementation Patterns

Scope it to HTML, on the web group only. Appending to web gives you rendered Blade, Inertia root documents, and Livewire full-page loads. Do not register it globally: api routes, file downloads, and streamed responses have no business going through an HTML parser, and a middleware that rewrites the body is the classic way to corrupt an application/octet-stream response.

Toggle by environment. Minification makes local debugging worse (stripped comments, mangled indentation in View Source, harder grep) and buys nothing behind a dev server. Gate it:

// config/htmlmin.php
'enabled' => env('HTMLMIN_ENABLED', ! app()->isLocal()),

Then keep HTMLMIN_ENABLED=false in .env and in phpunit.xml. This single flag removes most of the "why is my test asserting on HTML failing" class of problem.

Route-level opt-out. When one page must keep its whitespace (an email preview, a <pre>-heavy docs page, a page whose markup a JS library parses positionally), use ->withoutMiddleware(...) on that route rather than disabling the package, and leave a comment saying why. Future-you will otherwise "clean up" the exclusion.

Order matters if anything else injects markup. Debugbar, Telescope, @livewireScripts, and any middleware that appends HTML should run before the minifier so their output is included. Appending to the web group puts the minifier at the end, which is what you want; if you instead prepend it, injected markup arrives after minification and the response is a mix of minified and raw HTML.

Cache the output, not the process. The minifier rewrites the body on every request. If you're already using response caching (spatie/laravel-responsecache or a CDN), minified output gets cached and re-served — good. Don't stack two minifiers (this package plus a server-level mod_pagespeed/nginx filter); the second pass is wasted work and occasionally mangles the first pass's output.

Test against real views, not Blade::render(). v2.1.1's own fix is instructive: the directive test was rewritten to render through an actual view file because Blade::render()'s component path unbalances the output buffer. Follow the same rule in your app — assert on the output of $this->get('/route'), which exercises the real middleware + view pipeline. For content assertions, use assertSee(..., escape: false) on distinctive text rather than comparing exact HTML strings; whitespace-insensitive assertions survive both config changes and the minifier's improvements.

Wire the package's new tooling into your own expectations. make infection (MSI floor 70) and composer audit are non-blocking gates in the package's CI, not yours — but they're a useful signal. If you fork or vendor the package, you now have a baseline mutation score to defend. If you merely consume it, the hardened command/provider test assertions mean a boot-time regression should be caught upstream before it reaches you.

Gotchas and Tips

Stale published config now fails boot — loudly, and that's the point. This is the headline of v2.1.1. If your published config/htmlmin.php still carries a key the installed engine dropped (e.g. after a major bump of the underlying minifier), the provider used to die with Unknown named parameter $camelCasedKey, which tells you nothing about which key or which file. It now throws an InvalidArgumentException naming the offending snake_case key, the file, and the fix.

  • The failure is at boot, so it takes down the whole app: web requests, queued workers, and php artisan alike. Encountering it in CI or on deploy is expected; encountering it in production means a config that was never re-published after an upgrade.
  • Fix: re-publish the config to a scratch path, diff it against yours, delete the retired key (or take the new default), and commit. Don't delete keys by guesswork — the exception names exactly one offending key per boot, so you may need to iterate if several went stale.
  • Habit to adopt: after any composer update that moves the minifier engine, run php artisan about (or config:cache in CI) to force a boot. That surfaces stale keys in a pipeline stage instead of a deploy.
  • Config keys are snake_case (do_remove_comments), the engine's constructor parameters are camelCase (doRemoveComments). The exception deliberately reports the snake_case form because that's what's literally in your file. When grepping, grep the snake_case spelling.

Verify the Content-Length header. A middleware that rewrites the body must not leave the original Content-Length in place, or clients truncate or hang. The package's guard should skip anything it doesn't touch, but if you see a response that renders partially in production and fine in local (where compression/buffering differs), check header-vs-body consistency before blaming the view. Also check ETag — a stale strong ETag on a rewritten body causes cache poisoning you'll debug for a day.

<pre>, <textarea>, <script>, and inline <code>. Whichever engine is underneath should preserve these, but "should" is doing work in that sentence. Run one page of each through it once and eyeball the result, particularly if your config disables whitespace-sensitivity heuristics. This is the single most common way HTML minification breaks a page, and it breaks silently — the markup is still valid, just wrong.

Skip non-HTML aggressively. Streamed responses (response()->stream()), BinaryFileResponse, SSE, and JSON should ideally never reach the minifier. If you're seeing corrupted downloads or a hung SSE connection, the middleware's content-type check is the first thing to read; the second is whether some earlier middleware set a misleading Content-Type.

Minification is not compression. It removes bytes by deleting whitespace/comments; it doesn't gzip. If you're chasing transfer size, enable gzip/brotli at the web server or Laravel's response compression — a minified-but-uncompressed HTML response is often larger over the wire than a raw-but-gzipped one. Do both; expect minification to win 5–15% and compression to win much more.

Don't assert on exact HTML in your own tests. If you have snapshot tests of rendered pages, the minifier makes them fragile across package upgrades. Normalize whitespace before comparing, or assert on parsed DOM (e.g. DOMDocument / assertSee), so a change in the engine's whitespace strategy doesn't fail a hundred unrelated tests. The v2.1.1 test fix is the mirror image of this lesson: test the production path (a real view + middleware + request), not a synthetic rendering shortcut.

Weaver

How can I help you explore Laravel packages today?

Conversation history is not saved when not logged in.
Prompt
Add packages to context
No packages found.
octoberrain/html
octoberrain/extension
octoberrain/exception
octoberrain/contracts
frankyso/ipaymu
3mdikram/worldmoneywords
snowsoft/config
itmosoft/laravel-subscriptions
larva/laravel-ai-qwen
diephp/sequences
users-au/socialite-provider
twidpay/twid-logger
mrnewport/laravel-plaid
mrnewport/laravel-repo
mrnewport/laravel-docsign
mrnewport/laravel-stow
mrnewport/laravel-flow
mrnewport/laravel-priceable
mrnewport/laravel-nestedterms
fredmeng/laravel-apidoc-generator