withastro/astro · error · Error

SVG file does not contain an <svg> element

Error message

SVG file does not contain an <svg> element

What it means

After optimization (or directly if no optimizer is configured), Astro parses the SVG with parse-html-parser and looks for the first element node named `svg`. If none is found it throws a plain Error (not an AstroError) with this message. This means the imported .svg file's root structure is not actually an <svg> document.

Source

Thrown at packages/astro/src/assets/svg/utils.ts:36

	if (svgOptimizer) {
		try {
			processedContents = await svgOptimizer.optimize(contents);
		} catch (cause) {
			throw new AstroError(
				{
					...AstroErrorData.CannotOptimizeSvg,
					message: AstroErrorData.CannotOptimizeSvg.message(path, svgOptimizer.name),
				},
				{ cause },
			);
		}
	}
	const root = parse(processedContents);
	const svgNode = root.children.find(
		({ name, type }: { name: string; type: number }) => type === ELEMENT_NODE && name === 'svg',
	);
	if (!svgNode) {
		throw new Error('SVG file does not contain an <svg> element');
	}
	const { attributes, children } = svgNode;
	const body = renderSync({ ...root, children });

	// Collect text content of <style> elements for head propagation and CSP hashing
	const styles: string[] = [];
	for (const child of children) {
		if (child.type === ELEMENT_NODE && child.name === 'style') {
			const textContent = child.children
				?.filter((c: { type: number }) => c.type === TEXT_NODE)
				.map((c: { value: string }) => c.value)
				.join('');
			if (textContent) {
				styles.push(textContent);
			}
		}
	}

View on GitHub (pinned to d081033d5f)

Solutions

  1. Open the imported .svg file in a text editor and confirm it has exactly one top-level <svg> element.
  2. If the file is HTML or XML, re-export the asset from your design tool as a real SVG.
  3. If the SVG is a fragment (no <svg> wrapper), wrap its contents in <svg xmlns="http://www.w3.org/2000/svg">…</svg>.
  4. Run the file through an SVG validator / `svgo --pretty` to surface structural problems.

Example fix

// before — file contains: <html><body><svg>…</svg></body></html>
import logo from './logo.svg';

// after — logo.svg is a single root element
// <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 100 100">…</svg>
import logo from './logo.svg';
Defensive patterns

Strategy: validation

Validate before calling

import { readFileSync } from 'node:fs';
function hasSvgRoot(path: string): boolean {
  const txt = readFileSync(path, 'utf8').trim();
  return /^<svg\b/i.test(txt) && /<\/svg>\s*$/i.test(txt);
}

Type guard

function isSvgFile(content: string): boolean {
  return /<svg[\s>][\s\S]*<\/svg>/i.test(content);
}

Prevention

When it happens

Trigger: ESM-importing a file whose extension is .svg but whose content is HTML, XML, plain text, an empty file, or an SVG wrapped in other elements (e.g. a full HTML document or a <!DOCTYPE html>). The check `type === ELEMENT_NODE && name === 'svg'` fails on the top-level children.

Common situations: An asset downloaded/exported as .svg that is actually HTML or an XML preamble without an <svg> root; an SVG fragment that begins with <defs> or <g> at top level; a file that was overwritten by a build step writing non-SVG content; renaming a non-SVG file to .svg.

Related errors


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