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 Page Speed Laravel Package

vinkius-labs/laravel-page-speed

Laravel Page Speed is an end-to-end optimization pipeline for Blade-rendered pages and REST APIs, with composable middleware, measurable latency and bandwidth gains, store-agnostic caching, and built-in observability and resilience for Laravel 10–13.

View on GitHub
Deep Wiki
Context7

Getting Started

Minimum viable integration (5 minutes)

  1. Install and publish the config:
composer require vinkius-labs/laravel-page-speed
php artisan vendor:publish --provider="VinkiusLabs\\LaravelPageSpeed\\ServiceProvider"
  1. Enable it explicitly — the middleware is a no-op when disabled, so this is the first thing to check when "nothing happens":
LARAVEL_PAGE_SPEED_ENABLE=true

Clear config cache after touching .env (php artisan config:clear) — the package reads runtime config, and a stale bootstrap/cache/config.php is the single most common reason toggling env vars appears to do nothing.

  1. Register one middleware first. Do not paste the whole stack in on day one. Start with the safest, highest-signal pair on the web group.
  • Laravel 10 (app/Http/Kernel.php):
'web' => [
    // ...
    \VinkiusLabs\LaravelPageSpeed\Middleware\CollapseWhitespace::class,
    \VinkiusLabs\LaravelPageSpeed\Middleware\RemoveComments::class,
],
  • Laravel 11–13 (bootstrap/app.php):
->withMiddleware(function (Middleware $middleware) {
    $middleware->appendToGroup('web', [
        \VinkiusLabs\LaravelPageSpeed\Middleware\CollapseWhitespace::class,
        \VinkiusLabs\LaravelPageSpeed\Middleware\RemoveComments::class,
    ]);
})

Where to look first

  • docs/CONFIGURATION.md — the authoritative list of keys, defaults, and the skip patterns.
  • docs/WEB-OPTIMIZATION.md — behavior of each HTML middleware and the data-ps-* guard flags.
  • docs/API-CACHE.md — tag derivation and invalidation rules; read this before enabling ApiResponseCache.
  • config/laravel-page-speed.php after publishing — the only place worth editing.

First real use case

A Blade-heavy marketing or admin app where view-source shows 40% whitespace and comment bloat. Enable CollapseWhitespace + RemoveComments, load a page, and diff response size in the Network tab. Then add InlineCss for above-the-fold CSS and DeferJavascript for script-blocking issues. Verify by inspecting X-Response-Time / X-Memory-Usage headers, which the base middleware emits in debug mode.

Implementation Patterns

Staged rollout by group, not globally. The package is composable by design — resist $middleware->append(...) on the global stack. Attach HTML middleware to web and API middleware to api. This is not cosmetic: the base PageSpeed class now has a Content-Type guard that skips non-HTML responses, but keeping the groups separate means a misconfigured route can't accidentally run HTML minification over a JSON payload.

Per-route opt-in for sensitive surfaces. Rather than a blanket web group entry, scope the aggressive ones (e.g. InlineCss, DeferJavascript) to public routes:

Route::middleware([
    \VinkiusLabs\LaravelPageSpeed\Middleware\InlineCss::class,
    \VinkiusLabs\LaravelPageSpeed\Middleware\DeferJavascript::class,
])->group(function () {
    Route::get('/', HomeController::class);
    Route::get('/pricing', PricingController::class);
});

Keep TrimUrls and ElideAttributes off admin or Livewire-driven forms until you've tested them — ElideAttributes strips disabled/selected markers and TrimUrls rewrites src/href/action.

API stack layering. Order matters more than presence. A working pattern:

$middleware->appendToGroup('api', [
    \VinkiusLabs\LaravelPageSpeed\Middleware\ApiHealthCheck::class,       // cheapest, bails first
    \VinkiusLabs\LaravelPageSpeed\Middleware\ApiSecurityHeaders::class,
    \VinkiusLabs\LaravelPageSpeed\Middleware\ApiCircuitBreaker::class,    // fail fast before cache
    \VinkiusLabs\LaravelPageSpeed\Middleware\ApiResponseCache::class,     // hit short-circuits below
    \VinkiusLabs\LaravelPageSpeed\Middleware\ApiETag::class,
    \VinkiusLabs\LaravelPageSpeed\Middleware\ApiResponseCompression::class,
    \VinkiusLabs\LaravelPageSpeed\Middleware\ApiPerformanceHeaders::class, // innermost, measures real work
]);

Placing ApiResponseCache before ApiPerformanceHeaders means a cache hit reports near-zero work time, which is what you want in the metrics, but it also means X-Performance-Warning won't fire on cached responses — by design.

Cache tagging strategy. API_CACHE_DYNAMIC_TAGS=true derives tags from path segments, so /api/v1/products/42/reviews becomes taggable as products or v1 depending on your config. Two practical uses:

  • Invalidate an entire resource family from a model observer after a write (products.*).
  • Namespace by tenant if the URL carries it (/api/{tenant}/...) so a tenant purge doesn't nuke everyone.

Remember tags need a taggable driver — Redis, Memcached, DynamoDB. File and array drivers silently fall back to flat keys, so dynamic tags are inert there.

Method-aware invalidation is not automatic. ApiResponseCache invalidates on write verbs, but if your app mutates via POST /api/actions/reindex, add an explicit Cache::tags([...])->flush() in the controller rather than trusting the heuristic.

Circuit breaker scoping. Set scope to route rather than global for third-party integrations (payments, shipping) so one flaky dependency doesn't trip a site-wide fallback. Configure the fallback response in config/laravel-page-speed.php; keep it a valid payload shape so clients don't blow up on the degraded path.

Health checks for orchestrators. Point your Kubernetes livenessProbe/readinessProbe at a route with ApiHealthCheck. Probe results are cached for ~10s by default — that's the right tradeoff for a 1s probe interval, but it means a failing database shows ok (cached) for up to ten seconds after the real state changes. Don't use this as your only alerting signal.

Observability ingestion. X-Response-Time, X-Memory-Usage, X-Cache-Status, X-Circuit-Breaker-State are plain strings on the response — log them in a terminable middleware or a log channel formatter rather than scraping them from APM dashboards:

public function terminate($request, $response): void
{
    Log::channel('perf')->info('response', [
        'path' => $request->path(),
        'time' => $response->headers->get('X-Response-Time'),
        'cache' => $response->headers->get('X-Cache-Status'),
    ]);
}

Testing pattern. Assert headers in feature tests — it's the cheapest regression net:

$this->getJson('/api/v1/products')
    ->assertHeader('X-Circuit-Breaker-State', 'closed')
    ->assertHeader('Content-Encoding'); // present only when payload exceeded threshold

Also assert your HTML response body byte size drops between a withMiddleware and a withoutMiddleware test to catch a silently disabled package.

Environment differentiation. Enable the full HTML stack in staging, then promote. Enable API_CACHE_ENABLED=true only after you've confirmed your write paths invalidate correctly — a stale API cache is far more damaging than an unoptimized one.

Gotchas and Tips

The skip list is your first debugging tool. Debugbar, Telescope, Horizon, and diagnostic routes are skipped by default, but custom debug routes need explicit entries in the config's skip array. If a page suddenly minifies your Debugbar output, that's the fix.

Persistent workers (Octane, Swoole) are the classic failure mode. Instance properties on middleware used to leak across requests — InlineCss was resetting $class/$style/$inline only as of 4.4.1, and a stale static enable cache was removed in the same release. Pin to >= 4.4.1 if you run Octane. Also be aware InlineCss maintains a static $uniqueCounter in long-running processes; it's documented, but don't write tests that assert on that counter's value.

Livewire and Alpine.js are explicitly preserved — wire:snapshot, wire:effects, x-data, x-init survive CollapseWhitespace as of 4.4.2. Custom data-* attributes with significant whitespace are not preserved; if you have an Alpine component relying on an unusual attribute format, add it to your own preservation config or exclude the route.

Non-HTML responses are now skipped by the web middleware. This is a fix, not a limitation — but it means if you put a JSON-returning endpoint in the web group expecting minification, you'll get nothing. Use the api stack.

ApiETag deliberately no longer overwrites Cache-Control. If you have application-level cache headers, they win. If you need the ETag to drive a long max-age and you're getting short defaults, set the header yourself in a response macro or a controller middleware that runs before ApiETag.

Circuit breaker state must share the cache driver. As of 4.4.3 it respects API_CACHE_DRIVER rather than the default driver. In a load-balanced deployment, set API_CACHE_DRIVER=redis (or another shared store) — otherwise each node tracks independent failure counts and a tripped breaker on node A is invisible to node B. file and array drivers make the breaker effectively per-request.

X-XSS-Protection is now 0. Intended, per MDN guidance — CSP does the real work. If a security scanner or an internal compliance check flags it, point at the CSP header rather than reverting; the old 1; mode=block was itself a vulnerability vector in some browsers.

Regex minification can still fail on pathological HTML. 4.4.2 added the s PCRE modifier and a fallback to original content on failure, so a broken response should be impossible. But if you see a page whose HTML is untouched while its neighbors are minified, look for deeply nested template literals inside <script> blocks — that's the remaining stress case.

RemoveComments::removeSingleLineCommentFromLine() is deprecated. If you've subclassed or called into it, migrate to removeSingleLineCommentsFromContent(), which handles multi-line template literals and complex regex literals correctly.

Cache the things you'd never cache by hand. ApiResponseCache doesn't inspect auth state. Never attach it to a group containing auth:sanctum routes returning user-specific data unless your routes are properly Vary-keyed by the session. Treat the middleware as a blunt instrument for public, idempotent, GET-only resources.

Performance header warnings can now co-occur. Prior to 4.4.3 the second warning overwrote the first; now high-query-count and slow-request warnings are combined into a single header. Don't parse X-Performance-Warning assuming a single token.

Compression thresholds are your throttle. Small JSON payloads don't benefit from Brotli and pay the CPU cost. Tune the size threshold up to ~1 KB in the config; the default is conservative but still worth measuring against your p95 payload size.

Health check queue probing was a false positive until 4.4.3 — it reported ok without touching the driver. If you're below that version and relying on it, treat the queue status as decorative.

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.
thuraaung2493/laravel-rulecraft
cyberwizard/schedule-catchup
livetennisapi/livetennisapi-laravel
roundly-consulting/notifications-for-laravel
flightsadmin/bootstrap-starter-kit
lasserafn/laravel-intempus
likyly/sdk
tdt/input
coreation/php-shapefile
coreation/gisconverter
primix/widgets
primix/notifications
primix/actions
primix/tables
primix/details
primix/forms
primix/support
marcelgwerder/laravel-api-handler
firebrikdotcom/conveyor-laravel-broadcaster
kanata-php/conveyor-laravel-broadcaster