You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

Remix defer()基础示例不符合预期:等待所有Promise完成才传输数据

问题原因与解决方案

你的代码逻辑本身是正确的,但页面会等待3秒后同时渲染两个内容,核心原因是服务器端渲染未启用流式传输,导致Remix必须等待所有deferred数据加载完成后,才会发送完整的HTML页面到浏览器。

解决步骤

1. 替换entry.server.tsx为流式渲染实现

Remix默认的非流式渲染(如renderToString)会阻塞页面输出,直到所有Suspense包裹的异步数据加载完成。你需要确保entry.server.tsx使用React的流式渲染API(renderToPipeableStream或renderToReadableStream)。

替换为以下代码:

import { PassThrough } from "node:stream";
import type { AppLoadContext, EntryContext } from "@remix-run/node";
import { createReadableStreamFromReadable } from "@remix-run/node";
import { RemixServer } from "@remix-run/react";
import { isbot } from "isbot";
import { renderToPipeableStream } from "react-dom/server";

const ABORT_DELAY = 5_000;

export default function handleRequest(
  request: Request,
  responseStatusCode: number,
  responseHeaders: Headers,
  remixContext: EntryContext,
  loadContext: AppLoadContext
) {
  return isbot(request.headers.get("user-agent") || "")
    ? handleBotRequest(request, responseStatusCode, responseHeaders, remixContext)
    : handleBrowserRequest(request, responseStatusCode, responseHeaders, remixContext);
}

function handleBotRequest(
  request: Request,
  responseStatusCode: number,
  responseHeaders: Headers,
  remixContext: EntryContext
) {
  return new Promise((resolve, reject) => {
    let shellRendered = false;
    const { pipe, abort } = renderToPipeableStream(
      <RemixServer context={remixContext} url={request.url} />,
      {
        onAllReady() {
          shellRendered = true;
          const body = new PassThrough();
          const stream = createReadableStreamFromReadable(body);

          responseHeaders.set("Content-Type", "text/html");

          resolve(
            new Response(stream, {
              headers: responseHeaders,
              status: responseStatusCode,
            })
          );

          pipe(body);
        },
        onShellError(error: unknown) {
          reject(error);
        },
        onError(error: unknown) {
          responseStatusCode = 500;
          if (shellRendered) console.error(error);
        },
      }
    );

    setTimeout(abort, ABORT_DELAY);
  });
}

function handleBrowserRequest(
  request: Request,
  responseStatusCode: number,
  responseHeaders: Headers,
  remixContext: EntryContext
) {
  return new Promise((resolve, reject) => {
    let shellRendered = false;
    const { pipe, abort } = renderToPipeableStream(
      <RemixServer context={remixContext} url={request.url} />,
      {
        onShellReady() {
          shellRendered = true;
          const body = new PassThrough();
          const stream = createReadableStreamFromReadable(body);

          responseHeaders.set("Content-Type", "text/html");

          resolve(
            new Response(stream, {
              headers: responseHeaders,
              status: responseStatusCode,
            })
          );

          pipe(body);
        },
        onShellError(error: unknown) {
          reject(error);
        },
        onError(error: unknown) {
          responseStatusCode = 500;
          if (shellRendered) console.error(error);
        },
      }
    );

    setTimeout(abort, ABORT_DELAY);
  });
}

2. 确认依赖版本与服务器支持

  • 确保使用Remix v1.15+或v2+版本,这些版本原生支持defer的流式传输。
  • 确保你的服务器(如Remix默认Express服务器、Vercel、Netlify等)支持HTTP流式响应,自定义服务器需手动实现流式处理逻辑。

效果验证

修改后,页面会在请求发起约1秒后渲染quickAndCritical,再过2秒渲染slow,完全符合你的预期。

内容的提问来源于stack exchange,提问作者felixmp

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.07.05 11:35:17