Listing React, Vue, or any consumer-provided framework in dependencies instead of peerDependencies installs a second copy of that framework in the consumer's node_modules. For React specifically, this triggers the infamous "Invalid hook call" error because two separate React instances exist at runtime — a hard crash, not a degraded experience. It also silently doubles the framework's bundle weight. SSDF PS.3.2 flags this as a supply chain concern: you're injecting unverified versions of major frameworks into consumer apps. ISO 25010 interoperability fails when your version range conflicts with the host app.
High because duplicate framework installations cause runtime crashes (React hooks, Vue reactivity) and inflated bundle sizes that directly affect the end user.
Move framework dependencies from dependencies to peerDependencies with broad version ranges, and keep them in devDependencies for local development.
{
"peerDependencies": {
"react": "^17.0.0 || ^18.0.0 || ^19.0.0",
"react-dom": "^17.0.0 || ^18.0.0 || ^19.0.0"
},
"devDependencies": {
"react": "^18.0.0",
"react-dom": "^18.0.0"
}
}
For optional peer dependencies (e.g., a Vue adapter that's only needed for Vue users), add a peerDependenciesMeta entry: { "vue": { "optional": true } }. Test with the oldest and newest versions in your peer range.
ID: sdk-package-quality.package-config.peer-deps
Severity: high
What to look for: Enumerate every dependency in package.json. For each, classify whether it should be a dependency, devDependency, or peerDependency. check dependencies and peerDependencies in package.json. If the package uses a framework or runtime that the consumer is expected to provide (React, Vue, Angular, Svelte, Node.js built-in modules), it should be in peerDependencies, not dependencies. Common violations:
react or react-dom in dependencies instead of peerDependenciesvue in dependencies instead of peerDependencies@angular/core in dependencies instead of peerDependenciesnext in dependencies for a Next.js pluginexpress in dependencies for Express middlewarePass criteria: Framework and runtime dependencies that consumers are expected to provide are listed in peerDependencies (with corresponding peerDependenciesMeta for optional ones). The package's dependencies only contain libraries that the package itself bundles or needs to function independently — at least 1 framework dependency (React, Vue, etc.) must be a peerDependency if the SDK integrates with it. Report: "X dependencies classified, all Y correctly categorized."
Fail criteria: A framework dependency that the consumer must provide (React, Vue, Angular, etc.) is listed in dependencies instead of peerDependencies. This causes version duplication and potential runtime errors when the consumer has a different version installed.
Skip (N/A) when: The package has no framework dependencies — it's a standalone utility with no peer runtime requirements. Also skip for Python (uses install_requires with version ranges), Rust (peer dependencies are not a convention), and Go (module system handles this differently).
Detail on fail: "react and react-dom are in dependencies instead of peerDependencies. This will install a separate copy of React alongside the consumer's version, causing 'Invalid hook call' errors and increased bundle size."
Remediation: Peer dependencies tell npm that your package works WITH the consumer's version of a framework, rather than bringing its own copy.
// Before — bundling React with your library:
{
"dependencies": {
"react": "^18.0.0",
"react-dom": "^18.0.0"
}
}
// After — declaring React as a peer:
{
"peerDependencies": {
"react": "^17.0.0 || ^18.0.0 || ^19.0.0",
"react-dom": "^17.0.0 || ^18.0.0 || ^19.0.0"
},
"devDependencies": {
"react": "^18.0.0",
"react-dom": "^18.0.0"
}
}
Keep the framework in devDependencies for local development and testing. Use broad version ranges in peerDependencies to maximize compatibility.