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

Bun SSR开发:如何在React Layout组件中使用doctype与html标签?

在React SSR(Bun环境)中正确处理Layout里的doctype与html标签

问题根源

你遇到的错误本质是JSX语法不支持直接嵌入<!doctype html>声明:

  1. JSX解析器会将所有<开头的内容视为JSX元素,而<!doctype html>是HTML文档声明,不是合法的JSX元素结构,因此触发语法解析错误(比如“预期标识符却找到'!'”)。
  2. 由于doctype的语法错误,解析器无法正确识别后续的标签层级,进而出现「JSX fragment has no corresponding closing tag」的误报。

正确解决方案

不要在Layout组件的JSX中直接写doctype,而是将doctype单独抽离到SSR渲染的入口逻辑中输出,Layout只负责渲染<html>及内部的页面结构。

步骤1:修正Layout组件

去掉多余的Fragment包裹和doctype,专注于页面的基础HTML结构:

import type { ReactNode } from "react"

export default (props: { children : ReactNode }) => {
    return (
        <html>
            <head>
                <meta charset="UTF-8" />
                <meta name="viewport" content="width=device-width, initial-scale=1.0" />
                <link href="./output.css" rel="stylesheet" />
            </head>
            <body>
                {props.children}
            </body>
        </html>
    )
}

步骤2:在SSR入口中添加doctype

根据你使用的React SSR渲染方式(renderToString或renderToReadableStream),在最终输出的HTML前拼接doctype:

方式1:使用renderToString(简单同步渲染)

import { renderToString } from "react-dom/server";
import Layout from "./components/Layout";
import { render as renderIndex } from "./pages/Index";

Bun.serve({
  fetch(req) {
    // 渲染页面组件
    const pageContent = renderIndex();
    // 渲染Layout包裹的内容为字符串
    const htmlContent = renderToString(<Layout>{pageContent}</Layout>);
    // 拼接doctype和渲染结果
    const fullHtml = `<!doctype html>\n${htmlContent}`;
    
    return new Response(fullHtml, {
      headers: { "Content-Type": "text/html; charset=utf-8" },
    });
  },
});

方式2:使用renderToReadableStream(流式渲染,推荐生产环境)

import { renderToReadableStream } from "react-dom/server";
import Layout from "./components/Layout";
import { render as renderIndex } from "./pages/Index";

Bun.serve({
  async fetch(req) {
    const pageContent = renderIndex();
    // 流式渲染Layout和页面内容
    const reactStream = await renderToReadableStream(
      <Layout>{pageContent}</Layout>
    );
    
    // 创建包含doctype的响应流
    const fullStream = new ReadableStream({
      async start(controller) {
        // 先写入doctype
        controller.enqueue(new TextEncoder().encode("<!doctype html>\n"));
        // 转发React渲染的流内容
        const reader = reactStream.getReader();
        while (true) {
          const { done, value } = await reader.read();
          if (done) break;
          controller.enqueue(value);
        }
        controller.close();
      },
    });
    
    return new Response(fullStream, {
      headers: { "Content-Type": "text/html; charset=utf-8" },
    });
  },
});

额外说明

  • 不需要在Layout中使用Fragment(<>...</>),因为<html>本身就是根元素,Fragment在这里是多余的。
  • 确保所有JSX元素都正确闭合(比如<meta>标签要写成<meta ... />,符合JSX语法要求)。

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.07.01 11:57:24