diff --git a/packages/kit/package.json b/packages/kit/package.json
index 2b2bf749dbbc..989cb5792750 100644
--- a/packages/kit/package.json
+++ b/packages/kit/package.json
@@ -47,7 +47,7 @@
"peerDependencies": {
"@opentelemetry/api": "^1.0.0",
"@sveltejs/vite-plugin-svelte": "^7.0.0",
- "svelte": "^5.48.0",
+ "svelte": "^5.56.4",
"typescript": "^6.0.0",
"vite": "^8.0.12"
},
diff --git a/packages/kit/src/core/sync/sync.js b/packages/kit/src/core/sync/sync.js
index a53b6832c370..0adf76e3c5ac 100644
--- a/packages/kit/src/core/sync/sync.js
+++ b/packages/kit/src/core/sync/sync.js
@@ -2,7 +2,6 @@ import path from 'node:path';
import process from 'node:process';
import create_manifest_data from './create_manifest_data/index.js';
import { write_client_manifest } from './write_client_manifest.js';
-import { write_root } from './write_root.js';
import { write_tsconfig } from './write_tsconfig.js';
import { write_types, write_all_types } from './write_types/index.js';
import { write_ambient } from './write_ambient.js';
@@ -37,7 +36,6 @@ export function create(config, root) {
write_client_manifest(config.kit, manifest_data, `${output}/client`);
write_server(config, output, root);
- write_root(manifest_data, output);
write_all_types(config, manifest_data, root);
write_non_ambient(config.kit, manifest_data);
diff --git a/packages/kit/src/core/sync/write_client_manifest.js b/packages/kit/src/core/sync/write_client_manifest.js
index 696f72c72306..42b3e940282d 100644
--- a/packages/kit/src/core/sync/write_client_manifest.js
+++ b/packages/kit/src/core/sync/write_client_manifest.js
@@ -181,8 +181,6 @@ export function write_client_manifest(kit, manifest_data, output, metadata) {
export const decode = (type, value) => decoders[type](value);
- export { default as root } from '../root.js';
-
export const get_error_template = () => import('../shared/error-template.js').then(m => m.default);
`
);
diff --git a/packages/kit/src/core/sync/write_root.js b/packages/kit/src/core/sync/write_root.js
deleted file mode 100644
index 082b4c8b740d..000000000000
--- a/packages/kit/src/core/sync/write_root.js
+++ /dev/null
@@ -1,90 +0,0 @@
-import { dedent, write_if_changed } from './utils.js';
-
-/**
- * @param {import('types').ManifestData} manifest_data
- * @param {string} output
- */
-export function write_root(manifest_data, output) {
- // TODO remove default layout altogether
-
- const max_depth = Math.max(
- ...manifest_data.routes.map((route) =>
- route.page ? route.page.layouts.filter(Boolean).length + 1 : 0
- ),
- 1
- );
-
- const levels = [];
- for (let i = 0; i <= max_depth; i += 1) {
- levels.push(i);
- }
-
- write_if_changed(
- `${output}/root.svelte`,
- dedent`
-
-
-
-
- {#snippet pyramid(depth)}
- {@const Pyramid = constructors[depth]}
- {#snippet failed(error)}
- {@const ErrorPage = errors[depth]}
-
- {/snippet}
- (resetters[depth] = reset) : undefined}>
- {#if constructors[depth + 1]}
- {@const d = data[depth]}
-
-
- {@render pyramid(depth + 1)}
-
- {:else}
- {@const d = data[depth]}
-
-
- {/if}
-
- {/snippet}
-
- {@render pyramid(0)}
-
- {#if mounted}
-
- {#if navigated}
- {title}
- {/if}
-
- {/if}
- `
- );
-
- write_if_changed(
- `${output}/root.js`,
- dedent`
- import { asClassComponent } from 'svelte/legacy';
- import Root from './root.svelte';
- export default asClassComponent(Root);
- `
- );
-}
diff --git a/packages/kit/src/core/sync/write_server.js b/packages/kit/src/core/sync/write_server.js
index 5f0ccf0519c2..2609925a443d 100644
--- a/packages/kit/src/core/sync/write_server.js
+++ b/packages/kit/src/core/sync/write_server.js
@@ -24,7 +24,6 @@ const server_template = ({
has_service_worker,
template
}) => `
-import root from '../root.js';
import { set_building, set_prerendering } from '$app/env/internal';
import { set_assets } from '$app/paths/internal/server';
import { set_manifest, set_read_implementation } from '__sveltekit/server';
@@ -40,7 +39,6 @@ export const options = {
hooks: null, // added lazily, via \`get_hooks\`
link_header_preload: ${s(config.kit.output.linkHeaderPreload)},
paths_origin: ${s(config.kit.paths.origin)},
- root,
service_worker: ${has_service_worker},
service_worker_options: ${config.kit.serviceWorker.register ? s(config.kit.serviceWorker.options) : 'null'},
templates: {
diff --git a/packages/kit/src/runtime/client/client.js b/packages/kit/src/runtime/client/client.js
index dcc3c53aee61..cd55a9c83207 100644
--- a/packages/kit/src/runtime/client/client.js
+++ b/packages/kit/src/runtime/client/client.js
@@ -1,5 +1,6 @@
/** @import { RemoteFunctionDataNode, ServerNodesResponse, ServerRedirectNode } from 'types' */
/** @import { NavigationIntent } from './types.js' */
+/** @import { RenderNode } from '../types.js' */
/** @import { CacheEntry } from './remote-functions/cache.svelte.js' */
/** @import { Query } from './remote-functions/query/instance.svelte.js' */
/** @import { LiveQuery } from './remote-functions/query-live/instance.svelte.js' */
@@ -33,7 +34,6 @@ import {
} from './constants.js';
import { validate_page_exports } from '../../utils/exports.js';
import { noop } from '../../utils/functions.js';
-import { compact } from '../../utils/array.js';
import {
INVALIDATED_PARAM,
TRAILING_SLASH_PARAM,
@@ -46,6 +46,10 @@ import { page, update, navigating, updated } from './state.svelte.js';
import { add_data_suffix, add_resolution_suffix } from '../pathname.js';
import { noop_span } from '../telemetry/noop.js';
import { read_ndjson } from './ndjson.js';
+import RootModern from '../components/root.svelte';
+import { asClassComponent } from 'svelte/legacy';
+
+const Root = asClassComponent(RootModern);
export { load_css };
const ICON_REL_ATTRIBUTES = new Set(['icon', 'shortcut icon', 'apple-touch-icon']);
@@ -87,6 +91,11 @@ const scroll_positions = storage.get(SCROLL_KEY) ?? {};
*/
const snapshots = storage.get(SNAPSHOT_KEY) ?? {};
+/**
+ * @deprecated this is a temporary measure to avoid a regression, replace with nested `RenderNode` classes
+ */
+let current_tree = /** @type {RenderNode} */ ({});
+
if (DEV && BROWSER) {
let warned = false;
@@ -479,6 +488,7 @@ async function _invalidate(reset_page_state = true) {
navigation_result.props.page.state = prev_state;
}
update(navigation_result.props.page);
+ current_tree = navigation_result.props.tree;
current = { ...navigation_result.state, nav: current.nav };
reset_invalidation();
root.$set(navigation_result.props);
@@ -628,17 +638,14 @@ async function _preload_data(intent) {
load_cache = {
id: intent.id,
token: preload,
- promise: load_route({ ...intent, preload }).then((result) => {
+ promise: load_route({ ...intent, preload }).finally(() => {
preload_tokens.delete(preload);
- if (result.type === 'loaded' && result.state.error) {
- // Don't cache errors, because they might be transient
- discard_load_cache();
- }
- return result;
}),
fork: null
};
+ load_cache.promise.catch(discard_load_cache);
+
if (__SVELTEKIT_FORK_PRELOADS__ && svelte.fork) {
const lc = load_cache;
@@ -650,6 +657,7 @@ async function _preload_data(intent) {
return svelte.fork(() => {
root.$set(result.props);
update(result.props.page);
+ current_tree = result.props.tree;
});
} catch {
// if it errors, it's because the experimental flag isn't enabled in Svelte
@@ -708,9 +716,10 @@ async function initialize(result, target, hydrate) {
}
update(/** @type {import('@sveltejs/kit').Page} */ (result.props.page));
+ current_tree = result.props.tree;
// TODO: use mount()
- root = new app.root({
+ root = new Root({
target,
props: { ...result.props, components, resetters },
hydrate,
@@ -801,33 +810,11 @@ async function get_navigation_result_from_branch({
route
},
props: {
- constructors: compact(branch).map((branch_node) => branch_node.node.component),
- page
+ page,
+ tree: /** @type {RenderNode} */ ({})
}
};
- if (errors) {
- let last_idx = -1;
- result.props.errors = await Promise.all(
- // eslint-disable-next-line @typescript-eslint/await-thenable
- branch
- .map((b, i) => {
- if (i === 0) return undefined; // root layout wraps root error component, not the other way around
- if (!b) return null;
-
- i--;
- // Find the closest error component up to the previous branch
- while (i > last_idx + 1 && !errors[i]) i -= 1;
- last_idx = i;
- return errors[i]?.()
- .then((e) => e.component)
- .catch(() => undefined);
- })
- // filter out indexes where there was no branch, but keep indexes where there was a branch but no error component
- .filter((e) => e !== null)
- );
- }
-
if (error) {
result.props.error = error;
}
@@ -839,23 +826,44 @@ async function get_navigation_result_from_branch({
let data = {};
let data_changed = !page;
- let p = 0;
+ let current_node = result.props.tree;
- for (let i = 0; i < Math.max(branch.length, current.branch.length); i += 1) {
+ /** @type {RenderNode | undefined} */
+ let previous_node = current_tree;
+
+ for (let i = 0; i < branch.length; i += 1) {
const node = branch[i];
const prev = current.branch[i];
- if (node?.data !== prev?.data) data_changed = true;
if (!node) continue;
- data = { ...data, ...node.data };
+ const error_loader = errors?.slice(0, i + 1).findLast((x) => x) ?? default_error_loader;
+
+ current_node.error = (await error_loader()).component;
+ current_node.component = node.node.component;
- // Only set props if the node actually updated. This prevents needless rerenders.
- if (data_changed) {
- result.props[`data_${p}`] = data;
+ if (
+ // if an ancestor node in this path changed, `data_changed` is already true and the
+ // accumulated `data` differs from the previous render, so we must re-merge
+ data_changed ||
+ !previous_node ||
+ node?.data !== prev?.data
+ ) {
+ current_node.data = { ...data, ...node.data };
+ data_changed = true;
+ } else {
+ // use existing object — prevents effects re-running unnecessarily
+ current_node.data = previous_node.data;
}
- p += 1;
+ data = current_node.data;
+
+ if (i < branch.length - 1) {
+ current_node.child = /** @type {import('../types.js').RenderNode} */ ({});
+ current_node = current_node.child;
+
+ previous_node = previous_node?.child;
+ }
}
const page_changed =
@@ -876,8 +884,7 @@ async function get_navigation_result_from_branch({
status: status ?? error?.status ?? 200,
url: new URL(url),
form: form ?? null,
- // The whole page store is updated, but this way the object reference stays the same
- data: data_changed ? data : page.data
+ data
};
}
@@ -1155,32 +1162,6 @@ function diff_search_params(old_url, new_url) {
return changed;
}
-/**
- * @param {Omit & { error: App.Error }} opts
- * @returns {import('./types.js').NavigationFinished}
- */
-function preload_error({ error, url, route, params }) {
- return {
- type: 'loaded',
- state: {
- error,
- url,
- route,
- params,
- branch: []
- },
- props: {
- page: {
- // we skipped loading the error page, so we have to use the current page
- // store, but update the status received while preloading
- ...page,
- status: error.status
- },
- constructors: []
- }
- };
-}
-
/**
* @overload
* @param {import('./types.js').NavigationIntent} intent
@@ -1251,7 +1232,7 @@ async function load_route({ id, invalidating, url, params, route, preload }) {
const handled_error = await handle_error(error, { url, params, route: { id } });
if (preload && preload_tokens.has(preload)) {
- return preload_error({ error: handled_error, url, params, route });
+ throw handled_error;
}
return load_root_error_page({
@@ -1341,13 +1322,7 @@ async function load_route({ id, invalidating, url, params, route, preload }) {
}
if (preload && preload_tokens.has(preload)) {
- const error = await handle_error(err, { params, url, route: { id: route.id } });
- return preload_error({
- error,
- url,
- params,
- route
- });
+ throw await handle_error(err, { params, url, route: { id: route.id } });
}
/** @type {App.Error} */
@@ -1972,6 +1947,7 @@ async function navigate({
} else {
rendering_error = null; // TODO this can break with forks, rethink for SvelteKit 3 where we can assume Svelte 5
root.$set(navigation_result.props);
+ current_tree = navigation_result.props.tree;
// Reset any boundaries that failed on a previous navigation now that the
// new props are applied, otherwise the stale `+error.svelte` stays
// mounted above the new route's content. See sveltejs/kit#15694.
@@ -2175,15 +2151,13 @@ function setup_preload() {
if (!intent) return;
if (DEV) {
- void _preload_data(intent).then((result) => {
- if (result.type === 'loaded' && result.state.error) {
- console.warn(
- `Preloading data for ${intent.url.pathname} failed with the following error: ${result.state.error.message}\n` +
- 'If this error is transient, you can ignore it. Otherwise, consider disabling preloading for this route. ' +
- 'This route was preloaded due to a data-sveltekit-preload-data attribute. ' +
- 'See https://svelte.dev/docs/kit/link-options for more info'
- );
- }
+ void _preload_data(intent).catch((error) => {
+ console.warn(
+ `Preloading data for ${intent.url.pathname} failed with the following error: ${error.message}\n` +
+ 'If this error is transient, you can ignore it. Otherwise, consider disabling preloading for this route. ' +
+ 'This route was preloaded due to a data-sveltekit-preload-data attribute. ' +
+ 'See https://svelte.dev/docs/kit/link-options for more info'
+ );
});
} else {
void _preload_data(intent);
@@ -2471,7 +2445,22 @@ export async function preloadData(href) {
throw new Error(`Attempted to preload a URL that does not belong to this app: ${url}`);
}
- const result = await _preload_data(intent);
+ /** @type {Awaited>} */
+ let result;
+
+ try {
+ result = await _preload_data(intent);
+ } catch (error) {
+ // `load_route` throws the handled error (an `App.Error` with a `status`)
+ // when a preload fails, so surface it in the documented `{ type: 'error' }` shape
+ const handled = /** @type {App.Error & { status?: number }} */ (error);
+ return {
+ type: 'error',
+ status: handled?.status ?? 500,
+ error: handled
+ };
+ }
+
if (result.type === 'redirect') {
return {
type: result.type,
@@ -2482,14 +2471,6 @@ export async function preloadData(href) {
const { status, data } = result.props.page ?? page;
- if (result.type === 'loaded' && result.state.error) {
- return {
- type: 'error',
- status,
- error: result.state.error
- };
- }
-
return { type: result.type, status, data };
}
@@ -2680,6 +2661,7 @@ export async function set_nearest_error_page(error) {
current = { ...navigation_result.state, nav: current.nav };
root.$set(navigation_result.props);
+ current_tree = navigation_result.props.tree;
update(navigation_result.props.page);
void svelte.tick().then(() => reset_focus(current.url));
diff --git a/packages/kit/src/runtime/client/types.d.ts b/packages/kit/src/runtime/client/types.d.ts
index 9075d7b7054a..33595faab083 100644
--- a/packages/kit/src/runtime/client/types.d.ts
+++ b/packages/kit/src/runtime/client/types.d.ts
@@ -10,6 +10,7 @@ import {
Uses
} from 'types';
import { Page, ParamMatcher } from '@sveltejs/kit';
+import { RenderNode } from '../types.js';
export interface SvelteKitApp {
/**
@@ -57,8 +58,6 @@ export interface SvelteKitApp {
*/
hash: boolean;
- root: typeof SvelteComponent;
-
/**
* Lazily loads the contents of src/error.html, used as a last-resort
* error page when the root layout's load function throws during client-side rendering.
@@ -91,13 +90,10 @@ export type NavigationFinished = {
type: 'loaded';
state: NavigationState;
props: {
- constructors: Array;
- errors?: Array;
- components?: SvelteComponent[];
page: Page;
form?: Record | null;
error?: App.Error;
- [key: `data_${number}`]: Record;
+ tree: RenderNode;
};
};
diff --git a/packages/kit/src/runtime/components/root.svelte b/packages/kit/src/runtime/components/root.svelte
new file mode 100644
index 000000000000..4b230208ba66
--- /dev/null
+++ b/packages/kit/src/runtime/components/root.svelte
@@ -0,0 +1,66 @@
+
+
+{#snippet node(n: RenderNode, depth: number)}
+ {const Component = $derived(n.component)}
+ {const Error = $derived(n.error)}
+ {const data = $derived(n.data)}
+
+ (resetters[depth] = reset)}>
+ {#if n.child}
+
+
+ {@render node(n.child, depth + 1)}
+
+ {:else}
+
+
+ {/if}
+
+ {#snippet failed(error)}
+
+ {/snippet}
+
+{/snippet}
+
+{@render node(tree, 0)}
+
+{#if mounted}
+
+ {#if navigated}
+ {title}
+ {/if}
+
+{/if}
diff --git a/packages/kit/src/runtime/server/page/index.js b/packages/kit/src/runtime/server/page/index.js
index 3e6d5a8c408a..a091d9c4a169 100644
--- a/packages/kit/src/runtime/server/page/index.js
+++ b/packages/kit/src/runtime/server/page/index.js
@@ -1,5 +1,6 @@
+/** @import { Component } from 'svelte' */
/** @import { ActionResult, RequestEvent, SSRManifest } from '@sveltejs/kit' */
-/** @import { PageNodeIndexes, RequestState, RequiredResolveOptions, ServerDataNode, SSRComponent, SSRNode, SSROptions, SSRState } from 'types' */
+/** @import { PageNodeIndexes, RequestState, RequiredResolveOptions, ServerDataNode, SSRNode, SSROptions, SSRState } from 'types' */
import { text } from '@sveltejs/kit';
import { HttpError, Redirect } from '@sveltejs/kit/internal';
import { compact } from '../../../utils/array.js';
@@ -398,7 +399,7 @@ export async function render_page(
* @param {SSRManifest} manifest
*/
async function load_error_components(ssr, branch, page, manifest) {
- /** @type {Array | undefined} */
+ /** @type {Array | undefined} */
let error_components;
if (ssr) {
diff --git a/packages/kit/src/runtime/server/page/render.js b/packages/kit/src/runtime/server/page/render.js
index 583d4584324f..d945f01b7489 100644
--- a/packages/kit/src/runtime/server/page/render.js
+++ b/packages/kit/src/runtime/server/page/render.js
@@ -1,5 +1,5 @@
+/** @import { RenderNode } from '../../types.js' */
import * as devalue from 'devalue';
-import { readable, writable } from 'svelte/store';
import { DEV } from 'esm-env';
import { isRedirect, text } from '@sveltejs/kit';
import * as paths from '$app/paths/internal/server';
@@ -22,14 +22,11 @@ import {
} from '../utils.js';
import * as env from '__sveltekit/env';
import { collect_remote_data } from '../remote.js';
+import Root from '../../components/root.svelte';
+import { render } from 'svelte/server';
// TODO rename this function/module
-const updated = {
- ...readable(false),
- check: () => false
-};
-
/**
* Creates the HTML response.
* @param {{
@@ -46,7 +43,7 @@ const updated = {
* resolve_opts: import('types').RequiredResolveOptions;
* action_result?: import('@sveltejs/kit').ActionResult;
* data_serializer: import('./types.js').ServerDataSerializer;
- * error_components?: Array
+ * error_components?: Array
* }} opts
*/
export async function render_response({
@@ -91,7 +88,8 @@ export async function render_response({
// TODO if we add a client entry point one day, we will need to include inline_styles with the entry, otherwise stylesheets will be linked even if they are below inlineStyleThreshold
const inline_styles = new Map();
- /** @type {Awaited>} */
+ // TODO `svelte/server` should expose `RenderOutput`
+ /** @type {{ head: string, body: string, hashes: { script: string[] } }} */
let rendered;
const form_value =
@@ -142,49 +140,45 @@ export async function render_response({
if (page_config.ssr) {
/** @type {Record} */
const props = {
- stores: {
- page: writable(null),
- navigating: writable(null),
- updated
- },
- constructors: await Promise.all(
- branch.map(({ node }) => {
- if (!node.component) {
- // Can only be the leaf, layouts have a fallback component generated
- throw new Error(`Missing +page.svelte component for route ${event.route.id}`);
- }
- return node.component();
- })
- ),
- form: form_value
- };
-
- if (error_components) {
- if (error) {
- props.error = error;
+ components: [],
+ resetters: [],
+ form: form_value,
+ tree: /** @type {RenderNode} */ ({}),
+ error,
+ page: {
+ error,
+ params: /** @type {Record} */ (event.params),
+ route: event.route,
+ status,
+ url: event.url,
+ data: {},
+ form: form_value,
+ state: {}
}
- props.errors = error_components;
- }
+ };
- let data = {};
+ let current_node = props.tree;
+ let data = props.page.data;
- // props_n (instead of props[n]) makes it easy to avoid
- // unnecessary updates for layout components
for (let i = 0; i < branch.length; i += 1) {
- data = { ...data, ...branch[i].data };
- props[`data_${i}`] = data;
+ const node = branch[i];
+
+ data = { ...data, ...node.data };
+
+ // TODO this is undefined sometimes... where does the default error component come from?
+ const error = error_components?.slice(0, i + 1).findLast((x) => x);
+
+ current_node.error = error;
+ current_node.component = await node.node.component?.();
+ current_node.data = data;
+
+ if (i < branch.length - 1) {
+ current_node.child = /** @type {import('../../types.js').RenderNode} */ ({});
+ current_node = current_node.child;
+ }
}
- props.page = {
- error,
- params: /** @type {Record} */ (event.params),
- route: event.route,
- status,
- url: event.url,
- data,
- form: form_value,
- state: {}
- };
+ props.page.data = data;
const render_state = { ...event_state, is_in_render: true };
@@ -250,17 +244,15 @@ export async function render_response({
// We have to invoke .then eagerly here in order to kick off rendering: it's only starting on access,
// and `await maybe_promise` would eagerly access the .then property but call its function only after a tick, which is too late
// for the paths.reset() below and for any eager getRequestEvent() calls during rendering without AsyncLocalStorage available.
- // TODO use render from 'svelte/server' here
- const rendered = options.root.render(props, render_opts).then((r) => r);
+ const rendered = render(Root, { ...render_opts, props });
- // @ts-expect-error the legacy `render` API only returns html still, but the new API uses body
- const { head, html: body, css, hashes } = await rendered;
+ const { head, body, hashes } = await rendered;
if (hashes) {
csp.add_script_hashes(hashes.script);
}
- return { head, body, css, hashes };
+ return { head, body, hashes };
});
} finally {
if (DEV) {
@@ -268,7 +260,7 @@ export async function render_response({
}
}
} else {
- rendered = { head: '', body: '', css: { code: '', map: null }, hashes: { script: [] } };
+ rendered = { head: '', body: '', hashes: { script: [] } };
}
for (const { node } of branch) {
diff --git a/packages/kit/src/runtime/types.d.ts b/packages/kit/src/runtime/types.d.ts
new file mode 100644
index 000000000000..71afa916565e
--- /dev/null
+++ b/packages/kit/src/runtime/types.d.ts
@@ -0,0 +1,8 @@
+import { Component } from 'svelte';
+
+export interface RenderNode {
+ component: Component;
+ error: Component;
+ data: Record;
+ child?: RenderNode;
+}
diff --git a/packages/kit/src/types/internal.d.ts b/packages/kit/src/types/internal.d.ts
index 096593aede22..b71a05673375 100644
--- a/packages/kit/src/types/internal.d.ts
+++ b/packages/kit/src/types/internal.d.ts
@@ -1,4 +1,4 @@
-import { SvelteComponent } from 'svelte';
+import { Component } from 'svelte';
import {
Config,
ServerLoad,
@@ -113,7 +113,7 @@ export interface BuildData {
}
export interface CSRPageNode {
- component: typeof SvelteComponent;
+ component: Component;
universal: {
load?: Load;
trailingSlash?: TrailingSlash;
@@ -417,27 +417,7 @@ export interface ServerMetadata {
remotes: Map>;
}
-// TODO get rid of this in favor us using just import('svelte').Component
-export interface SSRComponent {
- default: {
- render(
- props: Record,
- opts: { context: Map; csp?: { nonce?: string; hash?: boolean } }
- ): Promise<{
- body: string;
- head: string;
- css: {
- code: string;
- map: any; // TODO
- };
- hashes: {
- script: Array<`sha256-${string}`>;
- };
- }>;
- };
-}
-
-export type SSRComponentLoader = () => Promise;
+export type SSRComponentLoader = () => Promise;
export interface UniversalNode {
/** Is `null` in case static analysis succeeds but the node is ssr=false */
@@ -501,7 +481,6 @@ export interface SSROptions {
hooks: ServerHooks;
link_header_preload: ValidatedConfig['kit']['output']['linkHeaderPreload'];
paths_origin: string | undefined;
- root: SSRComponent['default'];
service_worker: boolean;
service_worker_options: RegistrationOptions;
templates: {
diff --git a/packages/kit/test/apps/basics/src/routes/state/data/state-update/+layout.svelte b/packages/kit/test/apps/basics/src/routes/state/data/state-update/+layout.svelte
index 5e83135ec2a4..7c4d3062880c 100644
--- a/packages/kit/test/apps/basics/src/routes/state/data/state-update/+layout.svelte
+++ b/packages/kit/test/apps/basics/src/routes/state/data/state-update/+layout.svelte
@@ -15,7 +15,7 @@
page.data was updated {count} time(s)
-a
-b
+a
+b
{@render children()}
diff --git a/packages/kit/types/index.d.ts b/packages/kit/types/index.d.ts
index af5f78f382f3..af6ab67afee7 100644
--- a/packages/kit/types/index.d.ts
+++ b/packages/kit/types/index.d.ts
@@ -6,6 +6,7 @@ declare module '@sveltejs/kit' {
import type { StandardSchemaV1 } from '@standard-schema/spec';
import type { Plugin } from 'vite';
import type { RouteId as AppRouteId, LayoutParams as AppLayoutParams, ResolvedPathname } from '$app/types';
+ import type { Component } from 'svelte';
// @ts-ignore this is an optional peer dependency so could be missing. Written like this so dts-buddy preserves the ts-ignore
type Span = import('@opentelemetry/api').Span;
@@ -2836,27 +2837,7 @@ declare module '@sveltejs/kit' {
} | null;
}
- // TODO get rid of this in favor us using just import('svelte').Component
- interface SSRComponent {
- default: {
- render(
- props: Record,
- opts: { context: Map; csp?: { nonce?: string; hash?: boolean } }
- ): Promise<{
- body: string;
- head: string;
- css: {
- code: string;
- map: any; // TODO
- };
- hashes: {
- script: Array<`sha256-${string}`>;
- };
- }>;
- };
- }
-
- type SSRComponentLoader = () => Promise;
+ type SSRComponentLoader = () => Promise;
interface UniversalNode {
/** Is `null` in case static analysis succeeds but the node is ssr=false */