BunReact服务器端渲染 BunReactSSR

这个技能是关于使用 Bun 运行时和 React 框架进行服务器端渲染(SSR),用于构建高性能、SEO友好的 Web 应用。它包括设置 SSR、流式渲染、数据获取、路由、CSS 处理、开发和优化等关键技术。关键词:Bun, React, SSR, 服务器端渲染, 前端开发, 性能优化, 流式渲染, 数据获取, 路由, CSS 处理, 热重载, 生产构建, SEO 优化。

前端开发 0 次安装 0 次浏览 更新于 3/8/2026

名称: Bun React SSR 描述: 当使用 Bun 构建服务器端渲染的 React 应用时使用,包括流式 SSR、hydration、renderToString 或无需框架的自定义 SSR。 版本: 1.0.0

Bun React SSR

使用 Bun 构建自定义服务器端渲染的 React 应用。

快速开始

# 初始化项目
mkdir my-ssr-app && cd my-ssr-app
bun init

# 安装依赖
bun add react react-dom
bun add -D @types/react @types/react-dom

基本 SSR 设置

服务器入口

// src/server.tsx
import { renderToString } from "react-dom/server";
import App from "./App";

Bun.serve({
  port: 3000,
  async fetch(req) {
    const url = new URL(req.url);

    // 提供静态文件
    if (url.pathname.startsWith("/static/")) {
      const file = Bun.file(`./public${url.pathname}`);
      if (await file.exists()) {
        return new Response(file);
      }
    }

    // 渲染 React 应用
    const html = renderToString(<App url={url.pathname} />);

    return new Response(
      `<!DOCTYPE html>
      <html>
        <head>
          <meta charset="utf-8">
          <meta name="viewport" content="width=device-width, initial-scale=1">
          <title>React SSR</title>
        </head>
        <body>
          <div id="root">${html}</div>
          <script src="/static/client.js"></script>
        </body>
      </html>`,
      {
        headers: { "Content-Type": "text/html" },
      }
    );
  },
});

console.log("服务器运行在 http://localhost:3000");

客户端入口

// src/client.tsx
import { hydrateRoot } from "react-dom/client";
import App from "./App";

hydrateRoot(
  document.getElementById("root")!,
  <App url={window.location.pathname} />
);

React 应用

// src/App.tsx
interface AppProps {
  url: string;
}

export default function App({ url }: AppProps) {
  return (
    <div>
      <h1>使用 Bun 的 React SSR</h1>
      <p>当前路径: {url}</p>
      <button onClick={() => alert("Hydrated!")}>点击我</button>
    </div>
  );
}

构建客户端包

// build.ts
await Bun.build({
  entrypoints: ["./src/client.tsx"],
  outdir: "./public/static",
  target: "browser",
  minify: true,
  splitting: true,
});
# 构建客户端
bun run build.ts

# 启动服务器
bun run src/server.tsx

流式 SSR

// src/server-streaming.tsx
import { renderToReadableStream } from "react-dom/server";
import App from "./App";

Bun.serve({
  port: 3000,
  async fetch(req) {
    const url = new URL(req.url);

    const stream = await renderToReadableStream(
      <App url={url.pathname} />,
      {
        bootstrapScripts: ["/static/client.js"],
        onError(error) {
          console.error(error);
        },
      }
    );

    // 等待 shell 准备就绪(Suspense 边界)
    await stream.allReady;

    return new Response(stream, {
      headers: { "Content-Type": "text/html" },
    });
  },
});

使用 Suspense

// src/App.tsx
import { Suspense } from "react";

function SlowComponent() {
  // 这将是一个数据获取组件
  return <div>已加载!</div>;
}

export default function App({ url }: { url: string }) {
  return (
    <html>
      <head>
        <title>流式 SSR</title>
      </head>
      <body>
        <div id="root">
          <h1>快速外壳</h1>
          <Suspense fallback={<div>加载中...</div>}>
            <SlowComponent />
          </Suspense>
        </div>
      </body>
    </html>
  );
}

数据获取

服务器端数据

// src/server.tsx
import { renderToString } from "react-dom/server";
import { Database } from "bun:sqlite";
import App from "./App";

const db = new Database("data.sqlite");

Bun.serve({
  async fetch(req) {
    const url = new URL(req.url);

    // 在服务器端获取数据
    const users = db.query("SELECT * FROM users").all();

    const html = renderToString(
      <App url={url.pathname} initialData={{ users }} />
    );

    return new Response(
      `<!DOCTYPE html>
      <html>
        <head><title>SSR</title></head>
        <body>
          <div id="root">${html}</div>
          <script>
            window.__INITIAL_DATA__ = ${JSON.stringify({ users })};
          </script>
          <script src="/static/client.js"></script>
        </body>
      </html>`,
      { headers: { "Content-Type": "text/html" } }
    );
  },
});

客户端 Hydration

// src/client.tsx
import { hydrateRoot } from "react-dom/client";
import App from "./App";

const initialData = (window as any).__INITIAL_DATA__;

hydrateRoot(
  document.getElementById("root")!,
  <App url={window.location.pathname} initialData={initialData} />
);

路由

简单路由

// src/Router.tsx
import { useState, useEffect } from "react";

interface Route {
  path: string;
  component: React.ComponentType;
}

interface RouterProps {
  routes: Route[];
  initialPath: string;
}

export function Router({ routes, initialPath }: RouterProps) {
  const [path, setPath] = useState(initialPath);

  useEffect(() => {
    const handlePopState = () => setPath(window.location.pathname);
    window.addEventListener("popstate", handlePopState);
    return () => window.removeEventListener("popstate", handlePopState);
  }, []);

  const route = routes.find((r) => r.path === path);
  const Component = route?.component || NotFound;

  return <Component />;
}

export function Link({ href, children }: { href: string; children: React.ReactNode }) {
  const handleClick = (e: React.MouseEvent) => {
    e.preventDefault();
    window.history.pushState({}, "", href);
    window.dispatchEvent(new PopStateEvent("popstate"));
  };

  return <a href={href} onClick={handleClick}>{children}</a>;
}

function NotFound() {
  return <h1>404 - 未找到</h1>;
}

CSS 处理

内联样式

const html = renderToString(<App />);

return new Response(
  `<!DOCTYPE html>
  <html>
    <head>
      <style>${await Bun.file("./src/styles.css").text()}</style>
    </head>
    <body>
      <div id="root">${html}</div>
    </body>
  </html>`,
  { headers: { "Content-Type": "text/html" } }
);

外部样式表

// 构建 CSS
await Bun.build({
  entrypoints: ["./src/styles.css"],
  outdir: "./public/static",
});

// 在 HTML 中链接
`<link rel="stylesheet" href="/static/styles.css">`

开发设置

热重载开发

// dev.ts
import { watch } from "fs";

const srcDir = "./src";
let serverProcess: Subprocess | null = null;

async function startServer() {
  serverProcess?.kill();
  serverProcess = Bun.spawn(["bun", "run", "src/server.tsx"], {
    stdout: "inherit",
    stderr: "inherit",
  });
}

// 监视变化
watch(srcDir, { recursive: true }, async (event, filename) => {
  console.log(`检测到变化: ${filename}`);
  await startServer();
});

await startServer();
console.log("开发服务器正在监视...");

生产构建

// build-prod.ts

// 构建客户端
await Bun.build({
  entrypoints: ["./src/client.tsx"],
  outdir: "./dist/public/static",
  target: "browser",
  minify: true,
  splitting: true,
  sourcemap: "external",
});

// 构建服务器
await Bun.build({
  entrypoints: ["./src/server.tsx"],
  outdir: "./dist",
  target: "bun",
  minify: true,
});

console.log("构建完成!");

常见错误

错误 原因 修复
Hydration 不匹配 服务器/客户端 HTML 不同 检查初始状态
document 未定义 SSR 访问 DOM 使用 typeof window 防护
不能使用钩子 钩子用在组件外部 检查组件结构
无样式内容闪烁 CSS 未加载 内联关键 CSS

性能提示

  1. 使用流式 SSR 以更快 TTFB
  2. 内联关键 CSS 在首屏之上
  3. 代码分割 客户端包
  4. 缓存渲染的 HTML 对于静态页面
  5. 使用 Suspense 用于渐进加载

何时加载参考

加载 references/streaming-patterns.md 当:

  • 复杂的 Suspense 边界
  • 选择性 hydration
  • 渐进增强

加载 references/caching.md 当:

  • HTML 缓存策略
  • CDN 集成
  • 边缘渲染