串流允許您在內容可用時立即傳遞,而不是等待頁面的全部內容準備就緒,從而增強使用者體驗。
請確保您的託管供應商支援串流,並非所有供應商都支援。如果您的回應似乎沒有串流,這可能是原因。
串流資料有三個步驟
從一開始就準備就緒: 使用入門範本建立的 Remix 應用程式已預先設定為串流。
需要手動設定嗎?: 如果您的專案是從頭開始或使用較舊的範本,請驗證 entry.server.tsx
和 entry.client.tsx
是否具有串流支援。如果您沒有看到這些檔案,則表示您正在使用預設值且支援串流。如果您已建立自己的入口,以下是範本預設值供您參考
一個沒有串流的路由模組可能如下所示
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>
</>
);
}
即使在我們開始延遲資料之前,此程式碼也會繼續運作。最好先進行元件程式碼。如果您遇到問題,可以更容易追蹤問題所在。
現在我們的專案和路由元件都設定為串流資料,我們可以開始在載入器中延遲資料。我們將使用 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" />
renderToPipeableStream
的 entry.server.ts
中,如下所示const { pipe, abort } = renderToPipeableStream(
<RemixServer
context={remixContext}
url={request.url}
abortDelay={ABORT_DELAY}
/>,
{
nonce: "secretnoncevalue",
/* ...remaining fields */
}
);
這將確保任何延遲的 script 標籤都包含 nonce 值。