如何在Deno中实现原生Web Component的服务端渲染(SSR)
原生Web Component Deno + Oak 服务端渲染方案
完全可以实现该需求,核心是通过DOM模拟库在Deno服务端运行Web Component逻辑,提前拉取数据并生成静态HTML返回给爬虫和客户端。
实现步骤
1. 前置准备
Deno生态下使用deno-dom模拟浏览器DOM环境,支持HTMLElement、customElements等Web Component必备API,无需修改太多原有组件逻辑。
2. 组件兼容改造(可选但推荐)
为避免服务端和客户端重复请求数据,给组件增加预加载数据的支持,修改后代码如下:
import { api } from '/global.js'; class StatsBar extends HTMLElement { constructor() { super(); this.stats = {}; // 检测是否已经有服务端预渲染的内容 this.hasPrerendered = this.children.length > 0; } async getStats() { // 如果有传入的预加载数据,直接用,不发请求 if (this.dataset.stats) { this.stats = JSON.parse(this.dataset.stats); return; } const res = await fetch(`${api}/stats`); const data = res.ok && await res.json(); this.stats = data; } async connectedCallback() { // 已经预渲染过就不用重复渲染 if (this.hasPrerendered) return; await this.getStats(); this.innerHTML = ` <style> .stats { margin: 2rem 0; display: block; width: 100%; text-align: center; padding: 1rem; } </style> <div class="stats"> Read <a href="/recent">${this.stats.urls.count.toLocaleString()}</a> articles on <a href="/tags">${this.stats.tags.count.toLocaleString()}</a> topics by ${this.stats.visits.count.toLocaleString()} visitors so far... </div> `; } } if (typeof customElements !== 'undefined') { customElements.define("stats-bar", StatsBar); } export default StatsBar;
3. Oak 服务端实现
服务端核心逻辑:预拉取stats数据 → 初始化DOM环境 → 注册自定义组件 → 生成组件静态HTML → 拼接进页面返回
代码示例:
import { Application, Router } from "oak"; import { DOMParser, HTMLElement, customElements } from "deno-dom"; // 引入你的StatsBar组件 import StatsBar from "./path/to/StatsBar.js"; const app = new Application(); const router = new Router(); // 提前注册自定义元素 customElements.define("stats-bar", StatsBar); router.get("/", async (ctx) => { // 1. 预拉取stats数据,和组件内调用的接口保持一致 const statsRes = await fetch(`${YOUR_INTERNAL_API_ADDR}/stats`); const stats = await statsRes.json(); // 2. 初始化模拟DOM const doc = new DOMParser().parseFromString(`<html><body></body></html>`, "text/html"); globalThis.document = doc; globalThis.HTMLElement = HTMLElement; // 3. 创建组件实例,传入预加载数据 const statsBar = doc.createElement("stats-bar"); statsBar.dataset.stats = JSON.stringify(stats); // 手动触发组件渲染逻辑 await statsBar.connectedCallback(); // 4. 拼接完整页面HTML const html = ` <!DOCTYPE html> <html> <head> <title>你的页面</title> </head> <body> <!-- 直接插入预渲染好的组件内容 --> <stats-bar>${statsBar.innerHTML}</stats-bar> <!-- 客户端加载组件脚本,后续Hydration不会重复请求和渲染 --> <script type="module" src="/path/to/StatsBar.js"></script> </body> </html> `; ctx.response.body = html; ctx.response.headers.set("Content-Type", "text/html; charset=utf-8"); }); app.use(router.routes()); app.use(router.allowedMethods()); await app.listen({ port: 8000 });
注意事项
- 服务端配置的API地址要保证可以正常访问到stats接口,避免请求失败
- 如果组件依赖更多浏览器全局API,可以在服务端全局按需补全模拟
- 生成的静态HTML已经包含完整的内容,爬虫可以直接抓取,不需要等待客户端执行JS
内容的提问来源于stack exchange,提问作者chovy
相关产品推荐
相关产品推荐

