Skip to content

[Feature] Implement State Hydration Serialization (SSR Data Transfer) #3

@HaHiepThanh

Description

@HaHiepThanh

📋 Context & Problem Description

Currently, Gutter's Server-Side Rendering (SSR) uses the SSRResolver interface (implemented by AsyncBuilder) to load data asynchronously and render the resolved UI into the initial HTML document.

However, there is no state serialization or transfer mechanism from the host server to the WASM client. When the client WASM initializes:

  1. A new asyncState is created with its resolved field set to false.
  2. InitState() triggers start(), which runs the asynchronous load goroutine again.
  3. The client briefly enters the AsyncPending state (causing a visual UI flash or reflow) before the fetch completes.

This duplicate fetch defeats the purpose of rendering the resolved state on the server for the user, causing unnecessary server/API load and poor layout stability.

💡 Proposed Solution & Implementation Plan

We propose introducing a JSON state serialization bridge that transfers state data seamlessly.

1. Define StateSerializer Interface

In state.go or ssr.go:

type StateSerializer interface {
    SerializeState() ([]byte, error)
}

2. Collect State During SSR Walk

  • In ssrRender (ssr.go), maintain a depth-first search (DFS) state counter (stateIndex).
  • If a state implements StateSerializer (e.g., asyncState[T]), serialize its current snapshot.
  • In RenderDocumentCtx or SSRHandler, collect all serialized states and append them to the HTML template as a JSON script block:
    <script id="__GUTTER_STATE__" type="application/json">
    [
      {"idx": 0, "type": "widgets.AsyncSnapshot[T]", "data": {"State": 1, "Data": {...}, "Error": null}}
    ]
    </script>

3. Read Preloaded State During Hydration

  • In the WASM client runtime, extract and parse the JSON block from #__GUTTER_STATE__ during startup, storing it in a map preloadedStates.
  • During client-side hydration, traverse the tree with the same DFS counter.
  • When initializing asyncState[T], lookup the state index in the preloaded map. If found, deserialize the snapshot directly, seed the state, and set resolved = true to skip duplicate client-side loading.

🎯 Impact & Benefits

  • No Layout Shifts: Eliminates the brief loading flash or reflow on hydration.
  • Reduced API Overhead: Prevents client WASM from duplicate-calling the backend immediately after page load.

🏷️ Suggested Labels

feature, ssr, hydration, performance

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type
    No fields configured for issues without a type.

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions