Nitro + Takumi 实战:用一条服务器路由动态生成 Open Graph 社交分享图
【免费下载链接】nitroNext Generation Server Toolkit. Create web servers with everything you need and deploy them wherever you prefer.项目地址: https://gitcode.com/GitHub_Trending/ni/nitro
本文基于 Nitro 仓库中的官方示例 examples/takumi 展开,完整讲解如何借助 Takumi(takumi-js)在 Nitro 路由中按请求实时渲染 1200×630 的 Open Graph 图片,并把生成的图片接入og:image元信息、提供浏览器端的"边输入边出图"实时预览。读完本文,你将掌握从路由处理器编写、响应头上报渲染耗时,到页面侧引用与性能优化的完整链路。
示例概览:一条路由产出动态分享图
在社交平台(微信、X/Twitter、Facebook 等)中,链接被分享时会抓取页面<head>里的og:image元信息来生成预览卡片。静态图片无法随链接参数变化,而动态生成则可以让每一条分享链接都带上独一无二的卡片。
本示例的核心思路是:
- 在 Nitro 的
routes/目录下创建一条路由og.png.ts,它对应的 URL 路径是/og.png; - 路由处理器从 URL 的
title、description查询参数读取文案,用 Takumi 的container/text帮助函数搭出节点树; - 用
ImageResponse把这棵节点树渲染成 PNG 图片并直接返回(Nitro 处理器可以返回Response,ImageResponse本身就是Response的子类); index.html通过og:image元信息指向该路由,抓取器每次访问都会拿到一张实时渲染的预览图,页面自身还实现了"输入即出图"的实时预览交互。
示例文件结构
仓库中该示例的完整目录如下:
examples/takumi/ ├── README.md # 示例说明(本文主体内容来源) ├── index.html # 演示页面:OG 元信息 + 实时预览 ├── nitro.config.ts # Nitro 配置:扫描 ./ 目录下的路由 ├── package.json # 依赖与 dev/build/preview 脚本 ├── tsconfig.json ├── vite.config.ts # Vite 与 Nitro 的衔接配置 ├── routes/ │ └── og.png.ts # 核心:动态生成 OG 图片的路由处理器 └── src/ └── styles.css # 演示页面样式(含暗色模式与徽标动画)服务端路由:routes/og.png.ts 逐步拆解
核心代码位于 examples/takumi/routes/og.png.ts,完整内容如下:
import { defineHandler } from "nitro"; import { container, text } from "takumi-js/helpers"; import ImageResponse from "takumi-js/response"; export default defineHandler(async ({ url }) => { const title = url.searchParams.get("title") ?? "Takumi + Nitro"; const description = url.searchParams.get("description") ?? "Render OG images from a Nitro route."; const start = performance.now(); const response = new ImageResponse( container({ style: { width: "100%", height: "100%", display: "flex", flexDirection: "column", justifyContent: "center", padding: "64px", backgroundImage: "linear-gradient(to bottom right, #fff1f2, #fecdd3)", }, children: [ text(title, { fontSize: 72, fontWeight: 700, color: "#111827" }), text(description, { fontSize: 42, fontWeight: 500, color: "#4b5563" }), ], }), { width: 1200, height: 630 } ); await response.ready; response.headers.set("Server-Timing", `render;dur=${(performance.now() - start).toFixed(1)}`); return response; });路由如何被识别:文件系统路由与serverDir
根据 Nitro 的路由机制(详见 docs/1.docs/5.routing.md),routes/目录下的文件会被自动映射为路由:文件名即路径。og.png.ts的文件名里自带.png扩展名,因此映射出的路径就是/og.png,返回的是一张 PNG 图片而非 HTML 页面。
文件系统路由的前提是在配置中声明扫描目录。本示例的 examples/takumi/nitro.config.ts 非常简单:
import { defineConfig } from "nitro"; export default defineConfig({ serverDir: "./", });serverDir: "./"表示以示例目录为服务端根目录进行扫描,于是routes/og.png.ts被识别为路由、src/styles.css等静态资源照常由 Vite 托管。注意:根据路由文档,serverDir(或scanDirs)默认不会自动扫描任何目录,这一步是必需的。
用 defineHandler 获得更好的类型推断
处理器使用了从nitro导入的defineHandler。相比裸函数,defineHandler在开发体验上的优势是更好的类型推断(见 docs/1.docs/5.routing.md 的对比示例)。这里通过解构拿到url,即可读取查询参数:
const title = url.searchParams.get("title") ?? "Takumi + Nitro"; const description = url.searchParams.get("description") ?? "Render OG images from a Nitro route.";??兜底保证了即使不带任何查询参数,路由也能渲染出默认文案,不会返回空图。
用 Takumi helpers 搭节点树,免去 JSX 配置
Takumi 提供container/text等帮助函数(来自takumi-js/helpers),以纯函数方式声明式地构建图片节点树,无需任何 JSX 或构建期转换配置:
container定义一个块级容器,通过style控制布局:width/height撑满画布、display: flex+flexDirection: column+justifyContent: center让内容垂直居中、padding: 64px留出边距、backgroundImage使用从#fff1f2到#fecdd3的粉色调渐变背景;text定义文本节点,children中依次放入标题与描述两行文本,并通过fontSize、fontWeight、color控制字体样式(72px 加粗深色标题 + 42px 中等字重的灰色描述,形成清晰的层级对比)。
构造 ImageResponse 并等待渲染完成
ImageResponse来自takumi-js/response,接收两参:第一参是根节点树,第二参是输出画布尺寸:
new ImageResponse(nodeTree, { width: 1200, height: 630 })1200×630 正是社交平台分享卡片的标准横图比例,与 index.html 中og:image:width/og:image:height的声明一一对应。
构造完成后立即await response.ready,等待图像实际渲染完毕,随后再向响应头写入Server-Timing:
response.headers.set("Server-Timing", `render;dur=${(performance.now() - start).toFixed(1)}`);Server-Timing是标准化的性能上报响应头,调用方(演示页面或curl)可以从中解析出本次渲染耗时。由于 Nitro 处理器可以直接返回Response,ImageResponse无需任何包装即可作为路由返回值。
渲染后端:Node 原生绑定与 Edge WASM 的自动选择
README 明确说明:Takumi 会根据部署目标自动选择渲染后端——Node preset 下使用原生绑定,Edge preset 下使用 WebAssembly,全程无需配置。仓库的 docs/pnpm-lock.yaml 可以佐证这一点:依赖树中同时存在@takumi-rs/core(按平台分发的darwin-arm64、linux-x64-gnu、win32-x64-msvc等原生二进制包)与@takumi-rs/wasm(WebAssembly 版本),这正是"同一套 API、多后端自动切换"的实现基础。对开发者而言,这意味着同一份og.png.ts代码既能跑在 Node 服务器上,也能无改动地部署到 Cloudflare Workers、Deno Deploy 等边缘运行时。
依赖与运行脚本
examples/takumi/package.json 内容如下:
{ "type": "module", "scripts": { "dev": "vite dev", "build": "vite build", "preview": "node .output/server/index.mjs" }, "devDependencies": { "nitro": "latest", "takumi-js": "^2.1.1", "vite": "latest" } }nitro提供路由、构建与部署能力,takumi-js提供图片渲染能力,vite驱动开发服务器与构建;- 项目以 ESM 运行(
"type": "module"); - 脚本约定:
dev启动开发服务器,build产出.output/,preview用 Node 直接运行构建产物。
页面侧引用:OG 元信息、预加载与防布局偏移
演示页面 examples/takumi/index.html 在<head>中把 Open Graph 标签指向动态路由,让抓取器拿到实时渲染的预览:
<meta property="og:type" content="website" /> <meta property="og:title" content="Takumi + Nitro" /> <meta property="og:description" content="Render OG images from a Nitro route." /> <meta property="og:image" content="/og.png?title=Takumi%20%2B%20Nitro&description=Render%20OG%20images%20from%20a%20Nitro%20route." /> <meta property="og:image:width" content="1200" /> <meta property="og:image:height" content="630" /> <meta name="twitter:card" content="summary_large_image" /> <meta name="twitter:image" content="/og.png" />要点解读:
og:image指向/og.png?title=...&description=...,把标题与描述编码进查询参数(空格编码为%20、+编码为%2B),抓取器访问时即触发一次实时渲染;- 显式声明
og:image:width/og:image:height,帮助抓取器在图片下载完成前就知道卡片尺寸; - 同时提供
twitter:card为summary_large_image,兼容 X/Twitter 的大图卡片; <head>中还有一条preload:
<link rel="preload" as="image" href="/og.png?title=Takumi%20%2B%20Nitro&description=Render%20OG%20images%20from%20a%20Nitro%20route." />preload让浏览器在解析 HTML 的同时就并行发起首次图片请求,提前开始渲染,缩短首屏等待。页面主体中的<img>则通过width="1200" height="630"属性与 CSS 中的aspect-ratio: 1200 / 630(见 examples/takumi/src/styles.css)预先占位,避免图片加载过程中的布局抖动(CLS)。
实时预览:前端如何"边输入边出图"
index.html底部内联脚本实现了完整的交互闭环:
- 页面加载时立即调用
loadPreview(preview.src)拉取初始图片; - 标题/描述两个输入框监听
input事件,经300ms 防抖(refreshDebounced)后触发refresh(); refresh()用URLSearchParams重新拼接/og.png?title=...&description=...,同步更新右上角"原始端点"链接endpointLink.href,再调用loadPreview;loadPreview通过fetch获取图片为Blob,用URL.createObjectURL写入<img>,并从响应的Server-Timing头中用正则/dur=([\d.]+)/解析出渲染毫秒数,显示在预览图右下角的徽标里(渲染中显示 "Generating…",完成显示 "N ms");- 通过
requestId递增标记 +if (id !== requestId) return丢弃过期响应,防止快速连续输入时旧请求覆盖新结果;替换blob:地址前还会URL.revokeObjectURL释放旧对象,避免内存泄漏。
徽标在 styles.css 中通过[data-pending]属性切换黄色脉冲动画,让"正在生成"的状态一目了然。
运行与验证
在仓库根目录或示例目录执行:
# 安装依赖 pnpm install # 启动开发服务器 pnpm dev # 生产构建并本地预览 pnpm build pnpm preview开发模式下访问http://localhost:3000/即可看到演示页面与实时预览。直接用命令行验证渲染端点与性能头:
curl -i "http://localhost:3000/og.png?title=Hello&description=From%20Nitro"响应应包含Content-Type: image/png、Server-Timing: render;dur=...等头部,配合-o保存后即可得到一张 1200×630 的 PNG 图片。README 还提示:标题旁的链接图标始终指向当前预览对应的原始端点,方便随时复制分享。
扩展思路与注意事项
- 文案自由度:路由把
title/description作为查询参数,这意味着任何链接分享场景都能携带自定义文案;也可以在此基础上升级为读取event.context.params的动态路由,或结合runtimeConfig提供站点级默认值; - 样式扩展:
text/container的style支持常规 CSS 属性(字号、字重、颜色、渐变背景等),示例本身已用渐变背景 + 双文本层级展示了常见卡片风格;演示页 styles.css 还提供了完整的浅色/暗色双主题参考; - 性能可观测:
Server-Timing头让渲染耗时对调用方透明,可直接接入链路追踪或监控告警; - 部署无差别:同一份处理器代码在 Node preset 走原生绑定、在 Edge preset 走 WebAssembly,无需为部署目标改代码——这正是把"图片生成"这类计算任务放进服务器路由的价值所在。
本示例的完整说明与页面代码可在 examples/takumi 中查看,其镜像文档收录于 docs/4.examples/takumi.md,路由机制的整体说明参见 docs/1.docs/5.routing.md。
【免费下载链接】nitroNext Generation Server Toolkit. Create web servers with everything you need and deploy them wherever you prefer.项目地址: https://gitcode.com/GitHub_Trending/ni/nitro
创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考