Skip to content
This repository was archived by the owner on Nov 4, 2026. It is now read-only.
This repository was archived by the owner on Nov 4, 2026. It is now read-only.

apps/site: Schema Catalog 索引页把每个缩略图包在 button 里,85 个含 button 节点的示例都触发 React 嵌套按钮 hydration 报错 #3903

Closed

Description

@yinlianghui

发现于 #3787 的浏览器实证(PR:claude/issue-3786-3787-pageheader-docs-demo),不在该 PR 处理 —— 那一单改的是一份 demo JSON,这一条是索引页的 DOM 结构问题,与哪个示例无关。先说清:不是 #3787 引入的 —— 换 demo 之前之后都在报,且打到 85 个示例。

现象

/docs/guide/schema-catalog 上,SchemaCatalogIndex.tsx:111 把每个卡片渲染成 button,卡片里嵌的是 SchemaThumbnail(:116),而 SchemaThumbnail 会用真的 SchemaRenderer 渲染示例本身。任何含按钮节点的示例于是落成 button 套 button。

Chromium 控制台(headless、Next dev、访问该页)读数:

In HTML, %s cannot be a descendant of <%s>.
This will cause a hydration error.%s  button  <button>
| <%s> cannot contain a nested %s.
See this log for the ancestor stack trace.  button  <button>

命中面:grep -rl '"type": "button"' examples/schema-catalog/src/schemas | wc -l = 85 个示例。

为什么值得记一笔

React 明确把这个归为 hydration error 而不是样式瑕疵:嵌套按钮的 HTML 会被浏览器解析器重构(内层按钮被提到外层之外),server HTML 与 client 树因此不一致。此外它本身就是可访问性缺陷 —— 交互控件套交互控件,键盘与读屏行为不确定。

建议处置

把卡片外壳从 button 换成 div + role="button" 之外的方案更稳:推荐外壳用非交互元素,点击目标做成覆盖层(一个绝对定位的 button 兄弟节点,aria-label 给示例名),缩略图内容 pointer-events: none。这样卡片可点、示例内部的按钮不再被嵌套。SchemaThumbnail 已经是「scaled, non-interactive preview」,pointer-events: none 与它的定位一致。

参考位置

  • apps/site/app/components/SchemaCatalogIndex.tsx:111(卡片外壳 button)、:116(嵌入 SchemaThumbnail)
  • apps/site/app/components/SchemaThumbnail.tsx:60-128(内部真实 SchemaRenderer)
  • content/docs/guide/schema-catalog.mdx:33(索引页入口)

关联:#3787(发现于此)

Activity

  1. os-zhuang commented on Aug 9, 2026

    @os-zhuang
    Contributor

    Triage: pm:queue (objectui in-repo card — whole-repo seat, no domain label per protocol).

    • Classification: concrete defect with a measured repro — React classifies nested buttons as a hydration error (server HTML restructured by the parser), and it is an accessibility defect independent of hydration. The card's overlay-button direction is carried non-binding.
    • Anchors verified on origin/main @ 50fa376: apps/site/app/components/SchemaCatalogIndex.tsx:111 renders the card shell as a button and :116 embeds SchemaThumbnail (real SchemaRenderer inside). Hit surface re-measured: 85 example files under examples/schema-catalog/src/schemas contain button-type nodes — matches the card's census exactly.
    • Dedup: repo-scoped search for nested-button cards returns only this card; docs/examples: PageHeader 文档页的唯一 live demo(layout-page-header/pageheader-with-actions)手搓 div,完全不用 page-header #3787 is the discovery context, not a duplicate.
    • Stale-premise: fresh filing (01:07Z), anchors re-read at the current tip — nothing merged in between touches the index component.

    本评论来自分诊座位 Routine(#5474 试点),不构成认领。


    Generated by Claude Code

  2. self-assigned this
    on Aug 9, 2026
  3. yinlianghui commented on Aug 9, 2026

    @yinlianghui
    ContributorAuthor

    🔒 认领(objectui 分片 PM,session_01GTRjn8xBqp75dk7kFupVRt,批次 4 补位)

    • 分支:claude/issue-3903-catalog-index-nested-button
    • 范围:SchemaCatalogIndex 卡片外壳去 button 化(正文建议方向:非交互外壳 + 覆盖层 button + 缩略图 pointer-events: none,方向 non-binding 但偏离需论证),浏览器实证嵌套按钮 hydration 报错清零。
    • 文件面:apps/site/app/components/SchemaCatalogIndex.tsx(或 + SchemaThumbnail.tsx);与在飞各单不相交。

    Generated by Claude Code

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

Type

No type

Projects

No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions