reactnext.js

React use() og Suspense: datastrømming uten fossfall

React use() og Suspense: datastrømming uten fossfall

Et fossfall har en stygg aritmetisk egenskap. Fire 300ms-forespørsler i en kjede er 1,2 sekunder med tomt skjerm for data som kunne ha ankommet på 300ms.

Hva en suspendert komponent faktisk gjør

En komponent som ikke er klar, kaster et Promise. Ikke en feil, et Promise. React fanger det som en Error Boundary fanger en feil, forkaster det delvis renderte arbeidet, viser nærmeste fallback og abonnerer på det kastede Promise. Når det løser seg, kjører React komponenten på nytt fra toppen.

Promise-et må være stabilt mellom disse kjøringene. Et nytt Promise ved hver kjøring er en uendelig suspensjon. Dette er den vanligste måten å bryte use() på.

tsx

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

Suspense, det statiske skallet og HTML som ankommer i feil rekkefølge

React sender det statiske skallet umiddelbart. Det forsinkede stykket legges til på slutten av dokumentet, utenfor syne, og React strømmer et lite inline <script> ved siden av det for å flytte innholdet til rett sted.

tsx

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

Start tidlig, les sent

tsx

import { Suspense } from "react";
import Posts from "@/app/ui/posts";
import Comments from "@/app/ui/comments";
export default function Page() {
// begge forespørsler starter nå, parallelt
const posts = getPosts();
const comments = getComments();
return (
<>
<Suspense fallback={<div>Laster innlegg...</div>}>
<Posts posts={posts} />
</Suspense>
<Suspense fallback={<div>Laster kommentarer...</div>}>
<Comments comments={comments} />
</Suspense>
</>
);
}

Fellen ingen forventer: strømming bruker opp HTTP-statuskoden din

Når en Suspense-fallback renderer og strømmen åpner seg, har serveren allerede forpliktet seg til 200 OK. Et notFound() som utløses inne i en strømt grense kan ikke gi en ekte 404.

tsx

export default async function PostPage({ params }) {
const { slug } = await params;
const exists = await checkSlugExists(slug); // raskt, før strømmen åpner
if (!exists) notFound(); // ekte 404
return (
<Suspense fallback={<PostSkeleton />}>
<PostContent slug={slug} />
</Suspense>
);
}

Hva strømming gjør med Core Web Vitals

  • TTFB synker til omtrent rendertiden for layoutet.
  • LCP kan bli verre, ikke bedre. Hold LCP-elementer i det statiske skallet.
  • CLS er ditt ansvar.
  • INP forbedres takket være selektiv hydrasjon.

Fallgruver og beste praksis

  • Aldri opprette Promise under rendering av en Client Component.
  • Suspense fanger venting, ikke feil. Par hver grense med en Error Boundary.
  • Dimensjoner grensen som en person ville gjort.
  • Bots mottar ikke strømmen.
  • En Promise-prop krysser nettverket.

Konklusjon

I stedet for en sekvens — forespørsel, venting, rendering, forespørsel, venting, rendering — får du parallelle strømmer: alt starter på én gang, og hver del av siden vises så snart dens egne data ankommer.