sveltejs/kit · error

Cannot prerender a root +server.js that returns a non-HTML r

Error message

Cannot prerender a root +server.js that returns a non-HTML response - static hosts always serve an HTML file for `${config.paths.base || '/'}`

What it means

With full prerendering (e.g. adapter-static), the site root must be an HTML page. If your root +server.js handles GET at the base path and returns non-HTML (like JSON), the prerenderer refuses: static hosts map the root path to an HTML file (index.html), so a non-HTML root response cannot be represented on disk.

Source

Thrown at packages/kit/src/core/postbuild/prerender.js:538

	/**
	 * @param {'pages' | 'dependencies' | 'data'} category
	 * @param {Response} response
	 * @param {string | Uint8Array} body
	 * @param {string} decoded
	 * @param {string} encoded
	 * @param {string | null} referrer
	 * @param {'linked' | 'fetched'} referenceType
	 */
	function save(category, response, body, decoded, encoded, referrer, referenceType) {
		const response_type = Math.floor(response.status / 100);
		const headers = Object.fromEntries(response.headers);

		const type = headers['content-type'];
		const is_html = response_type === REDIRECT || matches_content_type(type, 'text/html');

		if (!is_html && response.status === 200 && decoded.slice(config.paths.base.length + 1) === '') {
			throw new Error(
				`Cannot prerender a root +server.js that returns a non-HTML response - static hosts always serve an HTML file for \`${config.paths.base || '/'}\``
			);
		}

		const file = output_filename(decoded, is_html);
		const dest = `${config.outDir}/output/prerendered/${category}/${file}`;

		if (written.has(file)) return;

		const encoded_route_id = response.headers.get('x-sveltekit-routeid');
		const route_id = encoded_route_id != null ? decode_uri(encoded_route_id) : null;
		if (route_id !== null) prerendered_routes.add(route_id);

		if (response_type === REDIRECT) {
			const location = headers['location'];

			if (location) {
				const resolved = resolve(encoded, location);

View on GitHub (pinned to 03f1687fe6)

Solutions

  1. Move the JSON response to a nested route like `/api/+server.js` so the root serves an HTML page
  2. Add a root `+page.js`/`+page.svelte` that renders HTML at the base path
  3. Set `export const prerender = false` on the root +server.js route (and keep a separate prerendered root page)
  4. Use an adapter that supports server responses (adapter-node) instead of a static host

Example fix

// before — src/routes/+server.js
export const GET = () => json({ hello: 'world' });

// after — src/routes/api/+server.js
export const GET = () => json({ hello: 'world' });
// plus src/routes/+page.svelte rendering HTML at '/'
Defensive patterns

Strategy: validation

Validate before calling

// pre-build check in a script
import { existsSync } from 'node:fs';
if (existsSync('src/routes/+server.js')) {
  console.warn('Root +server.js exists; ensure its GET returns text/html or set prerender = false');
}

Try / catch

try {
  await build();
} catch (e) {
  if (String(e.message).includes('root +server.js that returns a non-HTML')) {
    console.error('Move your root endpoint to /api or add a root +page.svelte');
    process.exit(1);
  }
  throw e;
}

Prevention

When it happens

Trigger: A root-level `src/routes/+server.js` with `export const GET` returning json()/text (content-type other than text/html, status 200) while the whole app or that route is prerendered.

Common situations: adapter-static apps whose root returns an API-style JSON document; converting an SPA into a static build while keeping a JSON root endpoint.

Related errors


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