Skip to content

fix(hydration): route hydration mismatch errors through handleError (fix #13154)#14757

Open
pierluigilenoci wants to merge 1 commit intovuejs:mainfrom
pierluigilenoci:fix/hydration-error-handler
Open

fix(hydration): route hydration mismatch errors through handleError (fix #13154)#14757
pierluigilenoci wants to merge 1 commit intovuejs:mainfrom
pierluigilenoci:fix/hydration-error-handler

Conversation

@pierluigilenoci
Copy link
Copy Markdown

@pierluigilenoci pierluigilenoci commented Apr 24, 2026

Summary

Hydration mismatch errors were using console.error directly, bypassing Vue's error handling pipeline. This meant onErrorCaptured and app.config.errorHandler could not catch these errors, making it impossible to integrate hydration mismatch reporting with observability tools like Sentry or Datadog.

This change routes the "Hydration completed but contains mismatches" error through Vue's handleError() pipeline:

  • Adds ErrorCodes.HYDRATION_MISMATCH to the error code enum so the error type is properly identified
  • Modifies logMismatchError() to accept a parentComponent parameter and call handleError() instead of console.error()
  • Updates all 7 call sites of logMismatchError() to pass the current parentComponent
  • Preserves backward compatibility: when no error handler is configured, the error still reaches console.error through handleError's default logError fallback
  • The error is an Error object (not a plain string), providing a proper stack trace

Example

const app = createSSRApp(App)

// Now works — previously these could not catch hydration mismatches
app.config.errorHandler = (err) => {
  reportToSentry(err) // err.message === 'Hydration completed but contains mismatches.'
}
// Also works with onErrorCaptured
export default {
  setup() {
    onErrorCaptured((err) => {
      console.log('Caught:', err.message)
      return false // stop propagation
    })
  }
}

Test plan

  • Added test: app.config.errorHandler catches hydration mismatch error
  • Added test: onErrorCaptured catches hydration mismatch error
  • Added test: hydration mismatch error is only reported once (existing "log once" behavior preserved)
  • All existing hydration tests continue to pass (pre-commit hook runs full type check)

Closes #13154

Summary by CodeRabbit

  • Bug Fixes

    • Hydration mismatch errors now properly flow through Vue's error handling system (errorHandler and onErrorCaptured) instead of only logging to console, providing better error visibility and handling control.
  • Tests

    • Added comprehensive test coverage for hydration mismatch error reporting and capture mechanisms.

 vuejs#13154)

Hydration mismatch errors were using `console.error` directly, bypassing
Vue's error handling pipeline. This meant `onErrorCaptured` and
`app.config.errorHandler` could not catch these errors, making it
impossible to integrate hydration mismatch reporting with observability
tools.

This change:
- Adds `ErrorCodes.HYDRATION_MISMATCH` to the error code enum
- Routes hydration mismatch errors through `handleError()` instead of
  `console.error()`, so they flow through `onErrorCaptured` hooks and
  `app.config.errorHandler`
- Passes the parent component instance to `logMismatchError()` at all
  call sites to provide proper component context
- Preserves backward compatibility: when no error handler is configured,
  the error still surfaces via `console.error` through `handleError`'s
  default fallback
- Adds tests verifying both `app.config.errorHandler` and
  `onErrorCaptured` can intercept hydration mismatch errors

Signed-off-by: Pierluigi Lenoci <pierluigilenoci@gmail.com>
@coderabbitai
Copy link
Copy Markdown

coderabbitai Bot commented Apr 24, 2026

📝 Walkthrough

Walkthrough

The changes centralize hydration mismatch error reporting through Vue's error handling pipeline instead of direct console output. A new error code HYDRATION_MISMATCH is introduced, along with a logMismatchError() function that properly routes mismatches to error handlers. An exported reset function supports testing of this new behavior.

Changes

Cohort / File(s) Summary
Error Handling Infrastructure
packages/runtime-core/src/errorHandling.ts
Added HYDRATION_MISMATCH error code and corresponding 'hydration' label to enable routing of mismatch errors through Vue's error handling pipeline.
Hydration Mismatch Reporting
packages/runtime-core/src/hydration.ts
Introduced logMismatchError() function to centralize mismatch emission via handleError instead of direct console.error. Added module-scoped guard hasLoggedMismatchError to prevent duplicate logging and exported resetHydrationMismatchState() for test support.
Test Coverage
packages/runtime-core/__tests__/hydration.spec.ts
Added comprehensive tests verifying that hydration mismatch errors are properly caught by both app.config.errorHandler and component-level onErrorCaptured, with deduplication across multiple mismatches.

Sequence Diagram

sequenceDiagram
    participant Hydration as Hydration System
    participant ErrorLog as logMismatchError()
    participant Handler as handleError()
    participant AppHandler as app.config.errorHandler
    participant Component as onErrorCaptured (Component)

    Hydration->>ErrorLog: mismatch detected<br/>(instance, parentComponent)
    ErrorLog->>ErrorLog: check hasLoggedMismatchError guard
    alt Guard not set
        ErrorLog->>ErrorLog: set hasLoggedMismatchError = true
        ErrorLog->>Handler: handleError(Error, instance,<br/>code: HYDRATION_MISMATCH)
        Handler->>AppHandler: invoke if configured
        AppHandler-->>Handler: error handled/propagated
        Handler->>Component: invoke if ancestor<br/>has onErrorCaptured
        Component-->>Handler: error handled/propagated
    else Guard already set
        ErrorLog->>ErrorLog: skip (already logged once)
    end
Loading

Estimated code review effort

🎯 3 (Moderate) | ⏱️ ~25 minutes

Possibly related PRs

Suggested labels

:hammer: p3-minor-bug, scope:hydration

Suggested reviewers

  • johnsoncodehk
  • Doctor-wu

Poem

🐰 A hydration hop, no more Console's call,
Errors now bubble through handlers for all!
With guards and with grace, we log just once right,
Mismatches caught cleanly—debugging's bright! ✨

🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 66.67% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (4 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title clearly and concisely describes the main change: routing hydration mismatch errors through handleError and fixing the referenced issue #13154.
Linked Issues check ✅ Passed The PR fully implements all objectives from #13154: routes hydration mismatches through handleError, enables onErrorCaptured and app.config.errorHandler capture, provides Error objects with context, and preserves backward compatibility.
Out of Scope Changes check ✅ Passed All changes are directly related to the stated objectives: error code enum addition, logMismatchError refactoring, call site updates, test coverage for error handling, and a reset helper for testing.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

Copy link
Copy Markdown

@coderabbitai coderabbitai Bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 1

🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@packages/runtime-core/src/hydration.ts`:
- Around line 68-92: The current call to handleError(...) always routes through
logError() and emits extra dev warnings; change the logic so handleError is only
invoked when there is an actual consumer (check
instance.appContext.config.errorHandler or hasErrorCaptured(instance)), and when
no consumer exists fall back to directly calling console.error(new
Error('Hydration completed but contains mismatches.')) instead of handleError;
update both the __TEST__ branch and the default branch to use this gating around
handleError and preserve the existing ErrorCodes.HYDRATION_MISMATCH constant
when routing to handleError.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: b4511270-f4c7-4adc-aa7e-d07fa84ea855

📥 Commits

Reviewing files that changed from the base of the PR and between 3310eea and 5e1c9ff.

📒 Files selected for processing (3)
  • packages/runtime-core/__tests__/hydration.spec.ts
  • packages/runtime-core/src/errorHandling.ts
  • packages/runtime-core/src/hydration.ts

Comment on lines +68 to +92
// Route through Vue's error handling pipeline so that
// onErrorCaptured and app.config.errorHandler can catch it.
if (__TEST__) {
// In test mode, only route through handleError if an error handler is
// actually configured (errorHandler or onErrorCaptured), to avoid adding
// unexpected "Unhandled error" warnings to every mismatch test.
if (
instance &&
(instance.appContext.config.errorHandler || hasErrorCaptured(instance))
) {
handleError(
new Error('Hydration completed but contains mismatches.'),
instance,
ErrorCodes.HYDRATION_MISMATCH,
false,
)
}
} else {
handleError(
new Error('Hydration completed but contains mismatches.'),
instance,
ErrorCodes.HYDRATION_MISMATCH,
false,
)
}
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🛠️ Refactor suggestion | 🟠 Major

Preserve the old fallback when no error handler is present.

handleError(..., false) still goes through logError(), which adds a new dev warning (Unhandled error during execution of hydration) before console.error(err). That means unhandled hydration mismatches now emit extra generic noise in dev, even though the old behavior was just the specific mismatch warning plus the one-off console error.

Please gate the handleError() path on an actual consumer (app.config.errorHandler / ancestor errorCaptured) and keep the old direct console.error(new Error(...)) fallback otherwise.

Proposed fix
 const logMismatchError = (
   instance: ComponentInternalInstance | null = null,
 ) => {
   if (hasLoggedMismatchError) {
     return
   }
   hasLoggedMismatchError = true

-  // Route through Vue's error handling pipeline so that
-  // onErrorCaptured and app.config.errorHandler can catch it.
-  if (__TEST__) {
-    // In test mode, only route through handleError if an error handler is
-    // actually configured (errorHandler or onErrorCaptured), to avoid adding
-    // unexpected "Unhandled error" warnings to every mismatch test.
-    if (
-      instance &&
-      (instance.appContext.config.errorHandler || hasErrorCaptured(instance))
-    ) {
-      handleError(
-        new Error('Hydration completed but contains mismatches.'),
-        instance,
-        ErrorCodes.HYDRATION_MISMATCH,
-        false,
-      )
-    }
-  } else {
-    handleError(
-      new Error('Hydration completed but contains mismatches.'),
-      instance,
-      ErrorCodes.HYDRATION_MISMATCH,
-      false,
-    )
-  }
+  const err = new Error('Hydration completed but contains mismatches.')
+  const hasConsumer =
+    !!instance &&
+    (instance.appContext.config.errorHandler || hasErrorCaptured(instance))
+
+  if (hasConsumer) {
+    handleError(err, instance, ErrorCodes.HYDRATION_MISMATCH, false)
+  } else if (!__TEST__) {
+    console.error(err)
+  }
 }
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
// Route through Vue's error handling pipeline so that
// onErrorCaptured and app.config.errorHandler can catch it.
if (__TEST__) {
// In test mode, only route through handleError if an error handler is
// actually configured (errorHandler or onErrorCaptured), to avoid adding
// unexpected "Unhandled error" warnings to every mismatch test.
if (
instance &&
(instance.appContext.config.errorHandler || hasErrorCaptured(instance))
) {
handleError(
new Error('Hydration completed but contains mismatches.'),
instance,
ErrorCodes.HYDRATION_MISMATCH,
false,
)
}
} else {
handleError(
new Error('Hydration completed but contains mismatches.'),
instance,
ErrorCodes.HYDRATION_MISMATCH,
false,
)
}
const logMismatchError = (
instance: ComponentInternalInstance | null = null,
) => {
if (hasLoggedMismatchError) {
return
}
hasLoggedMismatchError = true
const err = new Error('Hydration completed but contains mismatches.')
const hasConsumer =
!!instance &&
(instance.appContext.config.errorHandler || hasErrorCaptured(instance))
if (hasConsumer) {
handleError(err, instance, ErrorCodes.HYDRATION_MISMATCH, false)
} else if (!__TEST__) {
console.error(err)
}
}
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@packages/runtime-core/src/hydration.ts` around lines 68 - 92, The current
call to handleError(...) always routes through logError() and emits extra dev
warnings; change the logic so handleError is only invoked when there is an
actual consumer (check instance.appContext.config.errorHandler or
hasErrorCaptured(instance)), and when no consumer exists fall back to directly
calling console.error(new Error('Hydration completed but contains mismatches.'))
instead of handleError; update both the __TEST__ branch and the default branch
to use this gating around handleError and preserve the existing
ErrorCodes.HYDRATION_MISMATCH constant when routing to handleError.

@pierluigilenoci
Copy link
Copy Markdown
Author

Hi — friendly ping. Is this PR still on the radar for review? Happy to rebase or make changes if needed. Thanks!

@edison1105
Copy link
Copy Markdown
Member

I agree the observability use case is valid, but I don't think this should be merged in the current form.

Routing hydration mismatch reporting through handleError() changes the existing semantics: onErrorCaptured can now stop propagation, existing app.config.errorHandler hooks will start receiving hydration mismatches, and the fallback console behavior changes when there is no explicit consumer. So this is more of a minor feature than a bug fix.

I think the safer direction is to preserve the current default behavior and only expose hydration mismatch reporting to user handlers when there is an explicit consumer, or consider an app-level / warning-handler based API instead of putting it fully into the component error boundary pipeline.

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

Labels

Projects

None yet

Development

Successfully merging this pull request may close these issues.

"Hydration completed but contains mismatches" error not caught by the Vue error handler

2 participants