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 Minify Html Middleware Laravel Package

backstage/laravel-minify-html-middleware

A Laravel middleware that minifies HTML responses on the fly, trimming whitespace and redundant markup to shrink payloads and speed up page delivery without touching your views or blade templates.

View on GitHub
Deep Wiki
Context7

Getting Started

Install

composer require backstage/laravel-minify-html-middleware

Confirm what you actually installed

This is a small, low-traffic package (6 stars, 0 dependents), so spend the 90 seconds to read the source before wiring it up:

cat vendor/backstage/laravel-minify-html-middleware/composer.json   # shows the underlying minifier, if any
ls  vendor/backstage/laravel-minify-html-middleware/src
grep -rn "class .*Middleware" vendor/backstage/laravel-minify-html-middleware/src

The composer.json is the most useful file — it tells you whether HTML reduction is done by a regex pass in-house or delegated to a library like voku/html-min. That determines every gotcha in the last section.

Register the middleware

Laravel 11+/12, in bootstrap/app.php:

->withMiddleware(function (Middleware $middleware) {
    $middleware->web(append: [
        \Backstage\MinifyHtmlMiddleware\MinifyHtmlMiddleware::class,
    ]);
})

Laravel 10 and below, append to the web group in app/Http/Kernel.php:

protected $middlewareGroups = [
    'web' => [
        // ...
        \Backstage\MinifyHtmlMiddleware\MinifyHtmlMiddleware::class,
    ],
];

Use the web group, not a global append(). Global registration also hits API/JSON responses, download responses, and error pages, which is where this class of middleware usually breaks.

Verify it works

curl -s https://your-app.test/ | wc -c        # after
curl -s -H 'Accept-Encoding: identity' ...    # rule out gzip skewing the number

Then view-source: a page: you should see blank lines removed and indentation between tags collapsed, but <pre> blocks, <script> bodies, and <textarea> content intact. If those are intact, the package's "safe" claim is holding for your markup.

First use case

Public, Blade-rendered marketing pages behind web middleware. Leave it off in local — see the dev-tooling gotcha below.

Implementation Patterns

Scope it by group, gate it by config. The pragmatic setup is web-group registration plus an env/config switch, so a staging environment can toggle it without a deploy:

// config/view.php or a package config if one is publishable
'minify_html' => env('MINIFY_HTML', ! app()->isLocal()),

If the package ships a config file, publish it (php artisan vendor:publish --provider=... or --tag=...) and check for a skip-paths / enabled option before writing your own wrapper.

Understand where it sits in the pipeline. Appending to a middleware group places it innermost — it sees the controller's response first on the way out. That is usually what you want for HTML, but it means anything an outer middleware appends to the body afterwards (rare, but some header/footer injectors do this) won't be minified.

Skip the responses you can't safely touch. A defensive subclass is cheap insurance regardless of what the package claims:

public function handle($request, Closure $next)
{
    $response = $next($request);

    if (! $response instanceof \Illuminate\Http\Response) {
        return $response; // StreamedResponse, BinaryFileResponse, JsonResponse
    }

    if (! str_contains($response->headers->get('Content-Type', ''), 'text/html')) {
        return $response;
    }

    return parent::handle($request, fn () => $response);
}

Adapt to the actual signature — the point is the two guards: concrete Response instance, and text/html content type.

Per-route opt-out for pages where byte count doesn't matter and fidelity does (legal pages with odd markup, print stylesheets, anything with <pre>-heavy content):

Route::get('/press-kit', ...)->withoutMiddleware(MinifyHtmlMiddleware::class);

Inertia / Livewire / Alpine are fine. Inertia only minifies the initial HTML shell (subsequent navigation is JSON). Livewire's DOM updates are JSON payloads, not HTML responses, so they bypass this entirely. Alpine expressions live in attribute values, which a correct HTML minifier does not rewrite.

Cache the minified output, not the source. If you full-page cache, cache after this middleware has run, otherwise you pay the CPU cost on every hit and get no benefit.

Testing. Whitespace-sensitive assertions will now fail. Prefer structural assertions (assertSee('Total: 42', false) with false for escaping, or DOM queries) over comparing raw HTML strings, and don't assert on pretty-printed markup anywhere.

Gotchas and Tips

Whitespace-sensitive elements are the whole ballgame. <pre>, <textarea>, <script>, <style>, and inline <code> blocks must survive byte-for-byte. Test explicitly with a page containing each. If the package is regex-based rather than DOM-based, this is where it fails first — and it fails silently in production, not in tests.

Inline JS is the second failure mode. If the underlying minifier touches <script> contents, code relying on ASI (no trailing semicolons) or // comments will break. Confirm <script> bodies are untouched; if not, exclude the paths that carry inline JS or wrap them in <script type="text/plain">-style opt-outs if the minifier honors them.

Never touch Content-Length. HTML responses from Laravel normally don't set it, but if something upstream does, the header and body will disagree after minification. The instanceof Illuminate\Http\Response guard above prevents the worst of this; adding $response->headers->remove('Content-Length') is a belt-and-braces move.

Attribute values must not be rewritten. CSRF hidden inputs, signed URLs with &amp;, x-data expressions, and data-* payloads all live in attributes. A minifier that unquotes attributes or collapses internal whitespace will break form submissions and signed route validation in ways your test suite won't catch. Manual check: log in, submit a form with @csrf, click a signed URL.

Turn it off in local. Laravel Debugbar, Telescope, and Vite's HMR client all inject markup into the response, and minifying it makes debugging genuinely unpleasant — stack traces point at line 1 of a single-line document. ! app()->isLocal() in the enable flag, or ->withoutMiddleware() on local-only debug routes.

Error pages in debug mode. When APP_DEBUG=true, an exception renders a large HTML page. If the middleware is in the web group, it may still run on it. Usually harmless, occasionally the source of a confusing "the error page itself 500s" report. Keeping it disabled locally sidesteps this.

ETag/Last-Modified ordering. Minification is deterministic, so a body-derived ETag stays stable across requests. It only breaks if the ETag is computed from the pre-minified body by an outer middleware. If you see spurious cache misses after enabling, check the order.

Measure the cost on your heaviest page. For a 200KB HTML document, expect single-digit milliseconds. It's negligible at normal traffic, but it is per-request CPU — if you're already fighting response time on a high-traffic page and not caching it, this adds to the pile for a bandwidth win that gzip already largely delivered.

Failure must degrade, not break. If the minifier throws on unusual markup, every page returns 500. Wrap the minification in try/catch and return the original response on failure (and log once). For a package with this little production exposure, that's the difference between a cosmetic feature and an outage.

Weigh build-vs-buy. The full behavior here is plausibly 30 lines of middleware around a well-known minifier. Given 6 stars and 0 dependents, pin the exact version in composer.json, and if the source turns out to be trivially small, consider vendoring the middleware into app/Http/Middleware — you keep the behavior, drop a supply-chain dependency, and gain the extension points the package may not offer.

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.
middlewares/firewall
qubiqx/montapacking
nyoncode/laravel-package-console
pnscripts/filament-broadquill-lite
pnscripts/laravel-sentfold-lite
vleap/mx-sdk-laravel-internal
twidpay/utils
htetmyathlaing/scout-elasticsearch-driver
artisan-toolbox/front-awesome
ls/client-assistant
users-au/laravel-client
cgv/localized-resource
noith/settings
kda/laravel-package
kda/filament-dynamic-navigation
bypersoft/signbox
pairus/product-data
fayne/laravel-filament-cms-core
nodisrupt/laravel-agent
sierratecnologia/media-manager