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

Htmlmin Laravel Package

graham-campbell/htmlmin

Laravel wrapper around HTMLMin that compresses your app's HTML output on the fly, stripping comments and unnecessary whitespace. Integrates via service provider and facade, and can be applied globally or to specific responses to shrink page size and speed up delivery.

View on GitHub
Deep Wiki
Context7

Getting Started

1. Verify the runtime floor before you install. v9 is a major line. Check the require block of the tag you are pulling against your PHP/Laravel version first — a caret range here will happily drag you across a major boundary later.

composer require graham-campbell/htmlmin:9.0.1   # pin through the validation window
php artisan vendor:publish --provider="GrahamCampbell\HTMLMin\HTMLMinServiceProvider"

Auto-discovery registers the service provider; nothing else is required for the package to exist in the container. Note that the package's core integration is a Blade compiler swap, not a naive output-buffer middleware — the service provider replaces Laravel's BladeCompiler with a minifying subclass so compiled views render compressed. v9.0.1's fix ("avoid resolving view when retrieving the previous compiler") lives exactly there. That is the seam you own when you upgrade Laravel majors.

2. Where to look first, in this order:

  • config/htmlmin.php — the published config is the kill switch and the ignore list. Read every key before writing any code.
  • src/HTMLMinServiceProvider.php — confirms which class it binds, which middleware alias it registers, and whether it touches the Blade compiler in your installed version.
  • The Minifiers namespace — the actual mrclay/minify wrappers you can call directly.

3. Scope it, don't globalize it. Register the middleware under a named alias so it can be attached per route group. On Laravel 11/12:

// bootstrap/app.php
->withMiddleware(function (Middleware $middleware) {
    $middleware->alias([
        'htmlmin' => \GrahamCampbell\HTMLMin\Http\Middleware\MinifyMiddleware::class,
    ]);
})

On Laravel 10 and below, add the same alias to $routeMiddleware in app/Http/Kernel.php. Confirm the exact class name in the service provider — if the alias differs, use theirs rather than inventing your own.

4. First use case: one marketing route group.

Route::middleware('htmlmin')->group(function () {
    Route::get('/', HomeController::class);
    Route::get('/pricing', [MarketingController::class, 'pricing']);
});

Ship that, diff the HTML, then decide whether widening it is worth it. Do not start with $middleware->append(...) globally — you will spend the first day discovering which internal tool routes you just broke.

5. Keep a one-line escape hatch. If the published config has no env hook, add one:

'enabled' => env('HTMLMIN_ENABLED', true),

Then a broken layout in production is HTMLMIN_ENABLED=false + php artisan config:clear, not a rollback.

Implementation Patterns

Route-group scoping as the default posture. Treat "which HTML surfaces get minified" as a product decision expressed in routes/web.php, not as a global framework toggle. A common shape:

// minified
Route::middleware('htmlmin')->group(base_path('routes/marketing.php'));

// never minified: dashboards, tooling, anything with code samples
Route::middleware('auth')->group(base_path('routes/app.php'));

Programmatic minification for non-HTTP surfaces. The middleware only helps rendered responses. When you generate HTML outside the request/response cycle — queued mail, a nightly sitemap/feed, a PDF template, an export — call the minifier directly:

$html = view('mail.receipt', compact('order'))->render();
$html = app(\GrahamCampbell\HTMLMin\Minifiers\HtmlMinifier::class)->render($html);

(Confirm the class name and method signature in src/Minifiers for your tag — the wrappers are thin passthroughs to mrclay/minify.) Wrap this in your own Minify::html() helper so the call site is one function and swapping the library later is a one-file change.

Gate it per environment, not per deploy. Enable in local and CI so every developer sees minified output every day, and so your HTML-diff assertions actually exercise the minifier. If minification is only on in production, you have a code path nobody tests.

Baseline-and-diff as the integration test. The cheapest reliable regression gate:

it('renders marketing pages without whitespace corruption', function (string $uri) {
    $with = $this->get($uri)->getContent();
    config(['htmlmin.enabled' => false]);
    $without = $this->get($uri)->getContent();

    expect(normalize($with))->toBe(normalize($without));
})->with(['/', '/pricing', '/blog/sample-post']);

Normalize whitespace between tags only — not inside <pre>, <textarea>, or attribute values. That single assertion catches the failure mode this package is most likely to produce.

Feature-flag the rollout, then measure. Log strlen($response->getContent()) before and after for one release cycle on the canary group. If the delta is under a few percent (typical when nginx or a CDN already compresses), the honest move is to remove the dependency rather than carry a request-path component for nothing.

Compose with response caching, don't fight it. Minify on the way into the cache, so cached responses are already small and the CPU cost is paid once per cache fill rather than per request.

Blade-level minification for inline fragments. If the package exposes a Blade directive for minifying a specific block (per the published docs), use it inside layout files for repeated header/footer partials where the compiler swap is being bypassed by @verbatim or a custom compiler.

Gotchas and Tips

Whitespace-sensitive content is the whole risk. <pre>, <textarea>, code sample blocks, and any content where a collapsed newline changes meaning. mrclay/minify is well-tested but it is operating on serialized markup it did not author. Put every page with code samples or rich text on an explicit exclusion list before the canary, not after someone files a bug.

Inline <script> and CSP nonces. Minifying script bodies can break nonced inline scripts, comment-dependent code (//# sourceMappingURL, conditional-compilation comments), and anything where a template literal contains meaningful whitespace. If your CSP uses nonces, diff a page with inline JS byte-for-byte, not just visually.

Alpine, Vue, HTMX, and Livewire all live in attributes and text nodes. x-data="{ open: false }" collapsing to x-data="{open:false}" is fine; a mustache inside a text node losing surrounding whitespace so Hello {{ $name }} becomes Hello{{ $name }} is not. Livewire is doubly risky — it patches the DOM against server-rendered HTML, and a DOM that no longer matches what the server sent produces hydration mismatches with no server-side error. Exclude Livewire routes from the canary.

Silent failure is the default. A minifier that mangles markup raises nothing and logs nothing. Your only signal is visual. Build the runbook around "page X looks wrong" → flip config → config:clear → investigate offline.

Clear the right caches. The Blade compiler swap means compiled views change, so php artisan view:clear after installing, upgrading, or toggling — config:clear alone will leave stale compiled templates and a confusing result where the flag appears not to work.

Check double minification. If nginx (gzip/brotli + any HTML filter), Cloudflare, or a Varnish layer already rewrites HTML, you are paying CPU twice and compounding edge-case risk. Confirm what the edge does before enabling this, and never stack two HTML-rewriting middlewares.

Streaming and non-HTML responses. Middleware that buffers the response to inspect it will fight StreamedResponse, chunked downloads, and SSE. Keep it off any route that streams, and verify it respects Content-Type — a JSON API returning a stray HTML error page should not traverse it. Also watch Content-Length: if something upstream set it and the body got shorter, downstream clients can hang.

Tooling routes. /horizon, /telescope, /livewire/*, _debugbar, and Sanctum/Passport endpoints all render HTML that fragile tooling depends on. Exclude them explicitly rather than discovering it during an incident.

Don't assert exact HTML strings in existing tests. Any assertSee/assertDontSee on whitespace, or snapshot tests of rendered markup, will flake the moment you enable this. Either scope the middleware away from those tests or normalize whitespace in the assertion helper.

Upgrade cadence: this package's blast radius is Laravel's internals. Because it subclasses the Blade compiler, every Laravel major is a potential silent break. Add "re-run the curated page diff" as a mandatory line item in your framework upgrade runbook. Budget it as an hour, not a project — but do not skip it.

Pin the version and own the contingency. Zero dependents and a small footprint mean you are the validating adopter. Pin 9.0.1, watch release cadence, and if the project goes quiet, the replacement is a ~40-line middleware wrapping mrclay/minify yourself — which is exactly why it's worth writing the programmatic call behind your own helper from day one.

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.
se7enxweb/flysystem
lampwick/laravel
jeslage/statamic-emojis
micahdshackelford/rmap-laravel
draftsmaninc/draftsman
square-bit/laravel-volition
himito/laravel-mailman
htinlynn/genius-helper
maco-studios/hephaestus
elmogy/larafast
xeviant/laravel-paystack
gorilladash/laravel-website-sdk
martialbe/flysystem-oss
chondal/3312client
jeroen-g/flickr
opscale-co/nova-genbi
itsjustvita/laravel-retention
tequilarapido/trackit
gregsphotography/site-builder-filament
cabaoctavio/laravel-query-log