ssr 中自定义元素需加 window 判断并仅客户端注册,服务端只输出降级 html,shadow dom 不可用,属性变更回调 ssr 无效。

SSR 里直接用自定义元素会报 ReferenceError: customElements is not defined,根本跑不起来——因为 Node.js 环境没有 DOM,更没有 customElements 这个 API。
SSR 场景下 customElements.define() 必须加 window 判断
服务端执行 JS 时,window、document、customElements 全都不存在。如果组件注册代码没做防护,Node.js 进程直接崩掉。
- 所有
customElements.define()调用必须包裹在if (typeof window !== 'undefined')条件块里 - 不要写成
if (window)—— SSR 下window是 undefined,访问会直接 throw - 构建工具(如 Vite)若把自定义元素类打包进通用 chunk,需确认它没被服务端入口(如
entry-server.js)意外引入
SSR 输出的 HTML 要能被客户端“水合”(hydration)
服务端只输出纯 HTML 字符串,不执行 JS;客户端加载后,得让浏览器识别出那些自定义标签,并补上行为逻辑。这要求:
- 服务端渲染时,自定义元素必须以“降级友好”方式输出:比如
<user-card name="Alice"></user-card>,不能依赖 Shadow DOM 内容(SSR 根本不生成 shadowRoot) - 客户端首次执行时,要确保
customElements.define()已完成,且标签名与服务端 HTML 中一致;否则 Vue/React 水合会失败或跳过该节点 - 若用框架做 SSR(如 Vue 3 的
createSSRApp),自定义元素需声明为“客户端专属”,避免服务端尝试实例化
Shadow DOM 在 SSR 中完全不可用
this.attachShadow({ mode: 'open' }) 是浏览器 DOM API,Node.js 环境调用会报错;即使你绕过报错,SSR 也绝不会生成任何 shadowRoot 内容——它只输出 light DOM 结构。
- 别在
connectedCallback里条件判断是否 SSR,然后“模拟” shadowRoot;那只是假封装,样式照样泄漏 - 真要 SSR + 封装,只能走 light DOM 路线:用命名空间 class(如
.my-button__inner)、CSS@layer或 CSS-in-JS 隔离,而非依赖 Shadow DOM - 如果组件强依赖 Shadow DOM(比如用
::slotted()控制插槽样式),那就明确放弃 SSR 支持,或仅对非关键路径组件启用
最常被忽略的一点:自定义元素的属性变更逻辑(attributeChangedCallback)在 SSR 输出的 HTML 中毫无意义——服务端不会触发回调,客户端 hydration 时也不会重放。所有初始状态必须靠 HTML 属性本身驱动,后续响应全交给客户端 JS 接管。
前端入门到VUE实战笔记:立即使用
在学习笔记中,你将探索 前端 的入门与实战技巧!











