news 2026/9/14 3:28:44

用 @scalar/server-side-rendering 为 Scalar API 参考文档开启服务端渲染与静态生成

作者头像

张小明

前端开发工程师

1.2k 24
文章封面图
用 @scalar/server-side-rendering 为 Scalar API 参考文档开启服务端渲染与静态生成

用 @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/vuevue。它导出两个核心 API:renderApiReferencegetJsAsset(完整导出清单见 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)生成的内联脚本按如下优先级确定明暗模式:

  1. forceDarkModeState配置——显式强制'dark''light',此时不做任何运行时检测,直接写死 body 类;
  2. localStorage中的colorMode——用户此前保存的偏好;
  3. 系统matchMedia('(prefers-color-scheme: dark)')偏好;
  4. darkMode布尔配置——作为显式默认值;
  5. 兜底为 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接受一个选项对象:

选项类型默认值说明
configAnyApiReferenceConfigurationAPI 参考文档的配置,与所有 Scalar 集成的配置一致
pageTitlestring'Scalar API Reference'HTML 文档的页面标题
cssstring内置样式覆盖默认 CSS
cdnstring'/scalar/scalar.js'独立 JS bundle 被服务的 URL 路径
  • config接受与所有 Scalar 集成相同的配置对象,完整的配置项说明见 documentation/configuration.md;
  • cdn必须与你在服务端实际暴露 bundle 的路由保持一致(默认/scalar/scalar.js),水合脚本才能正确加载;
  • pageTitlecdn等用户输入在写入 HTML 前会经过 HTML 转义(见escapeHtmlAttribute),防止注入。

水合配置的序列化细节

水合脚本中的配置不是简单的JSON.stringify。源码中的serializeConfigToJs(见 ssr.ts)做了两件关键事情:

  1. 保留顶层函数onLoaded等顶层函数属性、以及顶层数组中包含的函数,会被以源码字符串形式写回水合脚本,保证与客户端渲染行为一致;但嵌套在对象深处的函数无法序列化,会直接抛出错误而不是静默丢失(测试用例见 packages/server-side-rendering/src/ssr.test.ts);
  2. 防脚本逃逸:所有配置值在嵌入<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-rendererrenderToString输出 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-referencepackage.json中的browser字段定位独立 bundle 文件,读取后缓存(_cachedJs);若包尚未构建或缺少browser字段会抛出明确错误(见 ssr.ts);
  • 安全防护:从标题转义到配置序列化再到函数源码注入,全链路都有针对<script>逃逸的防御,测试文件 ssr.test.ts 中的prevents script breakoutescapes 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),仅供参考

版权声明: 本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若内容造成侵权/违法违规/事实不符,请联系邮箱:809451989@qq.com进行投诉反馈,一经查实,立即删除!
网站建设 2026/9/14 3:28:11

稀疏表示与双立方插值结合的图像去噪MATLAB实现解析

简介&#xff1a;基于双立方插值与稀疏表示的图像去噪Matlab源码包&#xff0c;主要面向本科、硕士阶段从事图像处理算法教研与复现的学生和研究者。整套资源共289个文件&#xff0c;包括172张bmp测试图、41个m源码文件、16个c辅助文件以及mat数据文件等&#xff0c;压缩包体量…

作者头像 李华
网站建设 2026/9/14 3:28:04

SpringBoot+OneNet+MySQL水质监测系统实战

简介&#xff1a;本资源是一套面向计算机专业本科生的毕业设计实战项目&#xff0c;基于SpringBoot框架构建水质监测Web系统&#xff0c;整合MySQL数据库与OneNet云平台&#xff0c;解决河流水质数据的远程采集、可视化展示与远程指令下发等核心问题&#xff0c;适用于物联网We…

作者头像 李华
网站建设 2026/9/14 3:26:40

KKBox音乐推荐实战:特征工程与LightGBM排序全流程

简介&#xff1a;面向Kaggle音乐推荐挑战的完整代码包&#xff0c;聚焦KKBox歌曲推荐场景&#xff0c;适合对推荐系统、机器学习竞赛感兴趣的开发者、学生及数据科学学习者。zip压缩包内共39个文件&#xff0c;以Python脚本&#xff08;15个py&#xff09;和C源码&#xff08;7…

作者头像 李华
网站建设 2026/9/14 3:26:12

2026最新Codex下载安装全攻略:三渠道+全平台避坑指南

打开任何一个技术社区&#xff0c;输入“Codex下载地址”这个词&#xff0c;你大概率会得到一堆互相矛盾的答案&#xff1a;有人说从GitHub Releases拿解压包&#xff0c;有人说 npm install 一条命令搞定&#xff0c;还有人强调必须靠Homebrew才能装。到了2026年&#xff0c…

作者头像 李华
网站建设 2026/9/14 3:26:03

PSD转游戏UI自动化:从设计稿到Prefab的四段式管线

1. 为什么PSD转游戏UI不能靠“切图手动拼”1.1 传统工作流到底慢在哪游戏UI的生产流程&#xff0c;绝大多数团队到现在还是这么转的&#xff1a;美术在PSD里画好界面&#xff0c;切图导出PNG&#xff0c;然后发给客户端同学&#xff0c;客户端对着设计稿在引擎编辑器里手动摆放…

作者头像 李华