Avatar

Lessons Learned: Fixing "Cannot use GraphQLObjectType from another module or realm" in Next.js 16 with Turbopack

While migrating the data editing frontend for Commitspark, a GraphQL-based toolkit to manage structured data with Git, to a newer version of Next.js, I hit a runtime error that was deceptively hard to fix:

Cannot use GraphQLObjectType "…" from another module or realm. Ensure that there is only one instance of "graphql" in the node_modules directory...

If you've seen this, you've probably already tried the obvious things:

  • Verify npm ls graphql shows one version
  • Add package-manager overrides or resolutions
  • Add bundler aliases to force a single graphql path
  • Fiddle with Next.js' "externalize this package on the server" options

I tried those too. The surprise: none of that was the real problem.

The fix ended up being simple and architectural:

Harmonize all GraphQL imports in the code to use only the top-level graphql entrypoint.

Once I did that, the error disappeared, and I was able to remove config overrides entirely. This post is the "what happened" and "what I'll do differently next time" version.

The symptom: "another module or realm"

GraphQL JS types (like GraphQLObjectType, GraphQLSchema, GraphQLScalarType) aren't just plain data - they're instances with identity and prototype chains.

So a GraphQL error "Cannot use GraphQLObjectType … from another module or realm" can actually mean:

  • You created a GraphQL type using one runtime instance of the GraphQL module
  • You passed it into objects and functions belonging to another runtime instance of the GraphQL module

Even if both copies are instantiated using the exact same library version, they are still different "realms" if they come from different module instances. This meaning of "realm" is not immediately clear and there appears to be little information about it available on the web.

The red herring: npm ls graphql looked fine

In my case, npm ls graphql showed a single version, fully deduplicated. So why was GraphQL still complaining?

Because "one version on disk" doesn't guarantee "one module instance at runtime" in a modern Next.js app, especially when bundling, splitting, and server/client boundaries are involved.

The real culprit: Deep imports

Commitspark's codebase contained a mix of imports like these:

  • import { isObjectType } from 'graphql'
  • import { GraphQLObjectType } from 'graphql/type'
  • import { GraphQLField } from 'graphql/type/definition'

Although this looks harmless and works fine e.g. in integration tests, it introduces errors down the chain. Those deep import paths can be treated as distinct module entrypoints by build tools. Under Turbopack (and also depending on how code ends up bundled across server/client chunks), that can lead to multiple loaded "instances" of GraphQL internals, even when everything ultimately maps to the same installed package.

The result: GraphQL type objects created in one realm (i.e. instance) get rejected by functions from another.

Config-level fixes that didn't help

Bundler aliasing

With Webpack you can force a single resolution by aliasing graphql to a specific path. While this worked fine with previous versions of Next.js where Webpack was available, Next.js 16+ is moving more and more to Turbopack as a bundler, where Webpack configurations (obviously) don't apply.

With Turbopack, I tried aliasing to absolute filesystem paths but got additional errors and then realized that this wouldn't address the issue I was facing anyway.

Externalizing GraphQL on the server

Next.js has configuration to opt packages out of bundling on the server. That can help some duplication cases, but it also makes Node's resolver the final authority. Node cannot always resolve deep GraphQL subpath imports the same way the bundler can, leading to errors like:

Package graphql can't be external … request graphql/type … could not be resolved by Node.js …

In short: attempts to use configuration-level fixes didn't resolve the issue.

The actual fix: only import from graphql

Once I replaced every deep GraphQL import like

import { GraphQLObjectType } from 'graphql/type' import { GraphQLField } from 'graphql/type/definition'

to come from the top-level entrypoint only, like

import { GraphQLObjectType, GraphQLField, GraphQLType, isObjectType, isNonNullType, isScalarType, isUnionType, } from 'graphql'

the "another module or realm" error disappeared entirely.

Even better: I could remove the special Next.js Webpack configuration I had in place for past releases.

What I learned (the durable takeaways)

  1. GraphQL is extremely sensitive to module identity
    It's not enough to have one version installed. You need one runtime identity.
  2. Deep imports are a footgun when processed by bundlers
    They can create multiple "paths" into the same package that bundlers treat differently.
  3. As so often, code simplicity wins over configuration
    Bundler aliasing and server externalization can be useful tools, but they don't always work as expected. In this case, correctness came from simplifying the codebase itself.

Closing thoughts

As coders facing "duplicate module" errors in JavaScript ecosystems, we quickly assume these must be triggered by having multiple versions of the same dependencies installed. In many cases this is the cause, but when using the graphql library, you may actually be looking at an error related to duplicate runtime instances instead.

The fix for Commitspark was simple: unify imports to only use graphql, delete special bundler configs, and add a linter rule that prevents deep imports and this issue from occurring again in the future.

Simple is good. Simple ships.