withastro/astro · warning

You are attempting to render <${tagName} ${clientAttr} />, b

Error message

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.

What it means

The hast-based counterpart of the MDX metadata analyzer (used when JSX content flows through the markdown/hast pipeline): after matching an element to its import, if the import path ends in .astro and the node carries a client directive (hasClient), it warns that Astro components do not render in the client. The exact attribute name is included when one is found; otherwise the generic client:* placeholder is shown.

Source

Thrown at packages/integrations/mdx/src/satteri/hast-astro-metadata.ts:117

	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);
	const exportName =
		matchedImport.name === '*' ? tagName.split('.').slice(1).join('.') : matchedImport.name;

	const metadata = (ctx.data.__astroMetadata ??= createDefaultAstroMetadata());
	if (hasClient && findAttrValue(node, 'client:only') !== null) {
		metadata.clientOnlyComponents.push({
			exportName: matchedImport.name,
			localName: '',
			specifier: tagName,
			resolvedPath,
		});
		ctx.setProperty(node, 'client:display-name', tagName);
		ctx.setProperty(node, 'client:component-path', resolvedPath);

View on GitHub (pinned to 52e6c34790)

Solutions

  1. Drop the client:* attribute from the .astro component element
  2. Hydrate a framework component embedded inside (or beside) the Astro component instead
  3. If the directive was accidental, simply delete it to silence the warning

Example fix

# before  src/content/docs/page.mdx
import Nav from '../../components/Nav.astro';
<Nav client:visible />

# after
import Nav from '../../components/Nav.astro';
import Search from '../../components/Search.svelte';
<Nav>
  <Search client:visible slot="search" />
</Nav>
Defensive patterns

Strategy: type-guard

Validate before calling

# CI review aid: surface all client: directives in MDX sources;
# each hit whose component import ends in .astro is a bug
grep -rn --include='*.mdx' 'client:' src/content

Type guard

const isAstroComponentFactory = (c: unknown): c is () => any =>
  typeof c === 'function' &&
  (c as { isAstroComponentFactory?: boolean }).isAstroComponentFactory === true;

// branch before attaching hydration props in shared/dynamic components
const hydration = isAstroComponentFactory(Comp) ? {} : { 'client:visible': true };

Prevention

When it happens

Trigger: An .mdx file (or markdown processed through the hast pipeline) using an imported .astro component with a client:* attribute — e.g. <Nav client:visible /> where Nav resolves to a .astro file. Warned during compilation of the containing file.

Common situations: Same as the rehype variant: docs and content sites in MDX, hydration directives copy-pasted from framework components, components migrated from React/Vue to .astro with leftover client: attributes.

Related errors


AI-assisted analysis of withastro/astro@52e6c34790 (2026-08-18). Data as JSON: /api/errors/072878b125836280. Report an issue: GitHub.