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

  1. Align versions: reinstall dependencies so astro and @astrojs/compiler match the same release
  2. Remove any code that manually injects render instructions into the stream
  3. Disable plugins that transform compiled .astro output and retest
  4. 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

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


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