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

  1. Add a top-level `import` for the component used with `client:`/`server:defer`.
  2. Match the imported name to the JSX tag exactly.
  3. 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.
  4. 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

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


AI-assisted analysis of withastro/astro@d081033d5f (2026-08-12). Data as JSON: /api/errors/53cd5d5faefcbdc4. Report an issue: GitHub.