← UI Rendering & SSR

UI RENDERING & SSR FEATURE

Reactive Import

The import keeps running on the server, and the page stays honest by streaming fresh HTML instead of faking progress in frontend state.

Feature Guide

A quick orientation block that answers the essential questions: what this feature does, how it works, why it matters, and the key concepts behind it.

What this does

Long-running import progress stays owned by the server, and the page simply keeps receiving fresh HTML snapshots of that truth.

How it works

The import job writes progress_percent and progress_message into DemoJobRun. ReactiveImportSlot re-renders on refreshInterval and the browser swaps the returned HTML into the existing page shell.

Why it matters

You do not need a frontend progress engine just to keep a counter moving. The server stays authoritative, and the UI still feels live.

Key concepts

DemoJobRun
Stores the import state that the live slot turns into visible progress.
ReactiveImportSlot
Deferred slot resource that keeps re-rendering the import panel as batches advance.
SSR-first live UI
The page remains server-rendered even while progress updates keep arriving.

Server-Owned Progress

Import progress stays truthful without a frontend state machine

The import job advances on the server, and the live slot keeps re-rendering that authoritative state as HTML.

1 source of truth for progress
0 frontend counters to reconcile
HTML payload pushed back into the page
0 Rows processed
200 Total rows

Waiting for import job…

Client-side drift

Progress is simulated in frontend state

The browser invents a temporary truth while the job is still running somewhere else.

SSR-first live import

Server state is the only state

Each refresh shows the latest job snapshot directly from the server-owned import pipeline.

refreshInterval: 2 server-owned progress batch processing SSR-first live UI

Verified against Semitexa Ultimate 2026.09.19.1020

Reactive Import

The import keeps running on the server, and the page stays honest by streaming fresh HTML instead of faking progress in frontend state.

The problem

Long-running import jobs often push teams toward client-side progress simulation. The browser invents a temporary truth while the job is still running somewhere else, and the two states have to be reconciled when the job finishes.

How it works

An import job runs in batches on the server and writes authoritative progress to storage. A deferred slot with refreshInterval: 2 holds its SSE connection open and the server pushes freshly rendered HTML every two seconds. Each render reads the actual import state and the page swaps the HTML in place. There is no request per refresh; the only requests after the first are the framework's own SSE reconnects.

Server state is the only state. Each refresh shows the latest job snapshot directly from the server-owned import pipeline.

Key mechanisms

  • refreshInterval: 2 — the server re-renders and pushes every two seconds; the browser never polls.
  • server-owned progress — the import job owns the authoritative progress state; the slot reads it on each render.
  • batch processing — the import advances in chunks, and each slot refresh picks up the latest committed progress.
  • SSR-first live UI — the page never switches rendering models; the live region is always server-rendered HTML.

Why this matters

Client-side progress simulation requires estimating or polling an API and translating the response into local state. Server-owned progress avoids that layer entirely. The slot reflects what the server actually knows, so the progress display is always accurate rather than extrapolated.

© Linus Torvalds: "Talk is cheap. Show me the code."

ReactiveImportSlot Secondary page region
<?phpdeclare(strict_types=1);use Examples\Rendering\Philosophy\Support\JobService;use Semitexa\Core\Attribute\InjectAsReadonly;use Semitexa\Ssr\Attribute\AsSlotHandler;use Semitexa\Ssr\Attribute\AsSlotResource;use Semitexa\Ssr\Application\Service\Http\Response\HtmlSlotResponse;#[AsSlotResource(    handle: 'imports_dashboard',    slot: 'job_status',    deferred: true,    refreshInterval: 3,)]final class ImportJobStatusSlot extends HtmlSlotResponse{    public function withStatus(string $status): self    {        return $this->with('status', $status);    }    public function withProgress(int $progress): self    {        return $this->with('progress', $progress);    }}#[AsSlotHandler(slot: ImportJobStatusSlot::class)]final class ImportJobStatusSlotHandler{    #[InjectAsReadonly]    protected JobService $jobs;    public function handle(ImportJobStatusSlot $slot): ImportJobStatusSlot    {        $run = $this->jobs->latestImportRun();        return $slot            ->withStatus($run->status())            ->withProgress($run->progressPercent());    }}// The slot keeps re-rendering server truth. No second client-side state machine is required.

How it works

The import job writes progress_percent and progress_message into DemoJobRun. ReactiveImportSlot re-renders on refreshInterval and the browser swaps the returned HTML into the existing page shell.

Why it matters

You do not need a frontend progress engine just to keep a counter moving. The server stays authoritative, and the UI still feels live.

Key concepts

DemoJobRun
Stores the import state that the live slot turns into visible progress.
ReactiveImportSlot
Deferred slot resource that keeps re-rendering the import panel as batches advance.
SSR-first live UI
The page remains server-rendered even while progress updates keep arriving.

Support Semitexa
Built for developers who prefer control over magic. Your support helps keep it fast, open, and evolving.

Donate via PayPal