PATTERNS FEATURE
Live regions: deferred and reactive slots
A page region is a resource with its own pipeline. Mark it deferred and it streams in as server HTML; add an interval and the server keeps re-rendering it.
Connecting to the server stream…
Live regions — deferred and reactive slots
This page covers two pillars at once: Late HTML (deferred blocks) and Live HTML (reactive slots). Both rest on the Region Contract pillar — the idea that a page region is a real resource with its own pipeline, not a partial with lucky context.
A region is a resource, not a fragment
Traditional page composition leaves nav, sidebars, widgets, and footers as special-case includes with hidden data dependencies, each wired by a different mechanism. Semitexa promotes every region to a first-class slot resource:
use Semitexa\Ssr\Attribute\AsSlotResource;#[AsSlotResource(slot: 'inventory-summary', layout: 'app')]final class InventorySummarySlot{ public function resolve(InventorySummaryResource $resource): InventorySummaryResource { return $resource->withRows($this->repository->recent()); }}The slot has its own handler flow, its own render context, its own asset collection — and it renders through the same Twig system as the page itself. layout_slot() composes the shell declaratively while the data flow stays explicit and reviewable. You can always answer "where does this region's data come from?" by reading its resource, because the region is declared, not improvised.
Late HTML — deferred blocks
Add deferred: true and the region is marked for late delivery. The page sends its shell immediately; the server then renders the slow region and streams the finished HTML into position over SSE. The browser swaps in HTML — it does not rebuild the page from client state.
#[AsSlotResource( slot: 'inventory-summary', layout: 'app', deferred: true, skeletonTemplate: '@app/slots/inventory-summary.skeleton.html.twig',)]final class InventorySummarySlot { /* … */ }A skeletonTemplate gives the region a meaningful placeholder while its final HTML is in transit. The crucial distinction: deferred does not mean "replace SSR with client rendering later." It means late server HTML. The page model stays server-driven from the first byte to the final slot render.
Live HTML — reactive slots
Add a refreshInterval and the region keeps re-rendering itself on a timer. The server renders the slot fresh each cycle, and the page swaps the new HTML into place:
#[AsSlotResource( slot: 'inventory-summary', layout: 'app', deferred: true, refreshInterval: 5, // seconds)]final class InventorySummarySlot { /* … */ }That is the entire live-widget contract. There is no bespoke polling loop, no hand-written reconnection handler, no per-widget HTML-replacement code — the framework owns the timed request, the HTML swap, and SSE connection recovery. Live UI without converting the page into an app shell, and without a second state machine for what is fundamentally "the server re-rendering truth."
One stream, not one stream per widget
All UI streaming rides a single canonical SSE channel, GET /__semitexa_kiss. The page opens that one stream via ui_page_sse_session_meta(...); individual components and slots never open their own connections. Deferred fills, live refreshes, and server-pushed component patches all arrive as typed frames on that single EventSource and flow through the same rendering and patch machinery the rest of the UI already uses. (The collaboration showcase rides this very stream — co-editing is just another consumer of the page's one feed.)
A note on direction, for honesty: these slots are an SSE pull/push-down model — the server re-renders and pushes HTML to the browser. They are not bidirectional websockets. That is deliberate: the pillar is "the server keeps re-rendering truth," and a one-way server→client HTML stream is exactly the right shape for it.
Why it matters
Without a framework-level region contract, every live or deferred region becomes its own little app: its own fetch, its own state, its own reconnection logic, its own divergence from the page. Declaring deferred and refreshInterval on a slot resource keeps the live behaviour co-located with the region's contract and keeps the region inside the one rendering story. The shell and its live widgets never become two architectures, because they were always one.
How it works
deferred: true marks a region for late delivery: the shell paints immediately and the server streams the finished HTML into position over SSE (an optional skeletonTemplate fills the gap). Adding refreshInterval: N keeps the server re-rendering the region on a timer and swapping the new HTML in place — the framework owns the request loop, the HTML swap and SSE reconnection. All of it rides one canonical stream (GET /__semitexa_kiss); slots never open their own connection.
Why it matters
Without a region contract, every live or deferred region becomes its own little app with its own fetch, state and reconnection logic. Declaring deferred and refreshInterval on a slot keeps the live behaviour co-located with the region's contract and inside the one rendering story. The shell and its widgets never become two architectures, because they were always one. The live demo above is a real deferred slot: the skeleton paints first, then the server streams a re-rendered region in — a server tick counter, timestamp and status word that change with no reload (reactive refresh requires the persistent deferred-SSE channel to be enabled).