React Router v7 已發佈。 檢視文件
串流
本頁內容

串流

串流允許您在內容可用時立即傳遞,而不是等待頁面的全部內容準備就緒,從而增強使用者體驗。

請確保您的託管供應商支援串流,並非所有供應商都支援。如果您的回應似乎沒有串流,這可能是原因。

步驟

串流資料有三個步驟

  1. 專案設定: 我們需要確保我們的客戶端和伺服器入口點都設定為支援串流
  2. 元件設定: 我們需要確保我們的元件可以呈現串流資料
  3. 延遲載入器資料: 最後,我們可以在載入器中延遲資料

1. 專案設定

從一開始就準備就緒: 使用入門範本建立的 Remix 應用程式已預先設定為串流。

需要手動設定嗎?: 如果您的專案是從頭開始或使用較舊的範本,請驗證 entry.server.tsxentry.client.tsx 是否具有串流支援。如果您沒有看到這些檔案,則表示您正在使用預設值且支援串流。如果您已建立自己的入口,以下是範本預設值供您參考

2. 元件設定

一個沒有串流的路由模組可能如下所示

import type { LoaderFunctionArgs } from "@remix-run/node"; // or cloudflare/deno
import { json } from "@remix-run/node"; // or cloudflare/deno
import { useLoaderData } from "@remix-run/react";

export async function loader({
  params,
}: LoaderFunctionArgs) {
  const [product, reviews] = await Promise.all([
    db.getProduct(params.productId),
    db.getReviews(params.productId),
  ]);

  return json({ product, reviews });
}

export default function Product() {
  const { product, reviews } =
    useLoaderData<typeof loader>();
  return (
    <>
      <ProductPage data={product} />
      <ProductReviews data={reviews} />
    </>
  );
}

為了呈現串流資料,您需要使用 React 的 <Suspense> 和 Remix 的 <Await>。這有點樣板程式碼,但很簡單

import type { LoaderFunctionArgs } from "@remix-run/node"; // or cloudflare/deno
import { json } from "@remix-run/node"; // or cloudflare/deno
import { Await, useLoaderData } from "@remix-run/react";
import { Suspense } from "react";

import { ReviewsSkeleton } from "./reviews-skeleton";

export async function loader({
  params,
}: LoaderFunctionArgs) {
  // existing code
}

export default function Product() {
  const { product, reviews } =
    useLoaderData<typeof loader>();
  return (
    <>
      <ProductPage data={product} />
      <Suspense fallback={<ReviewsSkeleton />}>
        <Await resolve={reviews}>
          {(reviews) => <ProductReviews data={reviews} />}
        </Await>
      </Suspense>
    </>
  );
}

即使在我們開始延遲資料之前,此程式碼也會繼續運作。最好先進行元件程式碼。如果您遇到問題,可以更容易追蹤問題所在。

3. 在載入器中延遲資料

現在我們的專案和路由元件都設定為串流資料,我們可以開始在載入器中延遲資料。我們將使用 Remix 的 defer 工具來執行此操作。

請注意非同步 Promise 程式碼中的變更。

import type { LoaderFunctionArgs } from "@remix-run/node"; // or cloudflare/deno
import { defer } from "@remix-run/node"; // or cloudflare/deno
import { Await, useLoaderData } from "@remix-run/react";
import { Suspense } from "react";

import { ReviewsSkeleton } from "./reviews-skeleton";

export async function loader({
  params,
}: LoaderFunctionArgs) {
  // 👇 note this promise is not awaited
  const reviewsPromise = db.getReviews(params.productId);
  // 👇 but this one is
  const product = await db.getProduct(params.productId);

  return defer({
    product,
    reviews: reviewsPromise,
  });
}

export default function Product() {
  const { product, reviews } =
    useLoaderData<typeof loader>();
  // existing code
}

我們將 reviews Promise 傳遞給 defer,而不是等待它。這會告訴 Remix 將該 Promise 透過網路串流到瀏覽器。

就這樣!您現在應該將資料串流到瀏覽器了。

避免低效率的串流

在您等待任何其他 Promise 之前,務必先啟動延遲資料的 Promise,否則您將無法獲得串流的全部優勢。請注意此效率較低的程式碼範例之間的差異

export async function loader({
  params,
}: LoaderFunctionArgs) {
  const product = await db.getProduct(params.productId);
  // 👇 this won't initiate loading until `product` is done
  const reviewsPromise = db.getReviews(params.productId);

  return defer({
    product,
    reviews: reviewsPromise,
  });
}

處理伺服器逾時

當使用 defer 進行串流時,您可以透過 entry.server.tsx 檔案中的 <RemixServer abortDelay> 屬性(預設為 5 秒)告訴 Remix 等待延遲資料解析的時間長度,然後逾時。如果您目前沒有 entry.server.tsx 檔案,則可以透過 npx remix reveal entry.server 將其公開。您也可以使用此值透過 setTimeout 中止 React 的 renderToPipeableStream 方法。

const ABORT_DELAY = 5_000;

// ...

const { pipe, abort } = renderToPipeableStream(
  <RemixServer
    context={remixContext}
    url={request.url}
    abortDelay={ABORT_DELAY}
  />
  // ...
);

// ...

setTimeout(abort, ABORT_DELAY);

使用內容安全策略進行串流

串流的工作原理是在延遲的 Promise 解析時將 script 標籤插入 DOM 中。如果您的頁面包含 針對腳本的內容安全策略,您需要透過在 Content-Security-Policy 標頭中包含 script-src 'self' 'unsafe-inline' 來削弱安全策略,或將 nonce 新增至所有 script 標籤。

如果您正在使用 nonce,則需要包含在三個位置

  • Content-Security-Policy 標頭,如下所示:Content-Security-Policy: script-src 'nonce-secretnoncevalue'
  • <Scripts /><ScrollRestoration /><LiveReload /> 元件,如下所示:<Scripts nonce="secretnoncevalue" />
  • 在您呼叫 renderToPipeableStreamentry.server.ts 中,如下所示
const { pipe, abort } = renderToPipeableStream(
  <RemixServer
    context={remixContext}
    url={request.url}
    abortDelay={ABORT_DELAY}
  />,
  {
    nonce: "secretnoncevalue",
    /* ...remaining fields */
  }
);

這將確保任何延遲的 script 標籤都包含 nonce 值。

文件和範例授權於 MIT