cloudflare / cloudflare/vinext

Attribute hydration mismatches are missing from the dev overlay

Open
#2,705 0 comments 0 reactions 0 assignees View on GitHub
Dominant language
TypeScript
Stars
8.8k
Forks
406
Avg merge
2d 6h
Merged PRs (30d)
120

Description

## Summary

I ran into this problem while migrating my blog to vinext, some hydration errors never showed up in the dev error overlay, even though React was clearly warning about them in the browser console. Digging in, the root cause is that vinext's dev overlay does not intercept `console.error` at all, and React 19 reports some hydration mismatches through `console.error` only, not through `onRecoverableError`.

vinext's development overlay currently listens for React root callbacks, global `error` and `unhandledrejection` events, and Vite build errors, but it does not intercept console errors.

As a result, an attribute-only hydration mismatch is printed to the browser console with its React component diff, but no vinext error overlay is shown.

This is a different path from a text or element-tree mismatch:

- Recoverable text/structure mismatch: React calls `onRecoverableError` and may regenerate the affected tree.
- Attribute-only mismatch: React calls `console.error`, does not call `onRecoverableError`, and does not patch the mismatched attribute.

Next.js handles the latter as a development `Console Error`.

## Reproduction

- GitHub: https://github.com/MaxtuneLee/vinext-hydration-error-overlay-repro
- StackBlitz: https://stackblitz.com/~/github.com/MaxtuneLee/vinext-hydration-error-overlay-repro

Run:

```bash
pnpm install
pnpm dev
```

The reproduction uses an App Router Client Component whose module is evaluated in both the server and browser environments:

```tsx
"use client";

const isClient = typeof window !== "undefined";

export default function Page() {
return (

Theme

);
}
```

The server renders `aria-label="auto"`, while the client expects `aria-label="light"`. The element tree and text are otherwise identical.

## Current behavior

React prints the following message and component diff to the browser console:

```text
A tree hydrated but some attributes of the server rendered HTML didn't match the client properties. This won't be patched up.
```

The diff includes:

```text
+ aria-label="light"
- aria-label="auto"
```

Observed with `vinext@1.0.0-beta.3`, React 19.2.7, and Vite 8:

- The browser console receives the complete React hydration error.
- The hydrated DOM keeps `aria-label="auto"`, matching React's statement that this mismatch is not patched.
- The vinext development overlay is not mounted.
- `onRecoverableError` is not involved in this error path.

## Expected behavior

Match Next.js development behavior:

- The development overlay opens.
- The error is labeled `Console Error`.
- The React hydration message and component diff are preserved.
- The component stack and mapped application source location are shown when available.
- The error appears once rather than being duplicated through another error channel.
- Fixing the source through Fast Refresh clears the overlay.

Image

## Affected vinext code

### Overlay installation

`packages/vinext/src/client/dev-error-overlay.tsx` documents and installs four error sources:

1. React errors delivered through `onCaughtError`;
2. React errors delivered through `onUncaughtError`;
3. global `error` and `unhandledrejection` events;
4. Vite build errors.

`installDevErrorOverlay()` does not patch `console.error`, so React's attribute-mismatch error never reaches `reportDevError()`.

### Overlay state

`packages/vinext/src/client/dev-error-overlay-store.ts` has no console-error source:

```ts
export type Source =
| "server"
| "vite"
| "uncaught"
| "caught"
| "window-error"
| "unhandledrejection";
```

Correspondingly, the overlay has no `Console Error` label.

### Router coverage

The shared overlay installer is used by:

- App Router through `packages/vinext/src/server/app-browser-entry.ts`;
- Pages Router through `packages/vinext/src/server/pages-dev-hydration.ts` and `packages/vinext/src/entries/pages-client-entry.ts`.

The public reproduction confirms the App Router failure. Pages Router should be tested as part of the fix because it uses the same overlay installer and is likely affected by the same missing console channel.

## Next.js behavior

### Console interception

Next.js installs its App Router devtools hooks before hydration: `packages/next/src/next-devtools/userspace/app/app-dev-overlay-setup.ts`

That setup calls `patchConsoleError()`: `packages/next/src/next-devtools/userspace/app/errors/intercept-console-error.ts`

The interceptor:

1. reads React's console arguments;
2. preserves and formats the complete console message;
3. creates an overlay error;
4. queues it for the React dev overlay;
5. still forwards the original log to the browser console.

The associated error queue is implemented in: `packages/next/src/next-devtools/userspace/app/errors/use-error-handler.ts`

Sources:

- https://github.com/vercel/next.js/blob/canary/packages/next/src/next-devtools/userspace/app/app-dev-overlay-setup.ts
- https://github.com/vercel/next.js/blob/canary/packages/next/src/next-devtools/userspace/app/errors/intercept-console-error.ts
- https://github.com/vercel/next.js/blob/canary/packages/next/src/next-devtools/userspace/app/errors/use-error-handler.ts

### Hydration parsing

Next.js explicitly recognizes the React 19 attribute-mismatch message in: `packages/next/src/next-devtools/shared/react-19-hydration-error.ts`

Source: https://github.com/vercel/next.js/blob/canary/packages/next/src/next-devtools/shared/react-19-hydration-error.ts

### Acceptance test

The App Router development acceptance suite contains an `extra attributes set on server` case. It expects:

- the full React attribute diff;
- a mapped source/code frame;
- the overlay label `Console Error`.

Source: https://github.com/vercel/next.js/blob/canary/test/development/acceptance-app/hydration-error.test.ts

## Proposed direction

1. Add a `console-error` source to the overlay store and display it as `Console Error`.
2. Patch `console.error` exactly once when the development overlay is installed, before App Router or Pages Router hydration starts.
3. Preserve the original console behavior after forwarding the error to the overlay.
4. Format React's variadic console arguments so `%s` placeholders, the hydration diff, and the component stack are not lost.
5. Avoid duplicate entries and recursion:
- overlay-owned `console.error` calls from `devOnCaughtError` and `devOnUncaughtError` must not create a second console entry;
- errors already reported through a React callback or global event should remain deduplicated;
- the interceptor must call the captured original console method.
6. Map the resulting stack/component frame through the existing vinext source mapping path.
7. Keep recoverable hydration handling separate: audit `onRecoverableError` parity for text/structure mismatches, but do not use it as the mechanism for this attribute error path.

## Required tests

1. App Router attribute mismatch opens the overlay as `Console Error`.
2. The overlay contains the React 19 message and `aria-label` diff.
3. The DOM keeps the server attribute, proving this is the unpatched attribute path rather than a recoverable tree regeneration.
4. The error produces exactly one overlay entry.
5. The browser console still receives the original error.
6. Fixing the attribute mismatch through HMR clears the overlay.
7. Pages Router has equivalent coverage.
8. Existing caught, uncaught, global error, rejection, and Vite error behavior remains unchanged.

Suggested test locations:

- `tests/e2e/app-router/dev-error-overlay.spec.ts`
- a Pages Router dev-overlay/hydration E2E spec
- focused unit tests for console argument formatting and deduplication

Contributor guide

Open the contributing guide

Research direction

Start with packages/vinext/src/client/dev-error-overlay.tsx and packages/vinext/src/client/dev-error-overlay-store.ts, then trace installation from the App Router and Pages Router entry points. Review tests/e2e/app-router/dev-error-overlay.spec.ts and the referenced Pages Router and unit-test locations. Done means attribute mismatches appear once as Console Error with their React diff, preserve console output, clear through HMR, and leave existing error channels unchanged.

Written by the indexing model from the issue text.

Assessment

Tech stack
react, typescript, vite
Domain
developer-experience, frontend, testing
Issue type
Bug
Difficulty
4/5
Estimated time
3-5 days
Activity status
Quiet
Clarity
Clearly specified
Newbie friendliness
48/100

Get new issues in your inbox

A short digest of beginner-friendly GitHub issues.