nocache-dev / laravel
Correct Cache-Control headers for Laravel responses, plus artisan commands to check URLs and manage nocache.dev monitors.
Requires
- php: ^8.1
- guzzlehttp/guzzle: ^7.5
- illuminate/console: ^10.0|^11.0|^12.0|^13.0
- illuminate/http: ^10.0|^11.0|^12.0|^13.0
- illuminate/support: ^10.0|^11.0|^12.0|^13.0
Requires (Dev)
- orchestra/testbench: ^8.0|^9.0|^10.0|^11.0
- phpunit/phpunit: ^10.5|^11.0|^12.0
Suggests
None
Provides
None
Conflicts
None
Replaces
None
This package is not auto-updated.
Last update: 2026-11-03 13:25:40 UTC
README
CDN-friendly Cache-Control for Laravel, and artisan commands for nocache.dev checks and monitors. Laravel 10–13, PHP 8.1+ (Laravel 13 needs PHP 8.3+).
composer require nocache-dev/laravel
Cache headers
Put cacheable pages in a route group without sessions. The default web group runs StartSession, which sets a session cookie on every response; a page that carries a session cookie must never be stored by a CDN, or one visitor's page (CSRF token, flash messages, "logged in as …") can be served to another.
// routes/web.php — keep these out of the `web` middleware group Route::withoutMiddleware('web')->middleware('nocache.headers')->group(function () { Route::get('/', HomeController::class); // edge TTL 300 s Route::get('/blog/{post}', ShowPost::class)->middleware('nocache.headers:900'); // edge TTL 900 s });
Or register a dedicated file in bootstrap/app.php (Laravel 11+):
->withRouting(web: __DIR__.'/../routes/web.php', then: function () { Route::middleware('nocache.headers')->group(base_path('routes/public.php')); })
Anonymous GET responses get:
Cache-Control: public, max-age=0, s-maxage=300
CDN-Cache-Control: max-age=300, stale-while-revalidate=60
Browsers always revalidate; shared caches keep the page for 300 s. stale-while-revalidate lives only in CDN-Cache-Control, because s-maxage implies proxy-revalidate (RFC 9111) and Cloudflare will not serve stale content when the two are combined. Cloudflare uses CDN-Cache-Control instead of Cache-Control for its own caching decisions, so SWR works there. (On Cloudflare, HTML is only cached if a Cache Rule makes it eligible.)
The middleware sends private, no-store (plus Vary: Cookie) and no CDN-Cache-Control when:
- a user is signed in,
- a session was started for the request (route still in the
webgroup), or - the response sets a cookie.
This also overrides a public/s-maxage policy you set yourself in those cases. Any other Cache-Control you set yourself is kept.
Change the values with php artisan vendor:publish --tag=nocache-config (policy.html, policy.cdn — set null to skip CDN-Cache-Control — and policy.private).
Artisan
php artisan nocache:check https://example.com --min-grade=B NOCACHE_TOKEN=nc_... php artisan nocache:monitor https://example.com php artisan nocache:monitor --list
Config: NOCACHE_TOKEN, NOCACHE_API, NOCACHE_MONITOR_URLS (comma-separated).
Changelog
- 1.1.0 — Laravel 13 support. Started sessions now force
private, no-store. Default policy no longer combiness-maxagewithstale-while-revalidate; SWR moved toCDN-Cache-Control. Private policy is nowprivate, no-store(wasprivate, no-cache).