返回 AiToEarn
async-suspense-boundaries.md
根目录 / project / aitoearn-web / .agents / skills / vercel-react-best-practices / rules / async-suspense-boundaries.md
1 ---
2 title: Strategic Suspense Boundaries
3 impact: HIGH
4 impactDescription: faster initial paint
5 tags: async, suspense, streaming, layout-shift
6 ---
7
8 ## Strategic Suspense Boundaries
9
10 Instead of awaiting data in async components before returning JSX, use Suspense boundaries to show the wrapper UI faster while data loads.
11
12 **Incorrect (wrapper blocked by data fetching):**
13
14 ```tsx
15 async function Page() {
16 const data = await fetchData() // Blocks entire page
17
18 return (
19 <div>
20 <div>Sidebar</div>
21 <div>Header</div>
22 <div>
23 <DataDisplay data={data} />
24 </div>
25 <div>Footer</div>
26 </div>
27 )
28 }
29 ```
30
31 The entire layout waits for data even though only the middle section needs it.
32
33 **Correct (wrapper shows immediately, data streams in):**
34
35 ```tsx
36 function Page() {
37 return (
38 <div>
39 <div>Sidebar</div>
40 <div>Header</div>
41 <div>
42 <Suspense fallback={<Skeleton />}>
43 <DataDisplay />
44 </Suspense>
45 </div>
46 <div>Footer</div>
47 </div>
48 )
49 }
50
51 async function DataDisplay() {
52 const data = await fetchData() // Only blocks this component
53 return <div>{data.content}</div>
54 }
55 ```
56
57 Sidebar, Header, and Footer render immediately. Only DataDisplay waits for data.
58
59 **Alternative (share promise across components):**
60
61 ```tsx
62 function Page() {
63 // Start fetch immediately, but don't await
64 const dataPromise = fetchData()
65
66 return (
67 <div>
68 <div>Sidebar</div>
69 <div>Header</div>
70 <Suspense fallback={<Skeleton />}>
71 <DataDisplay dataPromise={dataPromise} />
72 <DataSummary dataPromise={dataPromise} />
73 </Suspense>
74 <div>Footer</div>
75 </div>
76 )
77 }
78
79 function DataDisplay({ dataPromise }: { dataPromise: Promise<Data> }) {
80 const data = use(dataPromise) // Unwraps the promise
81 return <div>{data.content}</div>
82 }
83
84 function DataSummary({ dataPromise }: { dataPromise: Promise<Data> }) {
85 const data = use(dataPromise) // Reuses the same promise
86 return <div>{data.summary}</div>
87 }
88 ```
89
90 Both components share the same promise, so only one fetch occurs. Layout renders immediately while both components wait together.
91
92 **When NOT to use this pattern:**
93
94 - Critical data needed for layout decisions (affects positioning)
95 - SEO-critical content above the fold
96 - Small, fast queries where suspense overhead isn't worth it
97 - When you want to avoid layout shift (loading → content jump)
98
99 **Trade-off:** Faster initial paint vs potential layout shift. Choose based on your UX priorities.
100
100 lines MARKDOWN