laravel-responsecache: a week-long default, a 10 minute example, and two links out of the repo
Speed up a Laravel app by caching the entire response
At a glance
- What is it?
- A Laravel package that caches whole responses behind route middleware, with a write-through first request and a second middleware that serves stale entries while refreshing them. It ships no install command, defaults to a seven day lifetime that its own example immediately overrides with ten minutes, and two of its links climb out of the repository into directories that only exist in the maintainers' monorepo.
- Who is it for?
- Use this when a route returns the same text to everyone for a measurable period and you can accept a page that is seconds or hours behind. Read the default lifetime before you attach the middleware, because the shipped default is a week and the example on the page sets ten minutes, so the default is 1,008 times the example lifetime.
- Can I use it commercially?
- Yes. MIT is a permissive licence: you can use, modify and sell software built on it, as long as you keep its copyright and licence notices.
- Is it still maintained?
- Yes. The repository last received commits 1 day ago.
- What is it written in?
- Mainly PHP, according to GitHub's language statistics.
Answers come from the project's GitHub data, last synced on November 14, 2026, and from our analysis. They are not legal advice.
Star history
- spatie/laravel-responsecache2,826 ★ · +10
| Repository | 2026-09-30 | 2026-11-14 |
|---|---|---|
| spatie/laravel-responsecache | 2.8k | 2.8k |
10 Stars in the last 45 days, measured from the daily GitHub snapshot. Compare with other projects
Editorial analysis
The first request is never faster, it only writes the cache
The caching behaviour is described in one sentence that decides how the package feels in production:
> The first time a request comes in, the package will save the response before sending it to the user. When the same request comes in again, the cached response is returned without going through the entire application.
So this is write-through, not cache-aside. The first request runs your whole application and additionally writes the response, and the saving only starts from the second identical request.
Two consequences follow. A cold cache produces no speedup at all, so a freshly deployed instance is exactly as slow as an uncached one plus the write cost. And the write sits on the request path, because it happens before the response is sent rather than after, so the first visitor pays for both the computation and the cache population.
The upside is that there is no background warmer and no priming step. The first person to hit a route populates it for everyone behind them, which for a page with uneven traffic is a reasonable bargain and for a burst of unique URLs is pure overhead.
The default is a week and the example sets ten minutes
Four conditions stack in the default, and they are easy to read past:
> By default it will cache all successful GET requests that return text based content (such as HTML and JSON) for a week.
Successful only, so an error response is never stored. GET only, so a POST is never stored. Text-based content only, which excludes anything the package does not recognise as HTML or JSON. And a lifetime of seven days.
Now compare that with the example the page offers:
use Spatie\ResponseCache\Middlewares\CacheResponse;
Route::middleware(CacheResponse::for(minutes(10)))->group(function () {
Route::get('/posts', [PostController::class, 'index']);
Route::get('/posts/{post}', [PostController::class, 'show']);
});Ten minutes, against a seven day default. A reader who copies the snippet and never reads the sentence above it ends up with a cache a thousand times shorter than the one they would have got by attaching nothing and relying on defaults.
It is also opt-in per route. The middleware is attached to a route group, so until you add it no response is cached, and the seven day figure describes what you get when you do add it without specifying a lifetime.
Flexible caching serves stale content while it recalculates
The second middleware changes the expiry behaviour rather than the lifetime:
> For pages where brief staleness is acceptable, you can use flexible caching. After the lifetime expires, the stale response is still served instantly while the cache refreshes in the background. Once the grace period is over, the cache is considered expired and the next request will be fully recalculated.
use Spatie\ResponseCache\Middlewares\FlexibleCacheResponse;
Route::get('/dashboard', [DashboardController::class, 'index'])
->middleware(FlexibleCacheResponse::for(lifetime: hours(1), grace: minutes(5)));Two parameters with distinct jobs. The lifetime is when background refresh begins. The grace period is how long the stale entry keeps being served while that happens. Only after both have elapsed is the entry treated as expired and the next request pays full cost.
One detail worth noticing is that the call uses PHP named arguments, `lifetime:` and `grace:`. Those parameter names are therefore part of the public interface of the package, and renaming either one would break every caller who wrote it this way rather than positionally.
The precondition is stated plainly as brief staleness being acceptable, which is a reasonable assumption for the dashboard in the example and a wrong one for anything showing a balance or a confirmation.
Two links climb two directories out of the repository
Two links in the page resolve outside the repository:
Please review [our security policy](../../security/policy) on how to report security vulnerabilities.- [All Contributors](../../contributors)Both use `../../`, so both expect a parent directory that contains a `security/` folder and a `contributors` entry. Those siblings exist in the maintainers' development layout, where packages are held together and split out afterwards. In the standalone `spatie/laravel-responsecache` repository they are not there, so both links are dead as published.
The cause is that the README is generated from a template rather than written for the split repository. It shows in the handling: the contributing link does the right thing by pointing at an absolute address in a separate `spatie/.github` repository, while the security and contributor links kept the relative form.
Of the two, the security one matters. A reader who wants to report a vulnerability is the person least likely to work around a broken link, and the page offers no alternative address for it.
One of the two workflow badges is named like a commit message
The header carries two workflow badges, and both link to filtered views of the Actions tab on the main branch. One filters on a workflow called `run-tests`. The other filters on a workflow whose name is a quoted imperative phrase, `Fix PHP code style issues`, embedded in the query string.
A CI workflow named as an instruction rather than as a job description is unusual. Test runners are conventionally called after what they do, and this one is named after a corrective action, which reads more like a commit subject or the title of an automation that opens a style-fix run than a pipeline.
Only those two appear. There is no badge for a build, a static analysis job, or a release, even though the repository root contains `phpstan.neon.dist`, so static analysis is configured somewhere in the project even if the page does not show it running in CI.
The `.github/` directory is present at the root, so the workflows themselves are in the repository. What is not visible from the page is how many there are and which of them gate a merge.
Sixteen seconds between the tag and the last push
Three releases are listed, all patch-level bumps within the same minor:
Version 8.4.3 on 2026-08-07, 8.4.4 on 2026-09-25, and 8.4.5 on 2026-09-29. The gap from 8.4.3 to 8.4.4 is seven weeks, and from 8.4.4 to 8.4.5 it is four days.
The timestamps line up unusually precisely. Version 8.4.5 was published at 11:24:03 UTC and the last push to the main branch is stamped 11:24:19 UTC, sixteen seconds later. So the tag was cut from the tip of the branch essentially immediately, with no drift between the released artefact and the committed state.
That is the pattern to look for in a package, and it is worth noticing precisely because it is not universal. A release tagged months after the last commit means the published version and the branch have diverged, and the tag number tells you nothing about which commits are in it.
Two supporting details sit alongside. An `UPGRADING.md` at the root carries the major-version upgrade path, which matters on a package sitting at major version 8 with no feature or breaking release in the visible window. And the repository's homepage address pins a version, ending in `/v8/introduction`, while the documentation link inside the page carries no version at all, so the two disagree about whether you are reading current or latest.
No install command, and a maintainer blog screenshot at the root
There is no installation command anywhere on the page. Not a composer require line, not a publish step, not a facade registration. The heading for getting the package simply links out to the documentation site, so the README cannot be followed from start to finish to get this running.
The only command it does give is for the tests:
composer testEverything else is off-site. That is a deliberate division for this publisher, whose documentation is versioned separately and whose packages are developed in a shared tree, but it does mean the page in the repository is an overview rather than a guide.
The root also carries a few files that are not part of the package. There is a PNG named after the maintainer's first name, sitting beside an `art/` directory for artwork. The licence file is `LICENSE.md`, carrying a markdown extension that is unusual for a licence. A `docs/` directory holds documentation prose in the repository as well as on the site, so the same words exist in two places and can drift.
The tooling configuration is ordinary and visible: `phpstan.neon.dist` for static analysis and `phpunit.xml.dist` for the test runner, both distributed rather than dotfile-named, alongside an `.editorconfig` and a `.gitattributes`.
Editorial conclusion
Use this when a route returns the same text to everyone for a measurable period and you can accept a page that is seconds or hours behind. Read the default lifetime before you attach the middleware, because the shipped default is a week and the example on the page sets ten minutes, so the default is 1,008 times the example lifetime. Choose the lifetime and invalidation policy according to your freshness requirements: flexible caching serves stale responses during its grace period and is appropriate only when that staleness is acceptable. Nothing is cached until you attach middleware, so an unwired route behaves exactly as it did before.
Frequently asked questions
What does spatie/laravel-responsecache cache by default?
All successful GET requests that return text based content, such as HTML and JSON, for a week. So the default is narrow on method and status and broad on lifetime.
How do I set a shorter cache lifetime in laravel-responsecache?
Attach the CacheResponse middleware to a route group with a lifetime, as in the page's example using `CacheResponse::for(minutes(10))`. Nothing is cached until you attach the middleware, so the route behaves as before until then.
What is flexible caching in laravel-responsecache?
A second middleware, FlexibleCacheResponse. After the lifetime expires the stale response is still served instantly while the cache refreshes in the background, and once the grace period is over the cache is considered expired and the next request is fully recalculated.
How does the first request fill the laravel-responsecache cache?
The package saves the response before sending it to the user. So the first request runs the whole application and writes the cache on the way past, and only the second identical request is served from cache.
How do I report a security issue in laravel-responsecache?
The page points at a security policy through a relative link that climbs two directories, to ../../security/policy. That path only resolves in the maintainers' shared development tree, so in the standalone repository the reporting route has to be found on the documentation site instead.
Official sources
Add this badge to your README
If you maintain this project, the badge below links readers to this analysis and shows its maintenance status from the daily GitHub snapshot. Paste the markdown into your README; add ?metric=license or ?metric=stars to the image URL for a different field.
[](https://hysenlabs.com/projects/spatie-laravel-responsecache)Star history chart for your README
[](https://hysenlabs.com/projects/spatie-laravel-responsecache)Was this useful?