withastro/astro · error · Error
Expected a matching import for component
Error message
Expected a matching import for component `${tagName}`. Did you forget to import it? What it means
The hast-astro-metadata pass (in the mdx integration's satteri module) collects island metadata for MDX builds: every component element carrying a `client:` directive or `server:defer` must match an import specifier so its module can be located for hydration/defer metadata. When `findMatchingImport` returns nothing, it throws with the tag name and a hint about the missing import. It is the metadata-collection sibling of the NoMatchingImport error.
Solutions
- Import the component in the same .mdx file with a name matching the tag.
- Verify the import specifier is not named-vs-default mismatched (import the default export under the tag's name).
- Drop the directive if the tag is plain HTML or should not hydrate/defer.
- Note `server:defer` components are also subject to this check — import them too.
Example fix
import Hero from '../../components/Hero'; <Hero server:defer />
Defensive patterns
Strategy: validation
Validate before calling
// CI check: components used with client:* OR server:defer in MDX must be imported
import { globSync } from 'glob';
import fs from 'node:fs';
const importRe = /import\s+([A-Za-z_$][\w$]*)\s+from\s+['"][^'"]+['"]/g;
const tagRe = /<([A-Z][\w.]*)[^>]*?\s(?:client:[a-z]+|server:defer)/g;
for (const f of globSync('src/**/*.mdx')) {
const src = fs.readFileSync(f, 'utf8');
const imported = new Set([...src.matchAll(importRe)].map((m) => m[1]));
for (const m of src.matchAll(tagRe)) {
if (!imported.has(m[1])) throw new Error(`${f}: <${m[1]}> used with client:*/server:defer without an import`);
}
} Prevention
- Import every component that carries client:* or server:defer — both directives are checked.
- Match the import specifier name to the JSX tag exactly.
- Enforce with an MDX lint in CI so omissions fail before the compiler does.
When it happens
Trigger: An .mdx file using `<Widget client:visible />` or `<Widget server:defer />` where no import named Widget exists, or the imported name differs from the tag.
Common situations: Omitting imports when copying component examples into MDX; using `server:defer` on a component that was never imported; renaming an import without updating its usages.
Related errors
- Could not render ` `. No matching import has been found for…
- You are attempting to render <
- You are attempting to render <
- Astro components cannot be used in the browser. Tried to…
- [@astrojs/mdx] Importing `getContainerRenderer` from…
AI-assisted analysis of withastro/astro@52e6c34790 (2026-08-18).
Data as JSON: /api/errors/53cd5d5faefcbdc4.
Report an issue: GitHub.
Appendix: source
Thrown at packages/integrations/mdx/src/satteri/hast-astro-metadata.ts:104
}
}
function processJsxNode(
node: MdxJsxHastNode,
ctx: HastVisitorContext,
imports: Map<string, Set<ImportSpecifier>>,
filePath: string,
) {
const tagName = node.name;
if (!tagName || !isComponent(tagName)) return;
const hasClient = hasDirective(node, 'client:');
const hasServerDefer = !hasClient && hasDirective(node, 'server:defer');
if (!hasClient && !hasServerDefer) return;
const matchedImport = findMatchingImport(tagName, imports);
if (!matchedImport) {
throw new Error(
`Expected a matching import for component \`${tagName}\`. Did you forget to import it?`,
);
}
if (matchedImport.path.endsWith('.astro') && hasClient) {
let clientAttr = 'client:*';
for (const a of node.attributes) {
if (a.type === 'mdxJsxAttribute' && a.name.startsWith('client:')) {
clientAttr = a.name;
break;
}
}
console.warn(
`You are attempting to render <${tagName} ${clientAttr} />, but ${tagName} is an Astro component. Astro components do not render in the client and should not have a hydration directive. Please use a framework component for client rendering.`,
);
}
const resolvedPath = resolvePath(matchedImport.path, filePath);View on GitHub (pinned to 52e6c34790)