Skip to content

Codex/ios safari header flicker tests - #8620

Closed
aurorascharff wants to merge 59 commits into
reactjs:mainfrom
aurorascharff:codex/ios-safari-header-flicker-tests
Closed

Codex/ios safari header flicker tests#8620
aurorascharff wants to merge 59 commits into
reactjs:mainfrom
aurorascharff:codex/ios-safari-header-flicker-tests

Conversation

@aurorascharff

Copy link
Copy Markdown
Collaborator

No description provided.

aurorascharff and others added 30 commits June 23, 2026 01:08
Runs `npx @next/codemod@canary upgrade canary`:

- next 15.1.12 → 16.3.0-canary.60
- react / react-dom ^19.0.0 → 19.2.7 (pinned)
- @types/react / @types/react-dom 19.2.x (pinned via resolutions)
- eslint-config-next 12.0.3 → 14 (last version supporting ESLint 7;
  16.x requires ESLint 8+ which is a separate upgrade)
- engines.node >=16.8.0 → >=20.9.0 (Next 16 minimum)

Type shims for React 19's stricter @types/react:

- src/types/jsx-bridge.d.ts re-exports React.JSX as the global JSX
  namespace used by existing Icon components
- src/types/css.d.ts provides ambient declarations for .css side-effect
  imports (needed under moduleResolution: 'bundler')
- @types/prop-types added (transitively required by legacy
  forwardRefWithAs.tsx)

Auto-updates from the codemod:

- tsconfig.json: moduleResolution 'node' → 'bundler', jsx 'preserve' →
  'react-jsx', .next/dev/types/** added to include
- next-env.d.ts: routes.d.ts and root-params.d.ts references
- CLAUDE.md: nextjs-agent-rules block injected by next dev (intended
  to be committed per the block's own instructions)
Migrates the docs site from the Pages Router to the App Router and
enables Cache Components (`experimental.cacheComponents` now top-level
`cacheComponents: true`).

Route tree (`src/app/`):

- `layout.tsx` — root layout (theme/uwu init script, fonts, GA, scroll
  restoration); replaces _document.tsx + _app.tsx
- `page.tsx` — home (`section: 'home'`)
- `{learn,reference,community,blog}/[[...slug]]/page.tsx` — section-
  specific catch-all docs pages (each owns its sidebar tree, section
  literal, and metadata generator)
- `warnings/[slug]/page.tsx`, `versions/page.tsx` — flat sections
- `errors/page.tsx`, `errors/[errorCode]/page.tsx` — error decoder
- `not-found.tsx`, `error.tsx` — error/404 boundaries
- `api/md/[...path]/route.ts`, `llms.txt/route.ts` — Route Handlers
  replacing the old API/static endpoints
- `renderSectionPage.tsx`, `DocsPage.tsx`, `clientEffects.tsx` —
  shared route helpers (server + client respectively)

Shared helpers (`src/lib/`, server-only):

- `readMarkdownPage.ts` — MDX read + compile from src/content
- `collectPaths.ts` — generateStaticParams enumeration
- `buildPageMetadata.ts` — canonical URL + hreflang alternates +
  open graph (replaces the old <Seo> component)
- `loadErrorDecoderData.ts` — error-codes fetch + MDX compile

Component-level changes:

- `next/router` → `next/navigation` across Page.tsx, Search.tsx,
  PageHeading.tsx, TopNav.tsx, SidebarRouteTree.tsx,
  ExpandableExample.tsx, Challenges.tsx
- `Page.tsx` takes `section` and `pathname` as props instead of
  sniffing them at runtime via `usePathname`; `<Seo>` and `<Head>`
  removed (replaced by App Router Metadata API)
- `MDXComponents.tsx` is now `'use client'` so the registry can host
  client-only components (Sandpack etc.); a parallel server-safe
  `MDXComponentsList.ts` keeps the component name list reachable from
  `compileMDX.ts`
- `useDeserializedMDX.tsx` — shared hook for revival of the
  serialized React tree on the client
- `usePendingRoute.ts` is a no-op (App Router has no equivalent of
  `router.events.routeChangeStart`; <Link> handles transitions)

next.config.js:

- `cacheComponents: true` and `reactCompiler: true` lifted out of
  `experimental`
- `serverExternalPackages` lists the Babel + MDX deps that compileMDX
  loads via runtime `require` (otherwise Next bundles them and breaks
  inside the cache scope)
- `turbopack: {}` placeholder so the build picks the webpack pipeline
  (the project's existing webpack config + Sandpack `raw-loader`
  imports aren't Turbopack-compatible yet)

Cache Components adoption:

- `'use cache'` on every page default export and `generateMetadata`
  (including `/errors/[errorCode]`)
- Zero `export const instant = false` opt-outs
- Build output: 816 routes, all Static or Partial Prerender, only
  `/api/md/[...path]` is dynamic

Other:

- `use(promise)` polyfill in HomeContent.js dropped in favour of
  React 19's built-in `use`
- `ErrorDecoderContext.tsx` doc comment refreshed to reflect that
  context now flows from a server component, not getStaticProps
- `worker-bundle.dist.js` regenerated by scripts/buildRscWorker.mjs
  with the updated React 19.2 runtime (much smaller)
From Copilot's automated review on PR reactjs#8492:

- layout.tsx: render `<meta property="fb:app_id">` as a property tag
  directly in <head> (Next's `metadata.other` only emits name=).
- layout.tsx: restore the RSS autodiscovery <link> and the Algolia
  preconnect <link> that lived in the old Pages Router <Head>.
- readMarkdownPage.ts: switch readFileSync -> fs.promises.readFile to
  avoid blocking the event loop while compiling MDX.
- package.json: bump @types/node from ^14 to ^20 to match the new
  engines.node >=20.9.0.
- buildPageMetadata.ts + renderSectionPage.tsx + learn/blog
  generateMetadata: thread the section's routeTree through so we can
  re-emit the `algolia-search-order` meta tag on Learn pages and Blog
  posts (matches the old <Seo> behavior; Algolia uses it for ordering).
The App Router migration and Cache Components (`cacheComponents`,
`'use cache'`) are all supported in stable 16.2.9 — the canary pin was
only needed during the migration itself. Builds clean with no config
warnings; all 816 routes still Static / Partial Prerender.

We removed every `instant = false` opt-out, so the 16.3-only `instant`
route config isn't used and there's nothing tying us to canary.
Per review feedback from @icyJoseph: instead of 'use cache' on every
page and generateMetadata, cache at the utility/data layer so callers
just work and the page render + generateMetadata share one compile.

- readMarkdownPage, collectSectionPaths, collectFlatSectionSlugs,
  loadErrorCodes, and a new compileErrorDecoderData helper are now
  'use cache' + cacheLife('max') (stable content, only changes on
  deploy; revalidate 30d instead of the default 15m).
- loadErrorDecoderData stays uncached and keeps the notFound() check,
  which can't run inside a 'use cache' scope; it delegates the cached
  compile to compileErrorDecoderData.
- Removed 'use cache' from all 9 routes and their generateMetadata.

Build unchanged: 816 routes, all Static / Partial Prerender.
Per review feedback from @MaxwellCohen: <Page> is a shared client
component rendered by every route, and it statically imported
HomeContent (~2.7k LOC of homepage-only marketing/animation code).
That pulled the homepage into the shared client bundle of every docs,
reference and blog page.

Load it with next/dynamic instead. The homepage chunk (~116 KB) now
loads only at '/'; a representative docs route drops from ~941 KB to
~830 KB of client JS.
Per review feedback from @icyJoseph: the /api/md/[...path] handler that
serves raw markdown was Dynamic (a function per request). The content
set is fixed at build time, so prerender it instead.

- Add generateStaticParams (new collectAllContentPaths in collectPaths)
  enumerating every .md under src/content.
- 'use cache' + cacheLife('max') the file read (the dynamic/dynamicParams
  route segment configs are disallowed under cacheComponents).

Result: /api/md/[...path] is now SSG; +221 prerendered markdown
endpoints. Served from the CDN, so self-hosted clones stay static too.
Paths that match a section catch-all but have no backing .md file (e.g.
/learn/state, a sidebar header) were 500ing. The fs read threw inside
readMarkdownPage's 'use cache' scope, which surfaces as a render error
instead of falling through to notFound().

readMarkdownPage now returns PageData | null for a missing file instead
of throwing; callers decide notFound(). Removes the now-redundant
safeReadPage try/catch wrapper.
Expand the comment above the empty `turbopack: {}` to explain why the
build runs on webpack (custom webpack config + Sandpack raw-loader
imports aren't Turbopack-ready) and that it's a tracked follow-up.
These files dropped the '/* Copyright (c) Facebook, Inc. */' banner when
the 'use client' directive was added. Restore it above the directive
(comments may precede a directive without disabling it).
Reading window.location.hash during render returned false under the App
Router (the server render sees no hash), so the queued-expand ref stuck at
false and deep-linked DeepDive/Example blocks no longer opened on load.
Move the hash check into the post-mount effect so the initial render stays
deterministic and the deep link expands again after hydration.
# Conflicts:
#	package.json
#	src/components/Layout/Page.tsx
#	src/pages/errors/[errorCode].tsx
#	yarn.lock
… components for community, learn, and reference sections
* Resize long og image text

* address feedback

(cherry picked from commit 7b6c3ce)
aurorascharff and others added 26 commits August 12, 2026 21:22
The Pages Router build always hid the generated /src/styles.css tab
(the old check read a `visible` flag that was never set). Restoring that
so ~76 pages don't grow a second tab.

Co-Authored-By: Claude <noreply@anthropic.com>
The font URLs were made relative in this PR, but translation forks rely
on loading (and sharing the cache for) the Optimistic fonts from
react.dev. Restores the absolute URLs in the CSS and the preloads.

Co-Authored-By: Claude <noreply@anthropic.com>
`send_page_view: false` suppressed GA4's automatic page_view on landing,
and the custom `pageview` event was sent for every pathname including the
initial one. Match the old _app.tsx: let gtag('config') report the landing
page_view, and only send the legacy custom event on client navigations.

Co-Authored-By: Claude <noreply@anthropic.com>
- Only collect top-level headings (run the extractor before the MaxWidth
  wrapper). Headings nested in <Note> etc. were never in the TOC before.
- Compile TOC entries from the already-processed mdast heading children
  instead of re-parsing the heading source. Re-parsing dropped smartypants
  (straight quotes on 43 pages) and turned headings like "1. Install" into
  an <ol> inside the TOC link.

Co-Authored-By: Claude <noreply@anthropic.com>
Reading src/content with `fs` inside 'use cache' meant that in development
an edited .md file kept serving stale content on a normal reload (only a
hard refresh bypassed the cache), and nothing triggered a refresh on save
now that next-remote-watch is gone.

Import the files via import.meta.glob with Turbopack's built-in raw-loader
instead. The content is part of the module graph, so saving a .md file
Fast Refreshes the open page in place, and production bundles the files
rather than depending on outputFileTracingIncludes.

Co-Authored-By: Claude <noreply@anthropic.com>
- Error decoder: key the compiled-MDX cache on the error message instead
  of the entire codes.json map.
- Home page clock: tick on minute boundaries again instead of every 60s
  from mount.
- next.config: drop the @babel/* entries from serverExternalPackages; the
  server no longer uses Babel.

Co-Authored-By: Claude <noreply@anthropic.com>
Not-found pages under the dynamic section routes are rendered entirely on
the client (the PPR fallback shell is empty), and React doesn't execute
inline scripts it inserts. So the theme/uwu/platform init never ran there:
the page came up light regardless of preference, and the theme toggle threw
because window.__setPreferredTheme didn't exist.

Move the script to a shared module and evaluate it from a client effect when
the inline version hasn't run.

Co-Authored-By: Claude <noreply@anthropic.com>
The Pages Router's Link happened to resolve [Setup](learn/setup) against
the site root. The App Router's Link resolves it against the current page
(/learn/learn/setup), so spell the paths out.

Co-Authored-By: Claude <noreply@anthropic.com>
useTocHighlight built its own list of headings by scanning the DOM for
anchor elements inside H1/H2/H3, and assumed that list lined up with the
TOC. It only did so because the page title rendered a broken #undefined
anchor that happened to pad index 0 ("Overview"); with that gone, the
previous entry got highlighted. Headings nested in <Note> etc. have
anchors but aren't in the TOC, which skewed it on those pages too.

Resolve the TOC entries' own ids to elements instead, so there is one
source of truth.

Co-Authored-By: Claude <noreply@anthropic.com>
The menu state became "open for this path", derived against the current
pathname so the overlay disappears as soon as a navigation commits. But
the path was never forgotten, so navigating Back to the page the menu was
opened on brought the menu back with it. Clear it once the path changes.

Co-Authored-By: Claude <noreply@anthropic.com>
@github-actions

Copy link
Copy Markdown

Size changes

Details

No comparable base-branch data yet — the base branch has not produced stats in this format. Showing current sizes only; deltas will appear on the next run after this lands on the base branch.

Metric Size (gzip)
Global (loads on every page) 166.29 KB
Total JS 936.75 KB
Total CSS 18.67 KB
First-load JS: Home 248.67 KB
First-load JS: Learn 222.40 KB
First-load JS: Reference 222.40 KB
First-load JS: Community 222.40 KB
First-load JS: Blog 211.01 KB
HTML: Home 71.45 KB
HTML: Effects guide 114.01 KB
HTML: useState 96.05 KB

@aurorascharff

Copy link
Copy Markdown
Collaborator Author

Closing this diagnostic PR because its base should be aurorascharff/react.dev:upgrade-next-16 so the diff contains only the isolated Safari experiment.

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

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants