rybakit/twig-deferred-extension
Twig extension that lets you defer rendering of heavy blocks and run them later, improving perceived performance. Works with Twig templates to collect deferred parts and flush them when ready—handy for widgets, fragments, and complex layouts.
Installation
composer require rybakit/twig-deferred-extension
Register the extension in your Laravel app’s Twig environment (typically in app/Providers/AppServiceProvider.php):
use Rybakit\Twig\DeferredExtension;
public function register()
{
$this->app->singleton(Twig_Environment::class, function ($app) {
$loader = new \Twig\Loader\FilesystemLoader($app['config']['view.paths']);
$twig = new \Twig\Environment($loader, [
'cache' => storage_path('framework/views'),
]);
$twig->addExtension(new DeferredExtension());
return $twig;
});
}
First Use Case
Defer a block in a Twig template (e.g., resources/views/layouts/app.blade.php):
{# Defer the 'scripts' block to render after the page loads #}
{% block scripts %}
{% deferred %}
<script src="{{ asset('js/app.js') }}"></script>
{% enddeferred %}
{% endblock %}
Render the template in a controller:
return view('layouts.app');
Markup Deferral
Use {% deferred %} to wrap content that should load asynchronously (e.g., scripts, heavy DOM elements):
{% deferred %}
<div id="heavy-widget">{{ include('widget.twig') }}</div>
{% enddeferred %}
Dynamic Deferral Conditionally defer blocks based on logic:
{% if user.is_premium %}
{% deferred %}
{{ include('premium-content.twig') }}
{% enddeferred %}
{% endif %}
Integration with Laravel Mix/Vite Combine with asset pipelines for optimized loading:
{% deferred %}
@vite(['resources/js/app.js'])
{% enddeferred %}
{% block footer %}
<div class="static-footer">...</div>
{% deferred %}
<div class="dynamic-footer">...</div>
{% enddeferred %}
{% endblock %}
Double Rendering
Ensure deferred blocks aren’t rendered twice (e.g., in layouts and child templates). Use {% block %} to override parent blocks safely.
{# Parent template #}
{% block deferred_scripts %}{% endblock %}
{# Child template #}
{% block deferred_scripts %}
{% deferred %}{{ parent() }}<script>...</script>{% enddeferred %}
{% endblock %}
Asset Paths in Deferred Blocks
Use {{ asset() }} or {{ mix() }} inside deferred blocks to avoid 404s for static assets. The extension doesn’t modify asset paths.
JavaScript Dependencies Deferred blocks execute in order. Avoid circular dependencies between deferred scripts.
storage/logs/laravel.log.try-catch blocks if they may fail:
{% deferred %}
<script>
try { /* risky code */ } catch(e) { console.error(e); }
</script>
{% enddeferred %}
Custom Deferral Logic
Override the DeferredExtension class to modify how deferred blocks are rendered:
class CustomDeferredExtension extends DeferredExtension {
public function renderBlock(\Twig\Node\Node $node, \Twig\Environment $env, array $context) {
// Custom logic here
return parent::renderBlock($node, $env, $context);
}
}
Global Configuration
Set default attributes (e.g., async or defer for scripts) via the extension constructor:
$extension = new DeferredExtension(['script_attributes' => 'async']);
Testing Mock the extension in PHPUnit to test deferred behavior:
$twig = new \Twig\Environment($loader);
$twig->addExtension(new DeferredExtension());
$this->assertStringContainsString('deferred="true"', $twig->render('template.twig'));
How can I help you explore Laravel packages today?