Optimize Federation for Streaming and Suspense
mainThe way you structure your encodeFederationPayload call determines whether your content streams incrementally or buffers entirely.
Top-level JSX (Buffered)
If you pass a React element directly to encodeFederationPayload, the entire flight stream is buffered. The consumer receives SSR HTML for instant display, but Suspense boundaries inside the element will not stream incrementally. The client waits for all promises to resolve before showing content.
Object with JSX fields (Streaming)
If you pass a plain object containing JSX, the flight rows stream incrementally. Suspense fallbacks arrive immediately, and resolved content streams in as it becomes available. This is the recommended pattern for slow data fetches or AI responses.
Comparison:
| Pattern | SSR Preview | Incremental Streaming | Use Case |
|---|---|---|---|
encodeFederationPayload(<JSX />) | Yes (Instant HTML) | No (Buffered) | Static/Fast content |
encodeFederationPayload({ key: <JSX /> }) | No | Yes | Slow data/AI/Suspense |
Example: Streaming via Object
Remote Side:
app.get('/api/dashboard', async () => {
return await encodeFederationPayload({
chart: (
<Suspense fallback={<div>Loading...</div>}>
<SlowChart />
</Suspense>
),
})
})Client Side:
'use client'
import { useEffect, useState } from 'react'
import { decodeFederationPayload } from 'spiceflow/react'
export function Dashboard() {
const [chart, setChart] = useState<React.ReactNode>(null)
useEffect(() => {
fetch('/api/dashboard')
.then((res) => decodeFederationPayload<{ chart: React.ReactNode }>(res))
.then((decoded) => setChart(decoded.chart))
}, [])
return <div>{chart}</div>
}