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

Blade Cache Directive Laravel Package

ryangjchandler/blade-cache-directive

A lightweight Laravel package that adds an @cache Blade directive, letting you cache rendered fragments of views—partials, loops, or expensive blocks—with a TTL and optional key. Drop it into existing templates to cut rendering time without extra setup.

View on GitHub
Deep Wiki
Context7

Getting Started

Install via Composer — the service provider is auto-discovered, so there is nothing to register:

composer require ryangjchandler/blade-cache-directive

As of v1.1.1 the package declares Laravel 13.x compatibility, so the constraint resolves cleanly on Laravel 10/11/12/13. If you are pinned to an older release, bump to ^1.1.1 before upgrading your app skeleton — earlier tags will fail composer update on a Laravel 13 host.

Where to look first:

  1. config/cache.php — the directive uses your default cache store, so the driver (file, redis, database, array) determines persistence and invalidation behaviour.
  2. The compiled Blade output — run php artisan view:clear then load a page and inspect storage/framework/views/*.php to see exactly what @cache compiles into. This is the fastest way to understand the semantics.

First use case — wrap an expensive fragment (a menu query, a computed widget, a slow relationship loop):

@cache('header-navigation')
    @foreach (app(Navigation::class)->tree() as $item)
        <a href="{{ $item->url }}">{{ $item->label }}</a>
    @endforeach
@endcache

The second argument is a TTL in seconds. Omit it and the fragment is cached indefinitely until you clear it:

@cache('homepage.stats', 300)
    {{-- refreshed every 5 minutes --}}
@endcache

Implementation Patterns

Dynamic keys. The key is a normal PHP expression, so build it from whatever distinguishes the fragment:

@cache("post.{$post->id}.v{$post->updated_at->timestamp}")
    @include('partials.post-body', ['post' => $post])
@endcache

Per-user scoping. The directive has no notion of the current user. If the fragment varies by auth state, put that in the key yourself:

@cache('cart.' . (auth()->id() ?? 'guest'))
    ...
@endcache

Version-key busting for deploy-time invalidation. Because there is no automatic invalidation, a common pattern is to namespace every key with a config value you bump on deploy:

// config/view.php or a dedicated config/cache-fragments.php
'version' => env('FRAGMENT_CACHE_VERSION', '1'),
@cache('v' . config('cache-fragments.version') . '.homepage.hero')

Model-driven invalidation. Clear fragments explicitly from observers when the underlying data changes:

class PostObserver
{
    public function saved(Post $post): void
    {
        Cache::forget("post.{$post->id}.v{$post->updated_at->timestamp}");
        Cache::forget('homepage.latest-posts');
    }
}

For anything with many keys, prefer short TTLs over elaborate invalidation logic — it is far less error-prone.

Wrapping includes and components. @cache around @include / <x-…> works and is the cleanest way to keep markup readable:

@cache('sidebar.trending', 600)
    <x-trending-posts :limit="5" />
@endcache

Nesting. Nested @cache blocks are supported, but each nested block triggers its own cache read and its own output buffer. Distinct keys are mandatory; nesting is usually a sign you should cache the inner fragment only.

Integration tip — testing. In tests the default store is often array, so caching happens but does not survive the request. Assert on rendered output and use Cache::spy() / Cache::shouldReceive('remember') when you want to prove the fragment was served from cache:

Cache::spy();

$this->blade('@cache("k", 60) hello @endcache')->assertSee('hello');

Cache::shouldHaveReceived('put')->once();

You can also render a directive in isolation with Blade::render() for fast unit-level checks.

Gotchas and Tips

  • No automatic invalidation. The key is the only contract. Change the markup, keep the key, and stale HTML ships. Always bump TTL-based keys or a version prefix when you edit a cached fragment.
  • Key collisions across pages. @cache('sidebar') in two different layouts returns the first rendered variant to both. Prefix keys with the route, layout, or model: layout.sidebar, not sidebar.
  • Inside loops, the key must include the loop variable. @cache('row') inside @foreach ($items as $item) caches the first row and replays it for every subsequent item. Use @cache("row.{$item->id}").
  • The cached value is the rendered HTML string. Side effects inside the block (@php assignments, counters, @push to stacks) only execute on a cache miss. Never put stack pushes, script registration, or state mutation inside @cache — push outside the block instead.
  • Escaping is frozen at write time. That is normally fine, but if you cache content produced with an older escaping policy (or raw {!! !!} output for a user whose permissions changed), you are serving a stale authorisation result. Keep permission-dependent fragments out of long-lived caches.
  • Driver matters. file and database have no tag support; Cache::tags()->flush() cannot free fragments created by this directive. Design for Cache::forget($key) or TTLs. array in tests means every request is a cold cache.
  • Memcached key length. Memcached caps keys at 250 bytes and rejects spaces. If you build long composite keys, hash them: @cache('frag.' . md5($signature)).
  • Debugging misses. Wrap a key in cache()->has($key) in a tinker session, or temporarily add {{-- cached --}} markers. php artisan cache:clear nukes everything — on Redis, prefer targeted Cache::forget() calls in a Tinker session.
  • Whitespace and output buffering. The directive captures literal output including indentation and newlines. A stray blank line before @cache is outside the block; one immediately inside is inside it and becomes part of the cached string.
  • View caching interaction. After editing a cached fragment, run php artisan view:clear as well — otherwise you may be looking at stale compiled Blade and a stale cache entry and misdiagnose which one is at fault.
  • Extension points are limited by design. There is no config file, no custom store binding, and no tag support. If you need per-key metadata or tag-based flushing, wrap the directive with your own Blade directive that calls Cache::remember() with the strategy you want, and keep this package for the simple cases.
  • Upgrade note for v1.1.1. The only change is the Laravel 13 compatibility bump — no API or behavioural changes, so no code edits are required. It is a safe patch upgrade; just ensure your composer.json allows ^1.1.1 and re-run your view tests after the framework upgrade.
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.
robertmarney/lara-hierarchial-collections
simonschaufi/rule-doc-generator
emissaoja/emissaoja
blackbricksoftware/gravityforms-civicrm-form-processor
al-rahabi/fancy-crud
jinas/dhivatar-component
mltstephane/laravel-quality
bernskiold/laravel-talentlms
bernskioldmedia/laravel-form-architect
youbar/easy-crud
c-point-labs/laravel-hostyourai
dbravoan/dbcms-admin-theme-dbcms
dbravoan/dbcms-theme-db-cyber
dbravoan/dbcms-blocks-basic
dbravoan/dbcms-module-content-audit
dbravoan/dbcms-module-ai-assistant
paygate-to/paygate-php
padosoft/askmydocs-connector-base
defstudio/supercharged-carbon
bernskioldmedia/laravel-matomo