|
1 | 1 | # defer() |
2 | 2 |
|
3 | | -The `defer()` function enables deferred rendering for React Server Components, allowing content to be streamed progressively to the client. |
| 3 | +The `defer()` function enables deferred rendering for React Server Components, reducing initial data load. |
| 4 | + |
| 5 | +You can think of this as React's `lazy` API but for Server Components. |
4 | 6 |
|
5 | 7 | ## Import |
6 | 8 |
|
7 | 9 | ```typescript |
| 10 | +// @funstack/static/server is where utilities for server components live |
8 | 11 | import { defer } from "@funstack/static/server"; |
9 | 12 | ``` |
10 | 13 |
|
11 | 14 | ## Usage |
12 | 15 |
|
13 | 16 | ```tsx |
14 | 17 | import { defer } from "@funstack/static/server"; |
15 | | -import { route } from "@funstack/router/server"; |
16 | | -import HeavyComponent from "./HeavyComponent"; |
| 18 | +import { HeavyServerComponent } from "./HeavyServerComponent"; |
17 | 19 |
|
18 | | -const routes = [ |
19 | | - route({ |
20 | | - path: "/", |
21 | | - component: defer(HeavyComponent), |
22 | | - }), |
23 | | -]; |
24 | | -``` |
25 | | - |
26 | | -## Signature |
27 | | - |
28 | | -```typescript |
29 | | -function defer<T extends React.ComponentType<any>>( |
30 | | - Component: T, |
31 | | -): React.ReactNode; |
| 20 | +function Page() { |
| 21 | + return ( |
| 22 | + <details> |
| 23 | + <summary>Very long description</summary> |
| 24 | + <Suspense fallback={<p>Loading...</p>}> |
| 25 | + {defer(HeavyServerComponent)} |
| 26 | + </Suspense> |
| 27 | + </details> |
| 28 | + ); |
| 29 | +} |
32 | 30 | ``` |
33 | 31 |
|
34 | | -### Parameters |
| 32 | +By using `defer()`, the `HeavyServerComponent` will still be rendered on the server (during build), but its data will be sent to the client as a separate RSC payload. |
35 | 33 |
|
36 | | -- **Component:** A React component to render with deferred streaming. |
| 34 | +This means that: |
37 | 35 |
|
38 | | -### Returns |
| 36 | +- Client can start rendering the rest of the page without waiting for `HeavyServerComponent`'s data |
| 37 | +- When the `defer(HeavyServerComponent)` part is rendered on the client, it will fetch the separate RSC payload and show the content. |
39 | 38 |
|
40 | | -A React node that will stream its content when rendered. |
| 39 | +The key point is that `HeavyServerComponent` is still a Server Component, so only the rendered HTML (and usage of Client Components inside it) is sent to the client, not the component code itself. |
41 | 40 |
|
42 | | -## When to Use defer() |
| 41 | +**Note:** you cannot pass any props to the component wrapped with `defer()`. This limitation may be lifted in the future. |
43 | 42 |
|
44 | | -Use `defer()` when you have components that: |
45 | | - |
46 | | -- Fetch data asynchronously (e.g., from APIs or databases) |
47 | | -- Perform expensive computations |
48 | | -- Include large amounts of content |
49 | | -- Have children that can be rendered independently |
| 43 | +**Note:** `defer()` must be used inside a `Suspense` boundary since the content will be streamed in later. |
50 | 44 |
|
51 | | -```tsx |
52 | | -// Async component that fetches data |
53 | | -async function BlogPosts() { |
54 | | - const posts = await fetchPosts(); // Slow API call |
55 | | - |
56 | | - return ( |
57 | | - <ul> |
58 | | - {posts.map((post) => ( |
59 | | - <li key={post.id}>{post.title}</li> |
60 | | - ))} |
61 | | - </ul> |
62 | | - ); |
63 | | -} |
| 45 | +## Signature |
64 | 46 |
|
65 | | -// Wrap with defer() to stream |
66 | | -route({ |
67 | | - path: "/blog", |
68 | | - component: defer(BlogPosts), |
69 | | -}); |
| 47 | +```typescript |
| 48 | +export function defer(component: FC<{}>): React.ReactNode; |
70 | 49 | ``` |
71 | 50 |
|
72 | | -## How It Works |
| 51 | +### Parameters |
73 | 52 |
|
74 | | -1. **Without defer():** The entire page waits for all components to render before sending any HTML. |
| 53 | +- **component:** A server component to render with deferred loading. |
75 | 54 |
|
76 | | -2. **With defer():** |
77 | | - - The page shell renders immediately |
78 | | - - A placeholder is inserted for the deferred content |
79 | | - - The deferred content streams in once ready |
80 | | - - The placeholder is replaced with the actual content |
| 55 | +### Returns |
81 | 56 |
|
82 | | -## Example: Mixing Static and Deferred Content |
| 57 | +A React Node that will stream its content separately from the main entry point. |
83 | 58 |
|
84 | | -```tsx |
85 | | -import { defer } from "@funstack/static/server"; |
86 | | - |
87 | | -// Fast, static header |
88 | | -function Header() { |
89 | | - return ( |
90 | | - <header> |
91 | | - <h1>My Blog</h1> |
92 | | - </header> |
93 | | - ); |
94 | | -} |
95 | | - |
96 | | -// Slow, data-fetching component |
97 | | -async function RecentPosts() { |
98 | | - const posts = await fetchRecentPosts(); |
99 | | - return <PostList posts={posts} />; |
100 | | -} |
| 59 | +## When to Use defer() |
101 | 60 |
|
102 | | -// Page component |
103 | | -function BlogPage() { |
104 | | - return ( |
105 | | - <div> |
106 | | - <Header /> {/* Renders immediately */} |
107 | | - {defer(RecentPosts)} {/* Streams when ready */} |
108 | | - </div> |
109 | | - ); |
110 | | -} |
111 | | -``` |
| 61 | +Use `defer()` when you have components that: |
112 | 62 |
|
113 | | -## MDX Integration |
| 63 | +- Renders large HTML content |
| 64 | +- Is not immediately visible on page load (e.g., inside a collapsed section) |
114 | 65 |
|
115 | | -`defer()` works seamlessly with MDX files: |
| 66 | +Typically, you will want to wrap route components with `defer()` to improve initial load performance. Otherwise, user needs to wait for contents for all pages to arrive before seeing anything. |
116 | 67 |
|
117 | 68 | ```tsx |
118 | 69 | import { defer } from "@funstack/static/server"; |
119 | | -import BlogPost from "./BlogPost.mdx"; |
| 70 | +import HomePage from "./HomePage"; |
| 71 | +import AboutPage from "./AboutPage"; |
120 | 72 |
|
121 | | -route({ |
122 | | - path: "/blog/post", |
123 | | - component: defer(BlogPost), |
124 | | -}); |
| 73 | +const routes = [ |
| 74 | + route({ |
| 75 | + path: "/", |
| 76 | + component: defer(HomePage), |
| 77 | + }), |
| 78 | + route({ |
| 79 | + path: "/about", |
| 80 | + component: defer(AboutPage), |
| 81 | + }), |
| 82 | + // ... |
| 83 | +]; |
125 | 84 | ``` |
126 | 85 |
|
127 | | -## Best Practices |
| 86 | +## How It Works |
128 | 87 |
|
129 | | -1. **Defer at the route level** for pages with async data fetching |
130 | | -2. **Don't over-defer** - only use it when you have genuinely slow components |
131 | | -3. **Consider user experience** - streaming can cause layout shifts; design accordingly |
132 | | -4. **Test without defer** first to understand baseline performance |
| 88 | +By default, FUNSTACK Static puts the entire app (`<App />`) into one RSC payload (`/.funstack/index.rsc`). The client fetches this payload to render your SPA. |
133 | 89 |
|
134 | | -## See Also |
| 90 | +When you use `defer(Component)`, FUNSTACK Static creates **additional RSC payloads** for the rendering result of `<Component />`. This results in an additional emit of RSC payload files like `/.funstack/rsc/fun:rsc-payload/b5698be72eea3c37`. |
135 | 91 |
|
136 | | -- [funstackStatic()](/api/funstack-static) - Main plugin configuration |
137 | | -- [React Server Components](/concepts/rsc) - Understanding async components |
| 92 | +In the main RSC payload, the `defer` call is replaced with a client component `<ClientWrapper moduleId="fun:rsc-payload/b5698be72eea3c37" />`. This component is responsible for fetching the additional RSC payload from client and renders it when it's ready. |
0 commit comments