sveltejs/kit · error · Error

${relative} is missing ${tag}

Error message

${relative} is missing ${tag}

What it means

The app template must contain the placeholders %sveltekit.head% and %sveltekit.body% so SvelteKit can inject the page head and rendered body. load_template reads the template and throws if either placeholder is missing, because rendering would silently produce broken pages otherwise.

Source

Thrown at packages/kit/src/core/config/index.js:89

 * required content.
 * @param {string} cwd
 * @param {ValidatedConfig} config
 */
export function load_template(cwd, config) {
	const { files } = config;

	const relative = path.relative(cwd, files.appTemplate);

	if (!fs.existsSync(files.appTemplate)) {
		throw new Error(`${relative} does not exist`);
	}

	const contents = fs.readFileSync(files.appTemplate, 'utf8');

	const expected_tags = ['%sveltekit.head%', '%sveltekit.body%'];
	expected_tags.forEach((tag) => {
		if (contents.indexOf(tag) === -1) {
			throw new Error(`${relative} is missing ${tag}`);
		}
	});

	return contents;
}

/**
 * Loads the error page (src/error.html by default) if it exists.
 * Falls back to a generic error page content.
 * @param {ValidatedConfig} config
 */
export function load_error_page(config) {
	let { errorTemplate } = config.files;

	// Don't do this inside resolving the config, because that would mean
	// adding/removing error.html isn't detected and would require a restart.
	if (!fs.existsSync(config.files.errorTemplate)) {
		errorTemplate = url.fileURLToPath(new URL('./default-error.html', import.meta.url));

View on GitHub (pinned to 03f1687fe6)

Solutions

  1. Add the missing placeholder(s) into the template: `%sveltekit.head%` inside <head> and `%sveltekit.body%` inside <body> (wrapped in a div or body content).
  2. Restore the default src/app.html from the SvelteKit template (`npx sv create` output or the docs) if unsure of the shape.
  3. Check for preprocessing/formatting tools that might strip the placeholder comments/tokens and exclude app.html from them.

Example fix

<!-- before: src/app.html missing placeholders -->
<html><head></head><body><div id="app"></div></body></html>
<!-- after -->
<html><head>%sveltekit.head%</head><body data-sveltekit-preload-data="hover"><div style="display: contents">%sveltekit.body%</div></body></html>
Defensive patterns

Strategy: validation

Validate before calling

import fs from 'node:fs';
const contents = fs.readFileSync('src/app.html', 'utf8');
for (const tag of ['%sveltekit.head%', '%sveltekit.body%']) {
  if (!contents.includes(tag)) throw new Error(`src/app.html is missing ${tag}`);
}

Try / catch

try {
  await viteBuild();
} catch (e) {
  if (e.message.includes('is missing %sveltekit.')) {
    console.error('Add the missing %sveltekit.head% / %sveltekit.body% placeholder to app.html');
  }
  throw e;
}

Prevention

When it happens

Trigger: write_server -> load_template reads files.appTemplate successfully, but its contents do not include `%sveltekit.head%` or `%sveltekit.body%` (indexOf returns -1 for either tag).

Common situations: Users copy a plain HTML file as app.html without the placeholders; manually stripping tags when 'cleaning up' the file; using a template written for an older framework version; tooling that minifies/rewrites the file and removes the tokens.

Related errors


AI-assisted analysis of sveltejs/kit@03f1687fe6 (2026-09-02). Data as JSON: /api/errors/4a030f9ec67c941f. Report an issue: GitHub.