You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
 
 
 
 
 

6.1 KiB

设计:个人主页可选 EJS 整页渲染

日期:2026-04-21
状态:已定稿(与产品对话一致)

1. 背景与目标

当前公开个人根路径 /@:publicSlugNuxt 页面 app/pages/@[publicSlug]/index.vuelayout: public 渲染,视觉与结构受站内组件体系约束。

目标

  • 同一 URL /@:publicSlug(及与之同义的仅带尾部 / 的根路径)上,允许用户通过设置 切换
    • Vue 默认主页(现状),或
    • 服务端 EJS 输出的完整 HTML 文档(整页不局限于站内壳层;模板内可链到 /@slug/posts/...timelineabout 等现有 Vue 子路由)。
  • /@slug 根路径以外(如文章、时光机、关于页)行为不变,仍走现有 Vue 路由。
  • 模板能力(已确认):采用 完整 EJS(含任意 <% %> 服务端逻辑);等价于在服务器上以进程身份执行 用户可控代码信任模型与滥用责任由运营侧承担;本版 不做 沙箱化必选(若日后对公众开放,需另立项「受限模板 / 沙箱进程」)。

2. 产品规则(已确认)

维度 规则
切换范围 个人主页根路径 /@:publicSlug(路径上 下一级 segment,如 /posts 不算)。
子路由 /@:publicSlug/posts/...timelineabout始终 为现有 Vue 页;主页模板中可 链接 至这些路径(产品叙事:首页可完全自定义,子页仍是站内 App)。
模式 每用户一个 homeRenderMode(命名实现时可微调,语义固定):vue | ejsvue 时行为与现网一致;ejs 时对根路径返回 完整 text/html
编辑权限 对应 publicSlug已登录所有者 可在控制台(如 me / 设置)编辑模板与切换模式。
数据真相 注入 EJS 的公开数据与 GET /api/public/profile/:publicSlug 语义一致(同一可见性、同一字段含义),通过 共享 server 层 组装,避免两套逻辑漂移。

3. 非目标(本版明确不做)

  • 用 EJS 接管 /@slug/** 整棵子树; 改为独立子域或独立路径作为主入口(除非你方后续改需求)。
  • 将「完整 EJS」与「沙箱必选」同时作为本版交付条件;沙箱 / Liquid 等作为 后续 产品阶段评估。
  • 以「仅客户端 v-html 注入整站」冒充 整页 SSR HTML 的主方案(可与方案 1 对比说明,但不替代主路径)。

4. 架构(推荐实现)

4.1 方案选定:Nitro 短路与早返回

Nitro 侧(server/middleware 或等价、且顺序可控的钩子):

  1. 仅当请求路径 匹配「公开个人根路径」:/^\/@(?<slug>[^/]+)\/?$/(具体正则与大小写策略 与路由参数约定一致,需与 Nuxt @[publicSlug] 对齐)。
  2. 解析 publicSlug,查库得到用户与 homeRenderMode
  3. 若为 ejs:拉取 与公开 API 等价 的 payload,执行 ejs.render,设置 Content-Type: text/html;charset=utf-8结束响应(不进入 Nuxt 页面管线)。
  4. 若为 vue不写 body放行 至现有 index.vue

备选(不采用为主):独立路径或子域渲染子站(违背同 URL 切换);或仅用 Vue + v-html(违背整页 HTML / SEO 预期)。

4.2 数据与注入

  • 单一来源:抽取 getPublicProfilePayloadForHome(publicSlug)(名称可调整),供 GET /api/public/profile/:publicSlug 与 EJS 渲染 共用
  • ejs.render(template, locals)locals 至少包含:
    • profile:与 API 响应体(成功时的业务 data结构一致 的对象;
    • site:站名、站点公开根 URL 等(来源与现有全局配置一致);
    • 安全向 helper:如 escapeHtml、日期展示格式化等(鼓励 在模板里用 helper,但 通过模板消灭 EJS 的任意代码能力——与产品选项 A 一致)。
  • 禁止项(工程习惯):不向 locals 注入开放 require、未约束文件路径等;ejs 选项 需审阅官方文档,避免 root / filename 等导致意外包含路径遍历;模板存储于 DB 文本字段,不设「从磁盘任意路径读模板」。

4.3 存储

  • 用户表或等价扩展:保存 homeRenderModehomeEjsSource(或拆表);最大长度版本号(可选) 在实现层约束。
  • 保存时:可选 先做 语法与编译试跑(仅对所有者预览接口或保存路径),语法错误返回 400,错误信息 对匿名访客暴露内部栈。

5. 安全与滥用(在选项 A 下的最低工程线)

以下内容 不能 替代「仅可信用户 / 条款 + 封禁」的产品决策,但作为 最低限度

  • 单请求:渲染 超时(防止死循环 / 极重计算拖死进程)。
  • 模板体积:上传 上限
  • 错误响应:访客侧 通用错误页或简短 500 文案;调试信息仅在 已鉴权预览 中展示。
  • 可选:运营 一键将 homeRenderMode 置回 vue(管理端或 CLI 文档化)。

6. 错误与兼容性

  • 运行时错误:记录服务端日志;对匿名访客返回 安全 的 HTML 错误页或 500。
  • 自动降级(连续失败 N 次自动切回 vue):本版默认不做;若日后做,须在设置中明文提示并可关闭,避免主人困惑。

7. 测试

  • 单元测试:固定 locals 与短模板片段,断言 ejs.render 输出与 逃逸 行为。
  • 集成或手动清单:同一 publicSlugvue / ejs 下各访问一次根路径;确认 /posts 等子路径 仍为 Vue。

8. 依赖

  • 新增 ejs 运行时依赖(版本以 lockfile 与审计为准);类型如有 @types/ejs 则开发依赖补齐。

下一实现阶段:在用户确认本 spec 无异议后,另见 docs/superpowers/plans/implementation planwriting-plans 产出)。