withastro/astro · error · AstroError

NoMatchingImport

NoMatchingImport

Error message

Could not render `${componentName}`. No matching import has been found for `${componentName}`.

What it means

To generate the hydration script, Astro needs to know which export of the component module to load in the browser (metadata.componentExport.value, filled in during compile-time import analysis). If that value is empty, the component's import could not be statically analyzed, and generateHydrateScript throws NoMatchingImport for the component's display name.

Solutions

  1. Import the hydrated component directly with a static import in the .astro file that renders it
  2. Give the component module a plain default export and hydrate that
  3. Remove indirection (dynamic import, spread re-export, virtual module) between the .astro file and the component file
  4. Keep client:* usage in the same module that performs the static import

Example fix

// before
const Widget = await import(`../widgets/${name}.jsx`);
<Widget client:load />

// after
import Widget from '../widgets/Widget.jsx';
<Widget client:load />
Defensive patterns

Strategy: validation

Validate before calling

// dev-time sanity check: hydrated components must resolve to a real export
const mod = await import(componentUrl);
if (!(componentExportName in mod)) {
  throw new Error('Export ' + componentExportName + ' missing — client:* requires a statically analyzable import');
}

Type guard

function hasExport(mod: Record<string, unknown>, name: string): boolean {
  return name === 'default' ? 'default' in mod : Object.hasOwn(mod, name);
}

Prevention

When it happens

Trigger: Applying client:* to a component reached through dynamic import (import(`./widgets/${name}.jsx`)), import * as barrels that re-export framework components, virtual modules from custom plugins, or re-export chains the compiler cannot trace back to a real file export.

Common situations: Component registries built with dynamic imports; index.ts barrel files re-exporting hydrated components; custom Vite plugins serving framework components from virtual paths; refactors that moved hydrated components behind indirection.

Related errors


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

Appendix: source

Thrown at packages/astro/src/runtime/server/hydration.ts:134

interface HydrateScriptOptions {
	renderer: SSRLoadedRenderer;
	result: SSRResult;
	astroId: string;
	props: Record<string | number, any>;
	attrs: Record<string, string> | undefined;
}

/** For hydrated components, generate a <script type="module"> to load the component */
export async function generateHydrateScript(
	scriptOptions: HydrateScriptOptions,
	metadata: Required<AstroComponentMetadata>,
): Promise<SSRElement> {
	const { renderer, result, astroId, props, attrs } = scriptOptions;
	const { hydrate, componentUrl, componentExport } = metadata;

	if (!componentExport.value) {
		throw new AstroError({
			...AstroErrorData.NoMatchingImport,
			message: AstroErrorData.NoMatchingImport.message(metadata.displayName),
		});
	}

	const island: SSRElement = {
		children: '',
		props: {
			// This is for HMR, probably can avoid it in prod
			uid: astroId,
		},
	};

	// Attach renderer-provided attributes
	if (attrs) {
		for (const [key, value] of Object.entries(attrs)) {
			island.props[key] = escapeHTML(value);
		}

View on GitHub (pinned to 52e6c34790)