Bun SSR开发:如何在React Layout组件中使用doctype与html标签?
在React SSR(Bun环境)中正确处理Layout里的doctype与html标签
问题根源
你遇到的错误本质是JSX语法不支持直接嵌入<!doctype html>声明:
- JSX解析器会将所有
<开头的内容视为JSX元素,而<!doctype html>是HTML文档声明,不是合法的JSX元素结构,因此触发语法解析错误(比如“预期标识符却找到'!'”)。 - 由于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
相关产品推荐
相关产品推荐

