Lee's Blog
  • 首页
  • 博客
  • 作品
  • 成长
  • 学习
  • 打榜
  • 练习
  • 关于
登录 / 注册
✦

保持联系

关注我的最新动态

GitHubbilibili

© 2026 Lee's Blog

首页/博客/博客开发/用户登录后看不到自己的头像——一次 DOM 顺序撞车
用户登录后看不到自己的头像——一次 DOM 顺序撞车
博客开发2026年8月7日5 分钟阅读

用户登录后看不到自己的头像——一次 DOM 顺序撞车

把 4 个路由挪进 (public)/ 让 Header 套上后,curl 检查都通过。但用户实际打开首页看不到头像下拉。根因不是路由位置,而是首页嵌的装饰性 nav 跟真 Header 共用同一个 .nav CSS 类,物理位置完全重叠,把真的盖住了。

前端教程
X微博

用户登录后看不到自己的头像——一次 DOM 顺序撞车

部署完以为修好了,curl 检查 nav 数量都正常。结果用户打开首页,头像下拉就是看不到。这是同一段代码第二次栽在「Header 缺失」上,但根因完全不同。

故事的另一面

上一篇文章讲到怎么把 /u/*、/login、/onboarding 移到 (public)/ route group 里,让它们能用上 Header。部署完 curl 检查:

/: <nav> = 1
/login: <nav> = 1
/onboarding: <nav> = 1
/u/creator: <nav> = 1
/u/creator/edit: <nav> = 1

五个页面都有 nav,creator 登录后 nav-user-btn(头像下拉触发器)也是 1。所有 curl 指标都过。我正准备发个 commit。

然后用户回了一句:「等会,你没修好,我没看到头像下拉触发器。」

第一次没修好:根因 A

第一个 bug:/u、/login、/onboarding 不在 (public)/ route group 下,所以 (public)/layout.tsx 那个 Header 没套上。修法是 git mv 把它们挪进去。

这个修对了,curl 也确认了。但用户实际看的时候——

第二次没修好:根因 B

首页 / 是个特别的存在。它本身在 (public)/ 下,所以 Header 套上了,但首页内容里嵌了一个 CosmicHero 组件。这个 hero 组件内部又硬塞了一个装饰性 <nav>:

<nav className={`nav${navRevealed ? ' revealed' : ''}`}>
  <div className="nav-logo">Lee's Blog</div>
  <ul className="nav-links">
    <li><a href="#">首页</a></li>
    <li><a href="#posts">博客</a></li>
    ...
  </ul>
</nav>

然后这两个 nav 共享同一个 CSS 类 .nav:

.nav {
  position: fixed;
  top: 0;
  left: 0;
  right: 0;
  z-index: 100;
  padding: 18px 40px;
}

同一份 CSS,同一个 position: fixed; top: 0; z-index: 100。没有差异化的 stacking context。

谁在上面?

按 DOM 顺序:

  1. (public)/layout.tsx 渲染 <Header />(真的,带头像下拉)
  2. (public)/page.tsx 渲染 <CosmicHero />,里面硬塞了一个 <nav>(装饰性,不带下拉)

DOM 顺序里 CosmicHero 在后。后渲染的占同样的位置和 z-index,就盖在了真 Header 上。

curl 看到的 nav-user-btn = 1 是 DOM 里有这个按钮,但它被视觉上盖住了。curl 不跑 CSS、不渲染像素,看不到这一层。

我给用户截图(其实是我口头描述):

<nav class="nav revealed">  ← 真 Header,带 nav-user-btn
  ...
</nav>
<nav class="nav">          ← 装饰性 nav,没按钮
  ...
</nav>

两个 nav 物理上重叠。装饰性 nav 没有 avatar 按钮,用户看不到下拉。

修法

不是调 z-index 把 Header 顶上去(治标),而是删掉那个装饰性 nav(治本)。

CosmicHero.tsx 改三处:

- const [navRevealed, setNavRevealed] = React.useState(false)

- const t3 = setTimeout(() => setNavRevealed(true), 1900)

- <nav className={`nav${navRevealed ? ' revealed' : ''}`}>
-   <div className="nav-logo">Lee's Blog</div>
-   <ul className="nav-links">...</ul>
- </nav>

- clearTimeout(t3)

清掉 state、setter、定时器、DOM 节点、cleanup。这个装饰性 nav 是过去没有真 Header 时候的占位,现在 Header 已经在 layout 里了,多余。

部署后再验

curl -s https://blog.dogeggcode.cyou/ | grep -c '<nav'
# before: 2
# after:  1

curl 看到的 nav 数量从 2 降到 1,正好是去掉了那个装饰性的。

教训

1. CSS 类名不要「复用」

.nav 这个类被用在两个不同语义的组件上——一个是「全局导航栏(带用户菜单)」,另一个是「英雄区装饰条」。共享同一份 CSS 看起来省事,实际上埋了一颗语义炸弹:

  • 改 .nav 的 padding 不知道会动到哪个
  • 任何带 .nav 的新元素都会自动变成 position: fixed + z-index: 100
  • 调试时 CSS 类名不再有「这个类是干嘛的」的提示

更好的命名应该是 .global-header 和 .hero-mini-nav(或者直接把装饰性的也写成 .hero-mini-nav 用专门样式)。

2. 同 z-index 同位置,DOM 顺序决定谁在上

这是个事实但很容易忘。两个 position: fixed; top: 0; z-index: 100 的元素,后面那个赢。任何想用 z-index 数字游戏去压住另一个都是治标。

这次的根因不是 stacking,是「不应该有两个」。删一个,问题自然消失。

3. curl 检查 DOM,不检查视觉

我的整个验证流程是:

for path in / /login /onboarding /u/creator /u/creator/edit; do
  curl -s "https://blog.dogeggcode.cyou$path" -o /tmp/p.html
  echo "$path: <nav> = $(grep -c '<nav' /tmp/p.html)"
done

这只能告诉我「<nav> 标签存在」。它不能告诉我:

  • 那个 nav 是不是被另一个 nav 盖住了
  • 按钮的 display / visibility / opacity
  • CSS Grid / Flex 是不是把按钮挤出去了
  • 浏览器扩展、广告拦截是不是把 DOM 改了

视觉问题必须用浏览器看(哪怕是 Playwright headless 截图)。

我以后会加一道:用 Playwright headless 截 /、/u/creator、/u/creator/edit 三张图,肉眼对比 Header 上的 avatar 可见性。curl 只能用来快速 sanity check DOM 结构。

这次发版改了什么

根因 A(上一篇文章,已发):

  • 把 src/app/{login,onboarding,u} 移到 src/app/(public)/ 下,4 个用户面路由获得 Header

根因 B(本篇):

  • 删 src/components/hero/CosmicHero.tsx 里的装饰性 <nav> 和配套 state / timer

部署后所有 (public)/ 路由上 nav 数量从「0 或 2」统一成「1」,Creator 登录后所有页面都能看到头像下拉,包括首页。

用户登录后能不能修改个人信息、能不能退出登录——答案是:能,现在能了。

← 上一篇TOC 悬浮折腾记:6 次 commit 才彻底搞定下一篇 →一次登录 / 资料 / 头像的全面体检——5 个用户报告 + curl 抓出的真问题

相关文章

  • 一次登录 / 资料 / 头像的全面体检——5 个用户报告 + curl 抓出的真问题约 9 分钟
  • TOC 悬浮折腾记:6 次 commit 才彻底搞定约 6 分钟
  • 把博客搞崩两次的那次部署:一次 OTP 重构复盘约 8 分钟

评论

需要登录账号才能发表评论。

  • 加载中…

本页内容

  • 用户登录后看不到自己的头像——一次 DOM 顺序撞车
  • 故事的另一面
  • 第一次没修好:根因 A
  • 第二次没修好:根因 B
  • 谁在上面?
  • 修法
  • 部署后再验
  • before: 2
  • after: 1
  • 教训
  • 1. CSS 类名不要「复用」
  • 2. 同 z-index 同位置,DOM 顺序决定谁在上
  • 3. curl 检查 DOM,不检查视觉
  • 这次发版改了什么