withastro/astro · error · Error
Unexpected template-exit instruction without a matching temp
Error message
Unexpected template-exit instruction without a matching template-enter. This may indicate that the compiler emitted unbalanced template boundaries, or that a component manually injected a template-exit render instruction.
What it means
The renderer tracks paired template-enter/template-exit render instructions (used for transition scopes) with a depth counter in result._metadata.templateDepth. A template-exit arriving when the depth is 0 means the boundaries are unbalanced: either the compiler emitted mismatched instructions, or user code manually injected a template-exit instruction into the render stream. This is an internal invariant, not a normal authoring error.
Source
Thrown at packages/astro/src/runtime/server/render/common.ts:134
// don't mark it as deduplicated. Template content is inert, scripts
// inside don't execute, so the script must also appear outside the
// template for non-template instances to work
if (result._metadata.templateDepth > 0) {
return content;
}
if (result._metadata.renderedScripts.has(id)) {
return '';
}
result._metadata.renderedScripts.add(id);
return content;
}
case 'template-enter': {
result._metadata.templateDepth++;
return '';
}
case 'template-exit': {
if (result._metadata.templateDepth <= 0) {
throw new Error(
'Unexpected template-exit instruction without a matching template-enter. ' +
'This may indicate that the compiler emitted unbalanced template boundaries, ' +
'or that a component manually injected a template-exit render instruction.',
);
}
result._metadata.templateDepth--;
return '';
}
default: {
throw new Error(`Unknown chunk type: ${(chunk as any).type}`);
}
}
} else if (chunk instanceof Response) {
return '';
} else if (isSlotString(chunk as string)) {
let out = '';
const c = chunk as SlotString;
// Position-independent instructions (head, hydration, etc.) are emitted first.View on GitHub (pinned to 52e6c34790)
Solutions
- Align versions: reinstall dependencies so astro and @astrojs/compiler match the same release
- Remove any code that manually injects render instructions into the stream
- Disable plugins that transform compiled .astro output and retest
- If it persists on clean versions, open an Astro issue with a minimal reproduction
Defensive patterns
Strategy: try-catch
Try / catch
try {
const stream = await renderPage(request);
} catch (err) {
if (String(err).includes('template-exit instruction')) {
// compiler/runtime mismatch — capture versions and rebuild rather than retry
console.error('astro/@astrojs/compiler versions:', process.env.ASTRO_VERSION);
throw err;
}
throw err;
} Prevention
- Keep astro and @astrojs/compiler versions locked to the same release in your lockfile
- Never inject internal render instructions (createRenderInstruction) into rendered output yourself
- Re-run a clean install after major Astro upgrades
When it happens
Trigger: Version skew between astro and @astrojs/compiler producing unbalanced instructions; calling internal render APIs (createRenderInstruction) to emit template-exit manually; plugins that transform or reorder compiled .astro output and drop a template-enter.
Common situations: Partial upgrades where the lockfile kept an old @astrojs/compiler; forks/patches of the compiler; exotic integrations that re-emit render chunks. Ordinary projects essentially never see this.
Related errors
- NoMatchingImport
- The view transitions client API was called during a server s
- ⚠️ Astro expected an SVG for "${transform.src}" but the sou
- ⚠️ Astro could not optimize image "${transform.src}". Sharp
- [content] Could not read the chunked data store at ${fileURL
AI-assisted analysis of withastro/astro@52e6c34790 (2026-08-18).
Data as JSON: /api/errors/1b16ae620ac7bc77.
Report an issue: GitHub.