📋 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:
- A new
asyncState is created with its resolved field set to false.
InitState() triggers start(), which runs the asynchronous load goroutine again.
- 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
📋 Context & Problem Description
Currently, Gutter's Server-Side Rendering (SSR) uses the
SSRResolverinterface (implemented byAsyncBuilder) 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:
asyncStateis created with itsresolvedfield set tofalse.InitState()triggersstart(), which runs the asynchronous load goroutine again.AsyncPendingstate (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
StateSerializerInterfaceIn
state.goorssr.go:2. Collect State During SSR Walk
ssrRender(ssr.go), maintain a depth-first search (DFS) state counter (stateIndex).StateSerializer(e.g.,asyncState[T]), serialize its current snapshot.RenderDocumentCtxorSSRHandler, collect all serialized states and append them to the HTML template as a JSON script block:3. Read Preloaded State During Hydration
#__GUTTER_STATE__during startup, storing it in a mappreloadedStates.asyncState[T], lookup the state index in the preloaded map. If found, deserialize the snapshot directly, seed the state, and setresolved = trueto skip duplicate client-side loading.🎯 Impact & Benefits
🏷️ Suggested Labels
feature,ssr,hydration,performance