How to Fix: Issue where Terrace “react” and “react-dom” 19.0.0 versions do not match as “peerDependency” of module
React 19 peerDependency mismatch in Terrace: why react and react-dom break Webpack builds and how to fix it
This bug shows up when Terrace resolves react and react-dom to different effective versions or ranges, even though React 19 expects them to move in lockstep. The result is usually a noisy peerDependency warning during install, a confusing Webpack bundle outcome, or runtime behavior that looks unrelated until you inspect the dependency tree closely.
The issue described against the Next.js canary package manifest typically appears when a package declares one React package as compatible with 19.0.0 while another dependency in the same graph still expects a different range, prerelease tag, or unresolved alias. Because react and react-dom are tightly coupled, even a small mismatch can become visible in package manager resolution and bundler output.
Understanding the Root Cause
At a technical level, this is a dependency graph consistency problem.
react and react-dom are published as separate packages, but they are versioned and consumed as a pair. React DOM imports internal contracts from React that assume the same major and, in practice for production safety, the same exact version. If Terrace or one of its dependencies declares:
- react as a direct dependency pinned to 19.0.0
- react-dom as a peerDependency with a different range, such as ^18, a prerelease tag, or a looser unresolved range
- or the inverse relationship
then package managers like npm, pnpm, or Yarn can resolve a tree where the two packages are not treated as a guaranteed pair.
This tends to happen for one of four reasons:
- A library updates react to 19 but forgets to update react-dom in peerDependencies.
- A monorepo contains multiple package manifests and one workspace keeps an older peer range.
- A lockfile still pins an older transitive version, so the manifest and installed tree diverge.
- Webpack bundles code from a workspace or package boundary that causes duplicate React installations or mixed resolution paths.
Why Webpack makes this more visible: bundlers operate on the resolved installed tree, not just the package.json intent. If your install creates two copies of React or resolves react-dom against a different peer set than react, the bundle can include duplicated module references, invalid hook call scenarios, or warnings that appear only after build time.
In short, the root cause is not simply that the versions are different. It is that the module ecosystem expects react and react-dom to be declared and resolved consistently across dependencies, peerDependencies, and the final lockfile.
Step-by-Step Solution
The fix is to make sure every relevant package declares the same supported React 19 range and that your installed dependency tree actually reflects that declaration.
1. Inspect the current declarations
Open the package.json for Terrace and any related workspace packages. Check all of these fields:
- dependencies
- devDependencies
- peerDependencies
- optionalDependencies
You are looking for any version mismatch between react and react-dom.
"peerDependencies": {
"react": "19.0.0",
"react-dom": "19.0.0"
}
If the package is a library, keeping both in peerDependencies is usually the correct approach. If it is an app, they typically belong in dependencies.
2. Align the versions exactly
If Terrace is a library consumed by another app, update the peer declarations so both packages advertise the same compatibility.
{
"name": "terrace",
"peerDependencies": {
"react": "19.0.0",
"react-dom": "19.0.0"
}
}
If you need a range instead of a pin, keep the ranges identical and intentional:
{
"peerDependencies": {
"react": "^19.0.0",
"react-dom": "^19.0.0"
}
}
Avoid mixing exact and ranged versions unless you have a very specific release policy.
3. Verify the app-level dependencies too
If the consuming application depends on Terrace, it also must install matching versions:
npm install react@19.0.0 react-dom@19.0.0
Or with pnpm:
pnpm add react@19.0.0 react-dom@19.0.0
Or with Yarn:
yarn add react@19.0.0 react-dom@19.0.0
4. Clear stale lockfile state
Even after fixing package.json, your lockfile may still hold an older resolution. Remove install artifacts and reinstall cleanly.
rm -rf node_modules package-lock.json pnpm-lock.yaml yarn.lock
npm install
Use only the lockfile that matches your package manager. In a real project, do not delete multiple lockfiles unless you are intentionally standardizing the repo.
5. Audit the final dependency tree
Confirm that only the expected versions are installed.
npm ls react react-dom
With pnpm:
pnpm list react react-dom --depth 10
You want output that resolves both packages to the same effective version. If you see duplicates, identify which package is pulling the older range.
6. Fix monorepo and workspace drift
In a workspace setup, one package may still reference React 18 or a prerelease. Search the repository for all React declarations.
grep -R '"react"\|"react-dom"' .
Then normalize them across packages.
{
"peerDependencies": {
"react": "19.0.0",
"react-dom": "19.0.0"
},
"devDependencies": {
"react": "19.0.0",
"react-dom": "19.0.0"
}
}
This pattern is common for component libraries: peerDependencies define what consumers must provide, while devDependencies let the library build and test locally against the same version.
7. Add package manager overrides if a transitive package lags behind
If a third-party dependency still pulls an incompatible range, use an override as a temporary mitigation.
For npm:
{
"overrides": {
"react": "19.0.0",
"react-dom": "19.0.0"
}
}
For pnpm:
{
"pnpm": {
"overrides": {
"react": "19.0.0",
"react-dom": "19.0.0"
}
}
}
For Yarn:
{
"resolutions": {
"react": "19.0.0",
"react-dom": "19.0.0"
}
}
This should be treated as a short-term fix, not a substitute for correcting the library metadata.
8. Rebuild the Webpack bundle and validate
After reinstalling, rebuild and check for warnings or duplication.
npm run build
If the issue originally appeared in a Webpack output bundle, inspect whether React is bundled more than once and whether the runtime now resolves a single consistent copy.
9. Recommended final package.json patterns
For a library:
{
"peerDependencies": {
"react": "^19.0.0",
"react-dom": "^19.0.0"
},
"devDependencies": {
"react": "19.0.0",
"react-dom": "19.0.0"
}
}
For an application:
{
"dependencies": {
"react": "19.0.0",
"react-dom": "19.0.0"
}
}
If you are preparing a fix for the upstream issue, the main correction is to update the mismatched peerDependency entry so the package advertises support for the same React 19 version on both packages.
Common Edge Cases
1. React and React DOM match in package.json but not in node_modules
This usually means the lockfile or a transitive dependency is overriding your intended versions. Run a dependency tree audit and reinstall cleanly.
2. Duplicate React copies in a monorepo
If one workspace depends on React directly while another bundles its own copy, Webpack may include multiple React instances. That can trigger invalid hook call errors even when versions look correct. Make sure internal packages use peerDependencies for React instead of bundling it.
3. A package supports React 19 but still declares an older peer range
This is common during release transitions. The code may work, but the metadata is stale. In that case, use an override temporarily and update the package manifest in the source repository.
4. Prerelease versus stable version mismatches
19.0.0-rc and 19.0.0 are not interchangeable. If one package expects an RC and another expects stable, package managers can resolve them separately.
5. Next.js canary or framework internals masking the source
Framework packages can make the warning look like a framework bug when the actual mismatch is in your app or a plugin. Trace the full tree rather than assuming the top-level package is the only culprit.
6. Server and client package boundaries
Some builds appear fine in development but fail in production because separate server and client bundles resolve React differently. Always test a production build after changing React version declarations.
FAQ
Should react and react-dom always be the exact same version?
For practical stability, yes. While semver ranges may technically overlap, React and React DOM are designed to be consumed together. Using the same exact version avoids installation ambiguity and bundler issues.
Is this a Webpack bug or a package.json bug?
Usually it is a package metadata issue, not a Webpack bug. Webpack simply exposes the problem because it bundles the resolved dependency graph. The real fix is aligning dependencies and peerDependencies.
Should a library put React 19 in dependencies or peerDependencies?
Most reusable libraries should use peerDependencies for react and react-dom, often with matching devDependencies for local development. Applications should typically list them under dependencies.
The durable fix for this GitHub issue is simple: declare react and react-dom with the same React 19 support policy everywhere, remove stale lockfile state, and verify the installed tree before rebuilding. Once the metadata and resolution agree, the peer dependency warning and bundle inconsistency disappear together.