SSR with Data Queries FULL

The server renders the page with real data. The query adopts that markup as its starting data and keeps it fresh from there. Unlike other frameworks, there's no loading spinner, flash of empty content, or duplicate boot payload doing the same work twice.

Key Concept: SSR and data queries cover the same job at two different times. SSR turns data into HTML at first load. A query keeps that HTML current afterward. The handoff between them is data-ssr="true".

The Handoff Contract

Inside a data-ssr="true" component, every query shape adopts the server's work instead of redoing it:

ShapeOn bootFirst refresh
ListServer rows are kept. Nothing is cleared or rebuilt.Unchanged data leaves the DOM untouched. The first changed result renders the rows fresh; every refresh after that is keyed and patches in place.
RecordServer text is kept in every bound field.Fields patch individually when the fetched record differs.
State surfaceisLoading stays false, so skeletons and spinners never flash over real content.The catch-up runs as a stale refresh, with isStale set during flight and rows never cleared.
Live Example A server-rendered dashboard the queries keep alive Open Full Example ↗
View the page source. The profile card and every inventory row are literally in the HTML file, exactly as a server would render them, and the HTML is the only copy of that data. The queries parsed their starting state back out of the markup, adopted it with no loading state, and the buttons drive live updates that patch the adopted rows in place.

What the Server Sends

The server sends rendered markup, and nothing else:

<div data-component="ops-dashboard" data-ssr="true">

    <!-- Record shape: real text in the bound fields -->
    <article data-query="opsUser">
        <h2 data-bind="name">Ada Lovelace</h2>
        <p data-bind="title">Operations, Engine Division</p>
    </article>

    <!-- List shape: real rows after the template -->
    <tbody data-query="inventory">
        <template>
            <tr><td data-bind="sku"></td><td data-bind="stock" data-type="number"></td></tr>
        </template>
        <tr><td data-bind="sku">AX-100</td><td data-bind="stock" data-type="number">45</td></tr>
        <tr><td data-bind="sku">BX-200</td><td data-bind="stock" data-type="number">12</td></tr>
    </tbody>
</div>

What the Client Runs

wildflower.query('inventory', {
    from: '/api/inventory',
    key: 'sku',
    refresh: ['focus', 'etag:60']
});

wildflower.query('opsUser', {
    from: '/api/me',
    refresh: 'focus'
});

That is the entire handoff. Inside data-ssr="true", the rendered DOM is the seed: the query parses its starting rows back out of the markup it adopts, using the same fields the bindings declare, with data-type handling coercion. The server's data lives in exactly one place, the HTML, and the freshness ladder keeps it current from there, exactly as on a client-rendered page.

Hidden Fields: data-seed

The parse can only recover what the page displays, and the row key is often a field the page does not display. data-seed carries those fields as a small JSON object on the row itself, rendered by the same server loop that renders the row. It merges into the parsed row and wins any overlap. The attribute holds the machine value and the text holds the displayed one:

<!-- Rows display sku and stock; the row key (id) rides data-seed -->
<tbody data-query="inventory">
    <template>
        <tr><td data-bind="sku"></td><td data-bind="stock" data-type="number"></td></tr>
    </template>
    <tr data-seed='{"id":811}'><td data-bind="sku">AX-100</td><td data-bind="stock" data-type="number">45</td></tr>
    <tr data-seed='{"id":812}'><td data-bind="sku">BX-200</td><td data-bind="stock" data-type="number">12</td></tr>
</tbody>
wildflower.query('inventory', {
    from: '/api/inventory',
    key: 'id',   // matched from the data-seed fields
    refresh: ['focus', 'etag:60']
});

The same convention covers values the display formats. A cell that renders $1,250 holds only the formatted text; put data-seed='{"price":1250}' on the row and the machine number wins the merge, so sorting, totals, and any other arithmetic see the real value from first paint. The live example above does exactly this, computing an inventory total from seeded prices before the first fetch runs.

For a record, the same attribute sits on the query element: <article data-query="me" data-seed='{"userId":42}'>. Each datum still exists exactly once on the wire: visible fields as text, hidden fields in the attribute of the row they belong to. The attribute is read once at adoption and never needs maintaining afterward, since the store holds the data from that point on.

Row identity is what keeps refreshes cheap: with a key present, parsed or seeded, every refresh after the first framework render patches rows in place. Client-rendered pages, which have no markup to parse, can seed programmatically with the initial: option instead; it always wins over the parse.

This is not an SSR-only rule. Any query whose rows are populated before its first fetch, parsed from adopted markup or given via initial, treats that fetch as a stale refresh rather than a load: isLoading stays false, isStale flags the catch-up, and content stays put. Client-rendered pages can opt in with an initial seed of their own.
Full Status Page

Services, incidents, and the notice are read back out of the HTML with no loading flash, then stay live on polling, focus, and reconnect refreshes.