reactnext.js

React use() och Suspense: dataströmning utan vattenfall

Ett vattenfall har en ful aritmetisk egenskap. Fyra 300ms-förfrågningar i en kedja är 1,2 sekunder med tom skärm för data som kunde ha anlänt på 300ms.

Vad en suspenderad komponent faktiskt gör

En komponent som inte är klar kastar ett Promise. Inte ett fel, ett Promise. React fångar det precis som en Error Boundary fångar ett fel, kasserar det delvis renderade arbetet, visar närmaste fallback och prenumererar på det kastade Promise. När det löser sig kör React komponenten igen från toppen.

Promise måste vara stabilt mellan dessa körningar. Ett nytt Promise vid varje körning är en oändlig suspension. Det är det vanligaste sättet att förstöra use().

tsx

"use client";
import { use } from "react";
export default function Posts({ posts }) {
// suspenderar tills promise löser sig
const allPosts = use(posts);
return (
<ul>
{allPosts.map((post) => (
<li key={post.id}>{post.title}</li>
))}
</ul>
);
}

Suspense, det statiska skalet och HTML som anländer i fel ordning

React skickar det statiska skalet omedelbart. Den försenade biten läggs till i slutet av dokumentet, utom synhåll, och React strömmar ett litet inline <script> bredvid det för att flytta innehållet till rätt ställe.

tsx

import { Suspense } from "react";
export default function Page() {
return (
<main>
<h1>Dashboard</h1> {/* skickas omedelbart */}
<Suspense fallback={<StatsSkeleton />}>
<Stats />
</Suspense>
<Suspense fallback={<FeedSkeleton />}>
<Feed />
</Suspense>
</main>
);
}

Starta tidigt, läs sent

tsx

import { Suspense } from "react";
import Posts from "@/app/ui/posts";
import Comments from "@/app/ui/comments";
export default function Page() {
// båda förfrågningarna startar nu, parallellt
const posts = getPosts();
const comments = getComments();
return (
<>
<Suspense fallback={<div>Laddar inlägg...</div>}>
<Posts posts={posts} />
</Suspense>
<Suspense fallback={<div>Laddar kommentarer...</div>}>
<Comments comments={comments} />
</Suspense>
</>
);
}

Fällan ingen förväntar sig: strömning använder din HTTP-statuskod

När en Suspense-fallback renderas och strömmen öppnas har servern redan förbundit sig till 200 OK. Ett notFound() som utlöses inuti en strömmad gräns kan inte ge en äkta 404.

tsx

export default async function PostPage({ params }) {
const { slug } = await params;
const exists = await checkSlugExists(slug); // snabbt, innan strömmen öppnas
if (!exists) notFound(); // äkta 404
return (
<Suspense fallback={<PostSkeleton />}>
<PostContent slug={slug} />
</Suspense>
);
}

Vad strömning gör med Core Web Vitals

  • TTFB sjunker till ungefär rendertiden för layoutet.
  • LCP kan försämras, inte förbättras. Håll LCP-element i det statiska skalet.
  • CLS är ditt ansvar.
  • INP förbättras tack vare selektiv hydration.

Fallgropar och bästa praxis

  • Skapa aldrig Promise under rendering av en Client Component.
  • Suspense fångar väntan, inte fel. Para varje gräns med en Error Boundary.
  • Dimensionera gränsen som en person skulle göra.
  • Botar tar inte emot strömmen.
  • Ett Promise-prop passerar nätverket.

Slutsats

Istället för en sekvens — förfrågan, väntan, rendering, förfrågan, väntan, rendering — får du parallella strömmar: allt startar på en gång, och varje del av sidan visas så snart dess egna data anländer.