Skip to content

Commit 555c90f

Browse files
committed
add Optimizing RSC Payloads page
1 parent 740b76b commit 555c90f

3 files changed

Lines changed: 114 additions & 0 deletions

File tree

packages/docs/src/App.tsx

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@ import { Layout } from "./components/Layout/Layout";
55
import DeferApi from "./pages/api/Defer.mdx";
66
import FunstackStaticApi from "./pages/api/FunstackStatic.mdx";
77
import HowItWorks from "./pages/learn/HowItWorks.mdx";
8+
import OptimizingPayloads from "./pages/learn/OptimizingPayloads.mdx";
89
import RSCConcept from "./pages/learn/RSC.mdx";
910
import GettingStarted from "./pages/GettingStarted.mdx";
1011
import { Home } from "./pages/Home";
@@ -44,6 +45,10 @@ const routes: RouteDefinition[] = [
4445
path: "/learn/rsc",
4546
component: <Layout>{defer(<RSCConcept />)}</Layout>,
4647
}),
48+
route({
49+
path: "/learn/optimizing-payloads",
50+
component: <Layout>{defer(<OptimizingPayloads />)}</Layout>,
51+
}),
4752
route({
4853
path: "*",
4954
component: (

packages/docs/src/components/Sidebar/Sidebar.tsx

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -28,6 +28,10 @@ export const navigation: NavSection[] = [
2828
label: "How It Works",
2929
href: "/funstack-static/learn/how-it-works",
3030
},
31+
{
32+
label: "Optimizing RSC Payloads",
33+
href: "/funstack-static/learn/optimizing-payloads",
34+
},
3135
],
3236
},
3337
{
Lines changed: 105 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,105 @@
1+
# Optimizing RSC Payloads
2+
3+
FUNSTACK Static uses React Server Components (RSC) to pre-render your application at build time. By default, all content is bundled into a single RSC payload. This page explains how to split that payload into smaller chunks for better loading performance.
4+
5+
## The Default Behavior
6+
7+
Without any optimization, your entire application is rendered into one RSC payload file:
8+
9+
```
10+
dist/public/funstack__/
11+
└── fun:rsc-payload/
12+
└── b62ec6668fd49300.txt ← Contains everything
13+
```
14+
15+
This means users must download the entire payload before seeing any content - even if they only need one page of a multi-page application.
16+
17+
**Note:** RSC payloads contain the rendering results of server components only; client components and their JavaScript bundles are handled separately. However, it is still important to optimize RSC payload sizes because the recommended best practice is to keep as much of your UI as server components as possible.
18+
19+
## Chunking with defer()
20+
21+
The `defer()` function lets you split your application into multiple RSC payloads. Each `defer()` call creates a separate payload file that loads on-demand when that component renders.
22+
23+
```tsx
24+
import { defer } from "@funstack/static/server";
25+
26+
// Instead of this:
27+
<HeavyContent />
28+
29+
// Do this:
30+
<Suspense fallback={<p>Loading...</p>}>
31+
{defer(<HeavyContent />)}
32+
</Suspense>
33+
```
34+
35+
The content inside `defer()` is still rendered at build time, but it's stored in a separate file and fetched only when needed.
36+
37+
**Note:** use of `defer()` requires a `<Suspense>` boundary to handle the loading state while the payload is being fetched.
38+
39+
## Route-Level Optimization
40+
41+
The most impactful use of `defer()` is wrapping your route components. This ensures users only download the payload for the page they're viewing:
42+
43+
```tsx
44+
import { defer } from "@funstack/static/server";
45+
import { route } from "@funstack/router/server";
46+
import HomePage from "./pages/Home";
47+
import AboutPage from "./pages/About";
48+
import DocsPage from "./pages/Docs";
49+
50+
const routes = [
51+
route({
52+
path: "/",
53+
component: defer(<HomePage />),
54+
}),
55+
route({
56+
path: "/about",
57+
component: defer(<AboutPage />),
58+
}),
59+
route({
60+
path: "/docs",
61+
component: defer(<DocsPage />),
62+
}),
63+
];
64+
```
65+
66+
With this setup:
67+
68+
- Visiting `/` only downloads the Home page payload
69+
- Navigating to `/about` fetches the About page payload on-demand
70+
- Users see content faster because they're not waiting for pages they haven't visited
71+
72+
## Output Structure
73+
74+
After building with route-level `defer()`, your output looks like this:
75+
76+
```
77+
dist/public/funstack__/
78+
└── fun:rsc-payload/
79+
├── a3f2b1c9d8e7f6a5.txt ← Home page
80+
├── b5698be72eea3c37.txt ← About page
81+
├── b62ec6668fd49300.txt ← Main app shell
82+
└── c7d8e9f0a1b2c3d4.txt ← Docs page
83+
```
84+
85+
Each payload file has a **content-based hash** in its filename. This enables aggressive browser caching - the file only changes when its content changes.
86+
87+
**Note:** during development, UUIDs are used instead of content hashes for faster rebuilds. Content hashes are applied during production builds.
88+
89+
## Best Practices
90+
91+
**Always wrap route components** - This is the single most important optimization. It prevents users from downloading content for pages they may never visit.
92+
93+
**Use Suspense boundaries** - Every `defer()` call must be inside a `<Suspense>` boundary. The fallback is shown while the payload is being fetched.
94+
95+
```tsx
96+
<Suspense fallback={<PageSkeleton />}>{defer(<PageContent />)}</Suspense>
97+
```
98+
99+
**Consider below-the-fold content** - Content hidden in collapsed sections, tabs, or modals is a good candidate for `defer()` since users may never need it.
100+
101+
## See Also
102+
103+
- [defer()](/funstack-static/api/defer) - API reference with full signature and technical details
104+
- [How It Works](/funstack-static/learn/how-it-works) - Overall FUNSTACK Static architecture
105+
- [React Server Components](/funstack-static/learn/rsc) - Understanding RSC fundamentals

0 commit comments

Comments
 (0)