Hacktoberfest 2026:メンテナが10月に向けて印を付けた、オープンで初心者向けの issue。 Hacktoberfest の issue を見る

nav: hamburger icon pops in late on first load (toggle is portalled after hydration)

クローズ 初心者向け
#322 コメント 0 件 リアクション 0 件 担当者 0 名 GitHub で見る

メンテナーはふだん 1 日以内に返信

まだ誰も着手していません。

評価

難易度
2/5
見積もり時間
1〜3時間
初心者へのやさしさ
76/100
issue の種類
バグ
明瞭さ
明確に書かれている
活発さ
活発
技術スタック
next.js, react, typescript
領域
frontend

調査の方向性

components/layout/MobileMenu.tsxと、Chrome内の関連するナビゲーション状態を読み、次にpnpm buildのプリレンダリングされた出力を調べてください。既存のモバイルメニューの動作を維持しながら、初回レンダリング時に.mm-toggleが存在するようにしてください。エクスポートされたHTMLにそれが含まれ、オーバーレイとパネルが引き続き正しく開くことを確認してください。

索引モデルが issue の本文から書いたものです。

説明

bug

Symptom

On a cold load below 1280px the top-right menu icon (the `.mm-toggle` hamburger) is missing from the first paint and appears only once the JS bundle has downloaded and hydrated. On a real phone on a real network that is a visible pop-in next to a wordmark that was there from the first frame.

Confirmed

Served the fresh export (pnpm build on main @ 736bdfb) from a local static server and loaded it in headless Chromium (Playwright 1.56, 390×844 viewport) with JS chunk responses delayed 400 ms to widen the pre-hydration window, recording MutationObserver events on <html> from the first script tick plus a screenshot every ~100 ms.

  • The prerendered HTML contains no mm-toggle at all: grep -c mm-toggle out/index.html → 0, and the live page gives the same: curl -sL https://decdn.org/ | grep -c mm-toggle → 0.
  • First mutations after hydration (t ≈ 861 ms in this run):
    add <button .mm-toggle>        parent <body>
    add <div .mm-overlay>          parent <body>
    add <aside .mm-panel #mm-panel> parent <body>
    
  • Frame at 831 ms: wordmark only, empty top-right. Frame at 945 ms: hamburger present. Nothing else in the nav changes between those frames.
  • Same mutation sequence at 1440×900; there the toggle is display: none via the @media (min-width: 1280px) gate in globals.css, so desktop is unaffected.

Cause

components/layout/MobileMenu.tsx renders the toggle, overlay and panel through createPortal(..., document.body) and gates the whole subtree on a useSyncExternalStore hydration flag (server snapshot false), so the prerender emits nothing and the client mounts all three only after hydration. Introduced with the drawer in #56. The portal exists so the toggle can sit above the panel (z-65 > z-60) while the wordmark stays in the nav — see the comment above const drawer in that file.

Not the cause (checked)

  • Favicon: Chromium requests only /icon.svg (never favicon.ico), and the .ico frames and the SVG are the same artwork on a white rounded tile, so there is no tab-icon flash in Chrome.
  • The wordmark-light.svg / wordmark-dark.svg pair is requested at t ≈ 28 ms and both stay mounted, so the logo does not flash.
  • The try-it SelectMenu also swaps at hydration (NativeSelect → listbox), but it is below the fold and by design renders the same face in both.

Possible direction

Prerender the <button class="mm-toggle"> so it is in the static HTML from the first byte (e.g. render it inline in the nav, or portal it with a server-rendered fallback), and keep the hydration gate only for the overlay and panel, which are invisible until opened anyway. The toggle's data-tone and data-open come from Chrome state that already prerenders deterministically (intro / closed), so there should be no hydration mismatch.

主要言語
TypeScript
スター
3
フォーク
0
平均マージ
12時間 41分
マージ済み PR(30日)
70

環境構築

このプロジェクトには開発コンテナ、Dockerfile、コントリビューションガイドがありません。まず README を読み、一般的な手順ははじめてのコントリビューションガイドを参照してください。

はじめの一歩

  1. issue を最後まで読み、次にプロジェクトのコントリビューションガイドを読みます。
  2. 着手することを issue にコメントします — 二人が同じ作業をするのを防げます。
  3. リポジトリをフォークし、ブランチを切って変更します。
  4. issue 番号を参照したプルリクエストを送ります。

decdn/website のほかの issue

decdn/website の issue をすべて見る

似ている issue

TypeScript の issue をもっと見る

新しい issue をメールで受け取る

初心者向けの GitHub issue を短くまとめたダイジェスト。