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
- Import the hydrated component directly with a static import in the .astro file that renders it
- Give the component module a plain default export and hydrate that
- Remove indirection (dynamic import, spread re-export, virtual module) between the .astro file and the component file
- 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
- Use static imports for every component that gets a client:* directive
- Give hydrated components a default export
- Avoid dynamic import(), import * as barrels, and virtual modules between the .astro file and hydrated components
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
- Error: invalid hydration directive
- MissingMediaQueryDirective
- NoClientOnlyHint
- NoMatchingRenderer
- Unexpected template-exit instruction without a matching…
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)