withastro/astro · error · Error
Expected a matching import for component `${tagName}`. Did y
Error message
Expected a matching import for component `${tagName}`. Did you forget to import it? What it means
In the Sätteri (MDX) metadata analyzer, `analyzeAstroMetadata` requires every JSX component carrying a `client:` or `server:defer` directive to have a statically-visible matching import. When `findMatchingImport(tagName, imports)` returns falsy, it throws a plain `Error` with a 'Did you forget to import it?' hint. This mirrors `NoMatchingImport` but is the Sätteri code path's local equivalent.
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 d081033d5f)
Solutions
- Add a top-level `import` for the component used with `client:`/`server:defer`.
- Match the imported name to the JSX tag exactly.
- If the component is provided via the MDX `components` prop (not an import), remove the `client:` directive — it cannot be statically analyzed as an island.
- Switch to a static re-export the analyzer can follow.
Example fix
import { Chart } from '../components/Chart.tsx';
<Chart client:visible /> Defensive patterns
Strategy: validation
Validate before calling
function assertIslandImports(tree: any, imports: Map<string, Set<any>>) {
const missing: string[] = [];
visit(tree, (n: any) => {
if (!n.name || !/^[A-Z]/.test(n.name)) return;
const hasClient = n.attributes?.some((a: any) => a.name?.startsWith('client:'));
const hasDefer = n.attributes?.some((a: any) => a.name === 'server:defer');
if ((hasClient || hasDefer) && !imports.has(n.name)) missing.push(n.name);
});
return missing;
} Type guard
function isStaticallyImported(tagName: string, imports: Map<string, Set<unknown>>): boolean {
return imports.has(tagName);
} Prevention
- Import every component used with `client:`/`server:defer`.
- Don't rely on the MDX `components` prop for islands.
- Lint Sätteri MDX files for hydration directives without imports.
When it happens
Trigger: Same family as error 268 but in the Sätteri pipeline: `<Comp client:visible />` (or `server:defer`) in MDX with no matching static import. Tag name doesn't match the imported binding. Component provided via provider/MDX provider component map rather than an import.
Common situations: Using the Sätteri MDX processor and referencing a component by a hydration directive without importing it. Renaming imports. Components supplied through `components={{}}` prop instead of imports (these are not statically importable).
Related errors
- NoMatchingImport
- [MDX] A Sätteri plugin attempted to inject invalid frontmatt
- [MDX] A remark or rehype plugin attempted to inject invalid
- The markdown processor "${processor.name}" does not provide
- [RSS] You can only glob entries within 'src/pages/' when pas
AI-assisted analysis of withastro/astro@d081033d5f (2026-08-12).
Data as JSON: /api/errors/53cd5d5faefcbdc4.
Report an issue: GitHub.