polidog / rosanna-defer
Deferred fragments for Rosanna: placeholders, a GET endpoint, client caching and rosanna-defer.js, without sessions or state
Requires
- php: >=8.5
- polidog/rosanna: ^1.0
Requires (Dev)
- composer/composer: ^2.8
- friendsofphp/php-cs-fixer: ^3.93
- phpstan/phpstan: ^2.1
- phpunit/phpunit: ^11.0
- vimeo/psalm: ^6.14
Suggests
None
Provides
None
Conflicts
None
Replaces
None
This package is auto-updated.
Last update: 2026-11-04 13:16:31 UTC
README
polidog/rosanna-defer fetches parts of a Rosanna page in a separate request after load, so the page itself can sit on a CDN while per-visitor or slow parts stay separate.
It uses no session and no state. If you need useState or buttons that go through the server, use polidog/rosanna-state, which includes this package.
Install
composer require polidog/rosanna-defer
vendor/bin/rosanna-defer publish # copies rosanna-defer.js to public/
Usage
<?php // components/UserHeader.psx namespace App; use Polidog\Rosanna\Defer\Defer; use function Polidog\Rosanna\Defer\deferred; return deferred( fn (array $props, UserRepository $users) => <p>{$users->current()->name}</p>, new Defer(name: 'user-header', cacheControl: 'private, no-store'), );
vendor/bin/rosanna-defer compile components/
<?php use Polidog\Rosanna\Defer\RosannaDefer; $app = new RosannaDefer(); $app->loadComponentManifest(__DIR__ . '/var/cache/psx/manifest.php'); // Supplies arguments after $props, such as `$users`. Implements Polidog\Rosanna\Runtime\PsxArgumentResolver; without it the render returns 500 $app->setArgumentResolver($yourResolver); // Call before page handling. Returns null unless the path is /_defer/{name} $response = $app->handleDeferred(); if ($response !== null) { $response->send(); return; } // On the page, <UserHeader /> renders as a placeholder echo RosannaDefer::renderClientScript();
A host with its own Renderer uses new Renderer(deferred: $app->placeholderRenderer()). A host with its own request object passes new DeferRequest($method, $path, $query, $queryString).
Caching
| Option | Meaning |
|---|---|
cacheControl |
The endpoint's Cache-Control. Defaults to private, max-age=0 |
localCache |
Store in the browser's localStorage |
localCacheTtl |
Seconds to keep it in localStorage |
reloadable |
Keep the wrapper after loading so Rosanna.reloadDefer(name) can fetch again |
- A fragment with a form or a
type=hiddeninput is never written tolocalStorage, even withlocalCache, so CSRF tokens do not stay on a shared device. - A response that uses the session or sets a cookie is sent as
private, no-store, even ifcacheControlallows shared caches. /_defer/{name}is a public GET outside the host's protection, and its query is untrusted input. Authorize inside the target.
Artifacts from vendor/bin/rosanna compile are refused because it does not look for defer. Use vendor/bin/rosanna-defer compile.