用 @scalar/server-side-rendering 为 Scalar API 参考文档开启服务端渲染与静态生成
【免费下载链接】scalarScalar is an open-source API platform: 🌐 Modern REST API Client 📖 Beautiful API References ✨ 1st-Class OpenAPI/Swagger Support项目地址: https://gitcode.com/GitHub_Trending/sc/scalar
本篇技术指南讲解 Scalar 官方提供的@scalar/server-side-rendering包:如何在任意 JavaScript/TypeScript 服务端将 Scalar API 参考文档预渲染为完整 HTML,再通过独立 JS bundle 在客户端水合(hydrate)恢复完整交互能力。读完本文,你将掌握服务端渲染的接入方式、全部配置选项、静态站点生成流程,以及源码层面的实现原理与安全机制,可直接在自己的 Node.js 服务或静态托管平台上落地。
为什么需要服务端渲染
Scalar API 参考文档默认是纯客户端渲染(CSR):浏览器加载 JS 后再解析 OpenAPI 文档并渲染界面。这种方式有几个明显短板:
- 首屏空白:用户等待 JS 下载、解析、执行期间看不到任何内容;
- SEO 不佳:搜索引擎爬虫难以抓取到运行前的 HTML,页面描述、标题等内容无法被正确索引;
- 内容瞬时性差:首帧无法立刻呈现接口文档正文。
@scalar/server-side-rendering在服务端把 API 参考文档预先渲染成完整 HTML 字符串返回给客户端,首次绘制即可看到内容;同时注入水合脚本,浏览器加载独立 bundle 后恢复搜索、请求测试等全部交互能力。它不依赖任何特定框架,适用于任何 JavaScript/TypeScript 服务端(Hono、Express、Fastify 等),也可以脱离服务器在构建期生成静态站点。该包位于仓库的 packages/server-side-rendering 目录,其说明文档为 documentation/server-side-rendering.md。
安装
npm install @scalar/server-side-rendering该包声明了node >= 22的运行时要求(见 packages/server-side-rendering/package.json),并依赖@scalar/api-reference、@unhead/vue与vue。它导出两个核心 API:renderApiReference与getJsAsset(完整导出清单见 packages/server-side-rendering/src/index.ts)。
基本用法:启动时渲染一次,从内存持续服务
SSR 的核心思路是:渲染代价只在启动时支付一次,之后把生成的 HTML 与 JS bundle 字符串缓存起来,每次请求直接返回,避免逐请求重复渲染。
import { getJsAsset, renderApiReference } from '@scalar/server-side-rendering' // 启动时渲染一次 HTML const html = await renderApiReference({ pageTitle: 'My API Reference', config: { url: 'https://registry.scalar.com/@scalar/apis/galaxy?format=json', }, }) // 启动时读取一次独立 JS bundle const js = getJsAsset() // 服务端返回预渲染 HTML app.get('/scalar', (c) => c.html(html)) // 返回用于客户端水合的 JS bundle app.get('/scalar/scalar.js', (c) => c.body(js, { headers: { 'content-type': 'application/javascript' }, }), )config.url指向 OpenAPI 文档地址;你也可以用config.content直接内联 OpenAPI 内容(见下文静态生成示例),这两种方式与所有 Scalar 集成完全一致。
返回的 HTML 文档包含什么
renderApiReference返回一个完整的<!doctype html>文档,从源码 packages/server-side-rendering/src/ssr.ts 可以看出其内部结构:
<!doctype html> <html lang="en"> <head> <meta charset="utf-8"> <meta name="viewport" content="width=device-width, initial-scale=1"> <title>My API Reference</title> <style>/* 内联 CSS */</style> </head> <body class="dark-mode"> <script>/* 颜色模式检测脚本 */</script> <div id="app"><!-- 服务端预渲染的 Vue HTML --></div> <script src="/scalar/scalar.js"></script> <script>Scalar.createApiReference('#app', {...})</script> </body> </html>具体包含四部分能力:
- 内联 CSS:从
@scalar/api-reference包中读取内置样式并内联进<head>,避免未样式化内容闪烁(FOUC); - 颜色模式检测脚本:在首次绘制前根据用户偏好决定
<body>上的dark-mode/light-mode类; - 预渲染 HTML:
<div id="app">内是 Vue 服务端渲染出的完整参考文档内容,首屏立即可见; - 水合脚本:加载独立 bundle 后调用
Scalar.createApiReference('#app', config)在客户端接管并恢复交互。
颜色模式检测的优先级
generateBodyScript(见 ssr.ts)生成的内联脚本按如下优先级确定明暗模式:
forceDarkModeState配置——显式强制'dark'或'light',此时不做任何运行时检测,直接写死 body 类;localStorage中的colorMode——用户此前保存的偏好;- 系统
matchMedia('(prefers-color-scheme: dark)')偏好; darkMode布尔配置——作为显式默认值;- 兜底为 light 模式。
服务端初始渲染的 body 类由getInitialBodyClass决定:forceDarkModeState优先,其次darkMode,未配置时默认dark-mode,再由运行时脚本用 localStorage/系统偏好精修。
完整可运行的 Hono 参考实现
仓库自带一个可直接pnpm --filter @scalar/server-side-rendering dev启动的 playground(见 packages/server-side-rendering/playground/server.ts),展示了生产可用的细节——给 JS bundle 加上长缓存头:
import { serve } from '@hono/node-server' import { Hono } from 'hono' import { getJsAsset, renderApiReference } from '../src' const port = Number(process.env.PORT) || 5173 // 启动时渲染一次 const html = await renderApiReference({ config: { url: 'https://registry.scalar.com/@scalar/apis/galaxy?format=json', }, }) const js = getJsAsset() const app = new Hono() app.get('/', (c) => c.html(html)) app.get('/scalar/scalar.js', (c) => { return c.body(js, { headers: { 'content-type': 'application/javascript', 'cache-control': 'public, max-age=31536000, immutable', }, }) }) serve({ fetch: app.fetch, port }, () => { console.log(`Server started at http://localhost:${port}`) })由于 bundle 内容在构建期即已固定,为它配置immutable长缓存可以显著降低重复访问的带宽成本。
选项(Options)
renderApiReference接受一个选项对象:
| 选项 | 类型 | 默认值 | 说明 |
|---|---|---|---|
config | AnyApiReferenceConfiguration | — | API 参考文档的配置,与所有 Scalar 集成的配置一致 |
pageTitle | string | 'Scalar API Reference' | HTML 文档的页面标题 |
css | string | 内置样式 | 覆盖默认 CSS |
cdn | string | '/scalar/scalar.js' | 独立 JS bundle 被服务的 URL 路径 |
config接受与所有 Scalar 集成相同的配置对象,完整的配置项说明见 documentation/configuration.md;cdn必须与你在服务端实际暴露 bundle 的路由保持一致(默认/scalar/scalar.js),水合脚本才能正确加载;pageTitle、cdn等用户输入在写入 HTML 前会经过 HTML 转义(见escapeHtmlAttribute),防止注入。
水合配置的序列化细节
水合脚本中的配置不是简单的JSON.stringify。源码中的serializeConfigToJs(见 ssr.ts)做了两件关键事情:
- 保留顶层函数:
onLoaded等顶层函数属性、以及顶层数组中包含的函数,会被以源码字符串形式写回水合脚本,保证与客户端渲染行为一致;但嵌套在对象深处的函数无法序列化,会直接抛出错误而不是静默丢失(测试用例见 packages/server-side-rendering/src/ssr.test.ts); - 防脚本逃逸:所有配置值在嵌入
<script>前会转义<、>、&、U+2028/U+2029 等危险字符,函数源码也会中和</与<!--序列,杜绝用户提供的文档内容关闭 script 标签造成 XSS。相关测试覆盖了多种恶意输入场景。
静态站点生成(SSG)
如果不想维护一个常驻的 Node.js 服务器,也可以在构建期运行渲染器并把输出写入磁盘,生成的 HTML 与 JS 可直接部署到任何静态托管平台。
先把 OpenAPI 文档保存为openapi.json,再创建generate-docs.mjs:
import { mkdir, readFile, writeFile } from 'node:fs/promises' import { getJsAsset, renderApiReference } from '@scalar/server-side-rendering' const content = JSON.parse(await readFile('openapi.json', 'utf8')) const html = await renderApiReference({ pageTitle: 'My API Reference', config: { content }, cdn: './scalar.js', }) await mkdir('dist/docs', { recursive: true }) await writeFile('dist/docs/index.html', html) await writeFile('dist/docs/scalar.js', getJsAsset())这里用config.content内联 OpenAPI 内容,这样生成的页面不依赖任何外部请求即可完成渲染。注意cdn: './scalar.js'使用了相对路径,使 bundle 与 HTML 同目录存放。
生成并本地预览:
node generate-docs.mjs npx serve dist然后在预览服务器打开/docs/,并把dist目录内容部署到你的静态托管平台。
几个值得注意的部署细节:
- 保留尾斜杠:访问
/docs/时要保留结尾的/,这样相对路径./scalar.js才能正确解析为/docs/scalar.js;如果托管平台不会自动重定向目录 URL,请自行配置重定向,或在cdn中使用绝对资源 URL; - 默认 hash 路由:本示例使用 Scalar 默认的 hash 路由,操作链接都停留在同一个 HTML 文件内,托管平台无需为嵌套路由配置 fallback;
- JS 的职责边界:预渲染 HTML 已包含完整文档内容,JS 负责启用搜索与请求测试等交互能力;
- 内容变更时重新生成:当 API 描述变化时,重新运行脚本并重新部署即可;
- 请求目标地址:请使用 OpenAPI 文档
servers数组中的绝对 URL 来指向你的真实 API,而不是静态托管站点本身。
Nuxt 用户请直接使用官方集成
如果你在使用 Nuxt 应用,应改用 Nuxt 静态站点生成指南,而不是本包——Nuxt 自己管理渲染、payload 与 JS 资源,无需(也不应)再叠加一层@scalar/server-side-rendering。
源码实现原理速览
理解底层实现有助于排查问题与做深度定制:
- 渲染链路:
renderApiReference内部通过createSSRApp创建一个 Vue SSR 应用,渲染根组件为@scalar/api-reference包的ApiReference组件(见 ssr.ts),再经vue/server-renderer的renderToString输出 HTML,同时用@unhead/vue管理<head>元信息(标题、meta 描述等)。配置为数组形式时,会取第一个元素渲染,保证与客户端行为一致(对应测试见 packages/server-side-rendering/src/ssr-hydration-config.test.ts); - 元信息注入:配置中的
metaData会通过useServerSeoMeta注入<head>,这对 SEO 场景尤为重要——页面描述等元信息直接出现在服务端返回的 HTML 中,测试用例renders metadata from config in SSR head tags对此有验证; - CSS 读取:内置样式通过
require.resolve('@scalar/api-reference/style.css')读取并以模块级缓存(_cachedCss)避免重复 IO; - JS bundle 定位:
getJsAsset读取@scalar/api-reference包package.json中的browser字段定位独立 bundle 文件,读取后缓存(_cachedJs);若包尚未构建或缺少browser字段会抛出明确错误(见 ssr.ts); - 安全防护:从标题转义到配置序列化再到函数源码注入,全链路都有针对
<script>逃逸的防御,测试文件 ssr.test.ts 中的prevents script breakout、escapes script-breaking sequences等用例直接验证了这些行为。
小结
@scalar/server-side-rendering让 Scalar API 参考文档从"纯客户端渲染"升级为"服务端预渲染 + 客户端水合",同时覆盖动态服务与静态站点两种部署形态。接入只需三个步骤:启动时调用renderApiReference生成 HTML、调用getJsAsset读取 bundle、按cdn配置暴露对应路由。对于追求 SEO 与首屏体验的 API 文档站点,这是一条低成本的落地路径。
【免费下载链接】scalarScalar is an open-source API platform: 🌐 Modern REST API Client 📖 Beautiful API References ✨ 1st-Class OpenAPI/Swagger Support项目地址: https://gitcode.com/GitHub_Trending/sc/scalar
创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考