发布于 2026-01-06 2 阅读
0

使用 React 从零开始构建服务器端渲染 (SSR) DEV 的全球展示挑战赛,由 Mux 呈现:展示你的项目!

使用 React 从零开始构建服务器端渲染 (SSR)。

由 Mux 赞助的 DEV 全球展示挑战赛:展示你的项目!

Woovi的一项核心功能是我们的支付链接。它让您可以轻松地在网络上的任何地方销售产品,无需网站或其他任何设备。只需发送链接即可收款。

为什么要使用服务器端渲染?

如何确保用户在网络上分享此链接时获得良好的体验?我希望显示与该费用相关的二维码,并且在网络上的任何位置发送该链接时,都能显示与该 HTML 页面相关的 OG 和元标签。

对于客户端渲染的页面,由于无法在访问页面之前存储这些引用,因此无法实现这一点。

这就是我们选择使用 SSR 而非 CSR 来显示支付链接的原因。我们希望确保无论在网络上的哪个位置,当您分享此链接时,都能显示:正确的支付链接标题、作为原始图像的二维码,以及其他动态元数据。

WhatsApp 中 OG 标签的示例

服务器启动

第一步是创建入口点,页面将从这里渲染。在本例中,我们将使用koa框架。

// index.ts
import Router from '@koa/router';
import Koa from 'koa';
import bodyparser from 'koa-bodyparser';

const router = new Router();
const app = new Koa();

router.get('/(.*)', async (ctx) => {
  ctx.status = 200;
  ctx.body = 'OK';
});

app.use(bodyparser());
app.use(router.routes());
app.use(router.allowedMethods());

export default app;
Enter fullscreen mode Exit fullscreen mode

我们在这里所做的是:创建一个新的端点,用于捕获所有请求并返回一个200包含响应OK体的响应。如果用户访问 `<RequestMail>`/或 `<RequestMail> /foo/bar`,则会得到相同的响应。

现在,要运行服务器并打开一个端口以访问服务器,您可以运行以下代码:

// index.ts
import http from 'http';

const currentHandler = app.callback();
const server = http.createServer(app.callback());

server.listen(4000, (error) => {
  console.log(error);
});
Enter fullscreen mode Exit fullscreen mode

现在,我们可以运行所有这些服务器并访问该端口4000。如果您想进行测试,请使用tsup任何您想要的方式构建它,例如ts-node

渲染用户界面

现在,我们需要一种方法来渲染我们的 React 组件,对吧?所以我们的想法是使用现有的解决方案react-dom/server并进行处理。让我们看看下面的代码:

// index.ts
import Koa from 'koa';
import Router from '@koa/router';
import http from 'http';
import { renderToPipeableStream } from 'react-dom/server';

import { App } from './App';

const router = new Router();
const app = new Koa();

router.get('/(.*)', async (ctx) => {
  let didError = false;
  try {
    // Wraps into a promise to force Koa to wait for the render to finish
    return new Promise((_resolve, reject) => {
      const { pipe, abort } = renderToPipeableStream(
        <App />,
        {
          bootstrapModules: ['./client.js'],
          onShellReady() {
            ctx.respond = false;
            ctx.status = didError ? 500 : 200;
            ctx.set('Content-Type', 'text/html');
            pipe(ctx.res);
            ctx.res.end();
          },
          onShellError() {
            ctx.status = 500;
            abort();
            didError = true;
            ctx.set('Content-Type', 'text/html');
            ctx.body = '<!doctype html><p>Loading...</p><script src="clientrender.js"></script>';
            reject();
          },
          onError(error) {
            didError = true;
            console.error(error);
            reject();
          }
        },
      );

      setTimeout(() => {
        abort();
      }, 10_000);
    })
  } catch (err) {
    console.log(err);
    ctx.status = 500;
    ctx.body = 'Internal Server Error';
  }
});

app.use(router.routes());
app.use(router.allowedMethods());

const server = http.createServer(app.callback());

server.listen(3000, () => {
  console.log('Server listening on port 3000');
});
Enter fullscreen mode Exit fullscreen mode

我们在这里所做的是:使用该onShellReady函数将数据块插入到可写流中,并完成响应。它将解析 Suspense 组件并将其显示给最终用户。

我们需要将此流程包装在一个中,Promise以确保 Koa 将等待到 promise 解析完成,这意味着可写流也已解析完成,从而返回正确的 HTML 代码。

组件App就是你的根项目,在这个例子中,我只是像你在这里Hello, world!看到的那样写了一段代码。但你可以插入任何你想要的内容。

您可以在这里看到代码。

综上所述

通过这种简单的设置,您可以拥有一个强大的工具,为我们的最终用户提供更好的体验,包括更好的 SEO、更快的加载速度以及 SSR 在某些特定情况下为我们带来的其他有用功能。

但一个合理的问题是:为什么不使用像Handlebars这样的模板呢?对于 Woovi 内部的使用场景来说,模板帮不了我们,因为我们需要做到两点:复用我们的设计系统,并确保在支付链接中使用 GraphQL。

在 Woovi 内部,我们所有的代码库都使用Relay 客户端框架,并通过 GraphQL 进行管理。为了确保最终用户获得最佳用户体验,我们在支付链接中提供了一些实用功能,例如支付完成后实时更新。所有这些都由 GraphQL 处理,在我们的用例中,模板无法解决这些问题。

本文概述了我们内部如何处理服务器端渲染(SSR)相关事宜。要使代码达到生产就绪状态,还需要逐步完成一些工作。

关于我们

Woovi 是一家初创公司,致力于让消费者能够自由选择支付方式。为了实现这一点,Woovi为商家提供即时支付解决方案,方便他们接收订单。

如果你想迎接像从零开始使用服务器端渲染 + GraphQL 创建强大解决方案这样的挑战,那就加入我们吧!

我们正在 招聘

文章来源:https://dev.to/woovi/server-side-rendering-ssr-from-scratch-with-react-19jm