How to Fix: Getting `Error: Cannot find module ‘./test.mdx?static=567&dynamic=123’` for dynamic import with interpolated `resourceQuery`
Table of Contents
Encountering Error: Cannot find module './test.mdx?static=567&dynamic=123' in your Next.js application, especially when using dynamic imports with interpolated resourceQuery, is a common pitfall that often points to a misunderstanding of how bundlers like Webpack (which Next.js relies on) resolve modules and how resourceQuery is intended to be used. This guide will walk you through the technical reasons behind this error and provide a robust solution, ensuring your dynamic MDX components load as expected.
Understanding the Root Cause: Why Dynamic Imports Break with Interpolated Queries
The error Cannot find module './test.mdx?static=567&dynamic=123' occurs because you’re attempting to pass runtime data (like staticId and dynamicId) as part of the module’s resourceQuery within a dynamic import() statement. Let’s break down why this approach fails:
Webpack’s Module Resolution and Dynamic Imports
When Webpack processes your code during the build phase, it builds a dependency graph of all modules. For dynamic imports (import(...)), Webpack performs static analysis to identify potential import paths, which then become separate bundles (code splitting). It expects these paths to be largely predictable or follow a known pattern (e.g., a glob pattern with require.context).
- Static Analysis Limitation: When you interpolate variables directly into the module path, especially into the
resourceQuery, Webpack cannot statically determine all possible module paths at build time. It doesn’t know what valuesstaticIdordynamicIdwill take at runtime. - Module Identity: From Webpack’s perspective,
./test.mdxis a different module identifier than./test.mdx?static=1, and both are different from./test.mdx?static=2. Because theresourceQuerysignificantly alters the module identifier, Webpack cannot simply bundle all possible permutations. It tries to resolve the exact string passed toimport(). When that exact string (with specific interpolated values) doesn’t match a module it has processed and bundled, it throws a "Cannot find module" error.
Misuse of resourceQuery for Runtime Data
The resourceQuery (the ?key=value part of a module path) has a specific purpose in Webpack: to pass options to a loader that processes the module. For example, my-image.svg?react might tell an SVG loader to transform the SVG into a React component. It modifies how the module is processed at build time, not how it behaves at runtime with dynamic data.
Using resourceQuery to pass runtime props to a component is an anti-pattern because:
- It creates an infinite number of potential module identifiers, none of which Webpack can predictably bundle.
- Runtime data should be passed as props to the component once it’s loaded, not encoded into its import path.
Step-by-Step Solution: Correcting Dynamic MDX Imports
The solution involves two key steps: simplifying your dynamic import to resolve a static module path and then passing your dynamic data directly as component props.
1. Identify the Incorrect Pattern
Your current code likely resembles this (from the provided reproduction):
import dynamic from 'next/dynamic';
import { useState } from 'react';
export default function Home() {
const [staticId, setStaticId] = useState(123);
const [dynamicId, setDynamicId] = useState(456);
const MdxComponent = dynamic(
() => {
// THIS IS THE PROBLEM: Interpolating variables into the resourceQuery
return import(`../components/test.mdx?static=${staticId}&dynamic=${dynamicId}`).then(
(mod) => mod.default,
);
},
{ ssr: false },
);
return (
<div>
<MdxComponent />
<button onClick={() => setStaticId(Math.random())}>Change Static ID</button>
<button onClick={() => setDynamicId(Math.random())}>Change Dynamic ID</button>
</div>
);
}
The line import(`../components/test.mdx?static=${staticId}&dynamic=${dynamicId}`) is the culprit.
2. Refactor Dynamic Import
Modify your dynamic import to always point to the static module path, without any interpolated resourceQuery. Webpack can then successfully resolve and bundle ../components/test.mdx.
import dynamic from 'next/dynamic';
import { useState } from 'react';
export default function Home() {
const [staticId, setStaticId] = useState(123);
const [dynamicId, setDynamicId] = useState(456);
// CORRECT: Import the MDX module directly, without any dynamic resourceQuery
const MdxComponent = dynamic(
() => import('../components/test.mdx').then((mod) => mod.default),
{ ssr: false }
);
return (
<div>
{/* ... (render component with props in the next step) ... */}
</div>
);
}
3. Pass Data as Component Props
Once MdxComponent is successfully loaded, it behaves like any other React component. Pass your dynamic staticId and dynamicId directly as props.
import dynamic from 'next/dynamic';
import { useState } from 'react';
export default function Home() {
const [staticId, setStaticId] = useState(123);
const [dynamicId, setDynamicId] = useState(456);
// Correct dynamic import: no resourceQuery interpolation
const MdxComponent = dynamic(
() => import('../components/test.mdx').then((mod) => mod.default),
{ ssr: false } // Using ssr: false is important if the MDX component relies on browser APIs
);
return (
<div>
{/* Pass props directly to the rendered MDX component */}
<MdxComponent staticId={staticId} dynamicId={dynamicId} />
<button onClick={() => setStaticId(Math.random())}>Change Static ID</button>
<button onClick={() => setDynamicId(Math.random())}>Change Dynamic ID</button>
</div>
);
}
And ensure your components/test.mdx file correctly accesses these props:
# Hello MDX
Static ID: {props.staticId}
Dynamic ID: {props.dynamicId}
By following these steps, you correctly separate the concerns of module resolution (handled statically by Webpack) and runtime data passing (handled by React props), resolving the "Cannot find module" error.
Common Edge Cases
When resourceQuery IS Necessary (and how to handle it)
There are legitimate cases where resourceQuery is used, typically to configure a Webpack loader. For instance:
import './style.css?inline'to inline CSS.import './image.svg?url'orimport './image.svg?react'to process SVGs differently.
If you genuinely need to conditionally apply a resourceQuery based on *build-time* logic (e.g., different environments), you would typically use a Webpack configuration to define aliases or rules, or rely on environment variables that are resolved at build time. You cannot interpolate runtime variables into a resourceQuery for conditional loader behavior. If the query parameters change, Webpack would perceive them as completely different modules, leading to the same "cannot find module" error. The key is that resourceQuery operations are resolved during compilation, not at runtime.
Server-Side Rendering (SSR) Considerations
The original reproduction uses { ssr: false } for the dynamic import. This tells Next.js to only render the component on the client-side. If your MDX component needs to be rendered on the server (for SEO, initial load performance, etc.), you would typically omit ssr: false. In that case, ensure that the MDX file and any of its dependencies are compatible with a Node.js environment.
Type Safety with MDX Components
For better type safety with your MDX component, you can define an interface for its props. For example:
// types/mdx.d.ts or within your component file
interface MyMdxComponentProps {
staticId: number;
dynamicId: number;
}
declare module '../components/test.mdx' {
import type { ComponentType } from 'react';
const Component: ComponentType<MyMdxComponentProps>;
export default Component;
}
This helps TypeScript understand the expected props when you use <MdxComponent staticId={staticId} dynamicId={dynamicId} />.
FAQ: Addressing Your Questions
What exactly is resourceQuery in Webpack?
The resourceQuery is the part of a module request string that comes after the ? (question mark), like ./file.js?foo=bar&baz=qux. It’s primarily used to pass custom options or instructions to Webpack loaders that process the module. It allows a single file to be processed in different ways without changing the file’s content or path. For example, file.svg?url might tell a loader to treat an SVG as a URL, while file.svg?react might tell it to transform the SVG into a React component.
Can I pass props to dynamically imported components in Next.js?
Absolutely, and this is the standard and correct way to provide runtime data to dynamically loaded components. Once a component is loaded via next/dynamic, it behaves just like any other React component. You can pass any data it needs as props when you render it, for example: <MyDynamicComponent prop1={value1} prop2={value2} />.
How does this issue relate to code splitting in Next.js?
This issue directly impacts code splitting. When you use dynamic imports (import() or next/dynamic), Webpack performs static analysis to identify potential modules that can be split into separate JavaScript bundles. If the module identifier (including the resourceQuery) is completely dynamic due to runtime interpolation, Webpack cannot identify a finite, predictable set of modules to bundle. It won’t know which specific chunk to create or load, leading to the "Cannot find module" error at runtime because the requested module path was never bundled in the first place.