You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

如何在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

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.09.24 20:24:02