@sveltejs/kit 2.16.0 → 2.17.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (36) hide show
  1. package/package.json +5 -3
  2. package/src/core/adapt/builder.js +2 -0
  3. package/src/core/config/index.js +16 -1
  4. package/src/core/config/options.js +2 -1
  5. package/src/core/generate_manifest/index.js +14 -5
  6. package/src/core/postbuild/analyse.js +2 -2
  7. package/src/core/sync/write_client_manifest.js +33 -17
  8. package/src/core/sync/write_server.js +2 -1
  9. package/src/exports/public.d.ts +29 -6
  10. package/src/exports/vite/build/build_server.js +5 -0
  11. package/src/exports/vite/dev/index.js +29 -1
  12. package/src/exports/vite/index.js +51 -5
  13. package/src/runtime/app/state/index.js +11 -0
  14. package/src/runtime/client/client.js +80 -46
  15. package/src/runtime/client/parse.js +20 -0
  16. package/src/runtime/client/types.d.ts +16 -1
  17. package/src/runtime/client/utils.js +6 -0
  18. package/src/runtime/pathname.js +54 -0
  19. package/src/runtime/server/cookie.js +2 -1
  20. package/src/runtime/server/endpoint.js +0 -5
  21. package/src/runtime/server/fetch.js +12 -0
  22. package/src/runtime/server/page/index.js +1 -1
  23. package/src/runtime/server/page/render.js +25 -3
  24. package/src/runtime/server/page/server_routing.js +110 -0
  25. package/src/runtime/server/respond.js +45 -26
  26. package/src/runtime/server/validate-headers.js +64 -0
  27. package/src/runtime/utils.js +21 -0
  28. package/src/types/ambient-private.d.ts +1 -0
  29. package/src/types/global-private.d.ts +2 -0
  30. package/src/types/internal.d.ts +47 -9
  31. package/src/utils/filesystem.js +4 -3
  32. package/src/utils/routing.js +8 -0
  33. package/src/utils/url.js +0 -23
  34. package/src/version.js +1 -1
  35. package/types/index.d.ts +72 -13
  36. package/types/index.d.ts.map +2 -1
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@sveltejs/kit",
3
- "version": "2.16.0",
3
+ "version": "2.17.0",
4
4
  "description": "SvelteKit is the fastest way to build Svelte apps",
5
5
  "keywords": [
6
6
  "framework",
@@ -41,8 +41,8 @@
41
41
  "svelte": "^5.2.9",
42
42
  "svelte-preprocess": "^6.0.0",
43
43
  "typescript": "^5.3.3",
44
- "vite": "^6.0.1",
45
- "vitest": "^2.1.6"
44
+ "vite": "^6.0.11",
45
+ "vitest": "^3.0.1"
46
46
  },
47
47
  "peerDependencies": {
48
48
  "@sveltejs/vite-plugin-svelte": "^3.0.0 || ^4.0.0-next.1 || ^5.0.0",
@@ -96,6 +96,8 @@
96
96
  "test:integration": "pnpm -r --workspace-concurrency 1 --filter=\"./test/**\" test",
97
97
  "test:cross-platform:dev": "pnpm -r --workspace-concurrency 1 --filter=\"./test/**\" test:cross-platform:dev",
98
98
  "test:cross-platform:build": "pnpm test:unit && pnpm -r --workspace-concurrency 1 --filter=\"./test/**\" test:cross-platform:build",
99
+ "test:server-side-route-resolution:dev": "pnpm -r --workspace-concurrency 1 --filter=\"./test/**\" test:server-side-route-resolution:dev",
100
+ "test:server-side-route-resolution:build": "pnpm test:unit && pnpm -r --workspace-concurrency 1 --filter=\"./test/**\" test:server-side-route-resolution:build",
99
101
  "test:unit": "vitest --config kit.vitest.config.js run",
100
102
  "generate:version": "node scripts/generate-version.js",
101
103
  "generate:types": "node scripts/generate-dts.js"
@@ -138,6 +138,7 @@ export function create_builder({
138
138
  generateManifest: ({ relativePath }) =>
139
139
  generate_manifest({
140
140
  build_data,
141
+ prerendered: [],
141
142
  relative_path: relativePath,
142
143
  routes: Array.from(filtered)
143
144
  })
@@ -185,6 +186,7 @@ export function create_builder({
185
186
  generateManifest({ relativePath, routes: subset }) {
186
187
  return generate_manifest({
187
188
  build_data,
189
+ prerendered: prerendered.paths,
188
190
  relative_path: relativePath,
189
191
  routes: subset
190
192
  ? subset.map((route) => /** @type {import('types').RouteData} */ (lookup.get(route)))
@@ -115,5 +115,20 @@ export function validate_config(config) {
115
115
  );
116
116
  }
117
117
 
118
- return options(config, 'config');
118
+ const validated = options(config, 'config');
119
+
120
+ if (validated.kit.router.resolution === 'server') {
121
+ if (validated.kit.router.type === 'hash') {
122
+ throw new Error(
123
+ "The `router.resolution` option cannot be 'server' if `router.type` is 'hash'"
124
+ );
125
+ }
126
+ if (validated.kit.output.bundleStrategy !== 'split') {
127
+ throw new Error(
128
+ "The `router.resolution` option cannot be 'server' if `output.bundleStrategy` is 'inline' or 'single'"
129
+ );
130
+ }
131
+ }
132
+
133
+ return validated;
119
134
  }
@@ -261,7 +261,8 @@ const options = object(
261
261
  }),
262
262
 
263
263
  router: object({
264
- type: list(['pathname', 'hash'])
264
+ type: list(['pathname', 'hash']),
265
+ resolution: list(['client', 'server'])
265
266
  }),
266
267
 
267
268
  serviceWorker: object({
@@ -8,27 +8,31 @@ import { compact } from '../../utils/array.js';
8
8
  import { join_relative } from '../../utils/filesystem.js';
9
9
  import { dedent } from '../sync/utils.js';
10
10
  import { find_server_assets } from './find_server_assets.js';
11
+ import { uneval } from 'devalue';
11
12
 
12
13
  /**
13
14
  * Generates the data used to write the server-side manifest.js file. This data is used in the Vite
14
15
  * build process, to power routing, etc.
15
16
  * @param {{
16
17
  * build_data: import('types').BuildData;
18
+ * prerendered: string[];
17
19
  * relative_path: string;
18
20
  * routes: import('types').RouteData[];
19
21
  * }} opts
20
22
  */
21
- export function generate_manifest({ build_data, relative_path, routes }) {
23
+ export function generate_manifest({ build_data, prerendered, relative_path, routes }) {
22
24
  /**
23
25
  * @type {Map<any, number>} The new index of each node in the filtered nodes array
24
26
  */
25
27
  const reindexed = new Map();
26
28
  /**
27
29
  * All nodes actually used in the routes definition (prerendered routes are omitted).
28
- * Root layout/error is always included as they are needed for 404 and root errors.
30
+ * If `routes` is empty, it means that this manifest is only used for server-side resolution
31
+ * and the root layout/error is therefore not needed.
32
+ * Else, root layout/error is always included as they are needed for 404 and root errors.
29
33
  * @type {Set<any>}
30
34
  */
31
- const used_nodes = new Set([0, 1]);
35
+ const used_nodes = new Set(routes.length > 0 ? [0, 1] : []);
32
36
 
33
37
  const server_assets = find_server_assets(build_data, routes);
34
38
 
@@ -57,7 +61,11 @@ export function generate_manifest({ build_data, relative_path, routes }) {
57
61
  assets.push(build_data.service_worker);
58
62
  }
59
63
 
60
- const matchers = new Set();
64
+ // In case of server side route resolution, we need to include all matchers. Prerendered routes are not part
65
+ // of the server manifest, and they could reference matchers that then would not be included.
66
+ const matchers = new Set(
67
+ build_data.client?.nodes ? Object.keys(build_data.manifest_data.matchers) : undefined
68
+ );
61
69
 
62
70
  /** @param {Array<number | undefined>} indexes */
63
71
  function get_nodes(indexes) {
@@ -90,7 +98,7 @@ export function generate_manifest({ build_data, relative_path, routes }) {
90
98
  assets: new Set(${s(assets)}),
91
99
  mimeTypes: ${s(mime_types)},
92
100
  _: {
93
- client: ${s(build_data.client)},
101
+ client: ${uneval(build_data.client)},
94
102
  nodes: [
95
103
  ${(node_paths).map(loader).join(',\n')}
96
104
  ],
@@ -113,6 +121,7 @@ export function generate_manifest({ build_data, relative_path, routes }) {
113
121
  `;
114
122
  }).filter(Boolean).join(',\n')}
115
123
  ],
124
+ prerendered_routes: new Set(${s(prerendered)}),
116
125
  matchers: async () => {
117
126
  ${Array.from(
118
127
  matchers,
@@ -13,7 +13,7 @@ import { forked } from '../../utils/fork.js';
13
13
  import { installPolyfills } from '../../exports/node/polyfills.js';
14
14
  import { ENDPOINT_METHODS } from '../../constants.js';
15
15
  import { filter_private_env, filter_public_env } from '../../utils/env.js';
16
- import { resolve_route } from '../../utils/routing.js';
16
+ import { has_server_load, resolve_route } from '../../utils/routing.js';
17
17
  import { get_page_config } from '../../utils/route_config.js';
18
18
  import { check_feature } from '../../utils/features.js';
19
19
  import { createReadableStream } from '@sveltejs/kit/node';
@@ -88,7 +88,7 @@ async function analyse({
88
88
  }
89
89
 
90
90
  metadata.nodes[node.index] = {
91
- has_server_load: node.server?.load !== undefined || node.server?.trailingSlash !== undefined
91
+ has_server_load: has_server_load(node)
92
92
  };
93
93
  }
94
94
 
@@ -10,9 +10,11 @@ import colors from 'kleur';
10
10
  * @param {import('types').ValidatedKitConfig} kit
11
11
  * @param {import('types').ManifestData} manifest_data
12
12
  * @param {string} output
13
- * @param {Array<{ has_server_load: boolean }>} [metadata]
13
+ * @param {import('types').ServerMetadata['nodes']} [metadata] If this is omitted, we have to assume that all routes with a `+layout/page.server.js` file have a server load function
14
14
  */
15
15
  export function write_client_manifest(kit, manifest_data, output, metadata) {
16
+ const client_routing = kit.router.resolution === 'client';
17
+
16
18
  /**
17
19
  * Creates a module that exports a `CSRPageNode`
18
20
  * @param {import('types').PageNode} node
@@ -47,11 +49,14 @@ export function write_client_manifest(kit, manifest_data, output, metadata) {
47
49
  write_if_changed(`${output}/nodes/${i}.js`, generate_node(node));
48
50
  return `() => import('./nodes/${i}')`;
49
51
  })
52
+ // If route resolution happens on the server, we only need the root layout and root error page
53
+ // upfront, the rest is loaded on demand as the user navigates the app
54
+ .slice(0, client_routing ? manifest_data.nodes.length : 2)
50
55
  .join(',\n');
51
56
 
52
57
  const layouts_with_server_load = new Set();
53
58
 
54
- const dictionary = dedent`
59
+ let dictionary = dedent`
55
60
  {
56
61
  ${manifest_data.routes
57
62
  .map((route) => {
@@ -108,6 +113,13 @@ export function write_client_manifest(kit, manifest_data, output, metadata) {
108
113
  }
109
114
  `;
110
115
 
116
+ if (!client_routing) {
117
+ dictionary = '{}';
118
+ const root_layout = layouts_with_server_load.has(0);
119
+ layouts_with_server_load.clear();
120
+ if (root_layout) layouts_with_server_load.add(0);
121
+ }
122
+
111
123
  const client_hooks_file = resolve_entry(kit.files.hooks.client);
112
124
  const universal_hooks_file = resolve_entry(kit.files.hooks.universal);
113
125
 
@@ -123,6 +135,8 @@ export function write_client_manifest(kit, manifest_data, output, metadata) {
123
135
  );
124
136
  }
125
137
 
138
+ // Stringified version of
139
+ /** @type {import('../../runtime/client/types.js').SvelteKitApp} */
126
140
  write_if_changed(
127
141
  `${output}/app.js`,
128
142
  dedent`
@@ -137,7 +151,7 @@ export function write_client_manifest(kit, manifest_data, output, metadata) {
137
151
  : ''
138
152
  }
139
153
 
140
- export { matchers } from './matchers.js';
154
+ ${client_routing ? "export { matchers } from './matchers.js';" : 'export const matchers = {};'}
141
155
 
142
156
  export const nodes = [
143
157
  ${nodes}
@@ -158,7 +172,7 @@ export function write_client_manifest(kit, manifest_data, output, metadata) {
158
172
 
159
173
  export const decoders = Object.fromEntries(Object.entries(hooks.transport).map(([k, v]) => [k, v.decode]));
160
174
 
161
- export const hash = ${JSON.stringify(kit.router.type === 'hash')};
175
+ export const hash = ${s(kit.router.type === 'hash')};
162
176
 
163
177
  export const decode = (type, value) => decoders[type](value);
164
178
 
@@ -166,21 +180,23 @@ export function write_client_manifest(kit, manifest_data, output, metadata) {
166
180
  `
167
181
  );
168
182
 
169
- // write matchers to a separate module so that we don't
170
- // need to worry about name conflicts
171
- const imports = [];
172
- const matchers = [];
183
+ if (client_routing) {
184
+ // write matchers to a separate module so that we don't
185
+ // need to worry about name conflicts
186
+ const imports = [];
187
+ const matchers = [];
173
188
 
174
- for (const key in manifest_data.matchers) {
175
- const src = manifest_data.matchers[key];
189
+ for (const key in manifest_data.matchers) {
190
+ const src = manifest_data.matchers[key];
176
191
 
177
- imports.push(`import { match as ${key} } from ${s(relative_path(output, src))};`);
178
- matchers.push(key);
179
- }
192
+ imports.push(`import { match as ${key} } from ${s(relative_path(output, src))};`);
193
+ matchers.push(key);
194
+ }
180
195
 
181
- const module = imports.length
182
- ? `${imports.join('\n')}\n\nexport const matchers = { ${matchers.join(', ')} };`
183
- : 'export const matchers = {};';
196
+ const module = imports.length
197
+ ? `${imports.join('\n')}\n\nexport const matchers = { ${matchers.join(', ')} };`
198
+ : 'export const matchers = {};';
184
199
 
185
- write_if_changed(`${output}/matchers.js`, module);
200
+ write_if_changed(`${output}/matchers.js`, module);
201
+ }
186
202
  }
@@ -35,7 +35,6 @@ import { set_manifest, set_read_implementation } from '__sveltekit/server';
35
35
  import { set_private_env, set_public_env, set_safe_public_env } from '${runtime_directory}/shared-server.js';
36
36
 
37
37
  export const options = {
38
- app_dir: ${s(config.kit.appDir)},
39
38
  app_template_contains_nonce: ${template.includes('%sveltekit.nonce%')},
40
39
  csp: ${s(config.kit.csp)},
41
40
  csrf_check_origin: ${s(config.kit.csrf.checkOrigin)},
@@ -118,6 +117,8 @@ export function write_server(config, output) {
118
117
  return posixify(path.relative(`${output}/server`, file));
119
118
  }
120
119
 
120
+ // Contains the stringified version of
121
+ /** @type {import('types').SSROptions} */
121
122
  write_if_changed(
122
123
  `${output}/server/internal.js`,
123
124
  server_template({
@@ -659,6 +659,28 @@ export interface KitConfig {
659
659
  * @since 2.14.0
660
660
  */
661
661
  type?: 'pathname' | 'hash';
662
+ /**
663
+ * How to determine which route to load when navigating to a new page.
664
+ *
665
+ * By default, SvelteKit will serve a route manifest to the browser.
666
+ * When navigating, this manifest is used (along with the `reroute` hook, if it exists) to determine which components to load and which `load` functions to run.
667
+ * Because everything happens on the client, this decision can be made immediately. The drawback is that the manifest needs to be
668
+ * loaded and parsed before the first navigation can happen, which may have an impact if your app contains many routes.
669
+ *
670
+ * Alternatively, SvelteKit can determine the route on the server. This means that for every navigation to a path that has not yet been visited, the server will be asked to determine the route.
671
+ * This has several advantages:
672
+ * - The client does not need to load the routing manifest upfront, which can lead to faster initial page loads
673
+ * - The list of routes is hidden from public view
674
+ * - The server has an opportunity to intercept each navigation (for example through a middleware), enabling (for example) A/B testing opaque to SvelteKit
675
+
676
+ * The drawback is that for unvisited paths, resolution will take slightly longer (though this is mitigated by [preloading](https://svelte.dev/docs/kit/link-options#data-sveltekit-preload-data)).
677
+ *
678
+ * > [!NOTE] When using server-side route resolution and prerendering, the resolution is prerendered along with the route itself.
679
+ *
680
+ * @default "client"
681
+ * @since 2.17.0
682
+ */
683
+ resolution?: 'client' | 'server';
662
684
  };
663
685
  serviceWorker?: {
664
686
  /**
@@ -1088,31 +1110,31 @@ export interface AfterNavigate extends Omit<Navigation, 'type'> {
1088
1110
  }
1089
1111
 
1090
1112
  /**
1091
- * The shape of the `$page` store
1113
+ * The shape of the [`page`](https://svelte.dev/docs/kit/$app-state#page) reactive object and the [`$page`](https://svelte.dev/docs/kit/$app-stores) store.
1092
1114
  */
1093
1115
  export interface Page<
1094
1116
  Params extends Record<string, string> = Record<string, string>,
1095
1117
  RouteId extends string | null = string | null
1096
1118
  > {
1097
1119
  /**
1098
- * The URL of the current page
1120
+ * The URL of the current page.
1099
1121
  */
1100
1122
  url: URL;
1101
1123
  /**
1102
- * The parameters of the current page - e.g. for a route like `/blog/[slug]`, a `{ slug: string }` object
1124
+ * The parameters of the current page - e.g. for a route like `/blog/[slug]`, a `{ slug: string }` object.
1103
1125
  */
1104
1126
  params: Params;
1105
1127
  /**
1106
- * Info about the current route
1128
+ * Info about the current route.
1107
1129
  */
1108
1130
  route: {
1109
1131
  /**
1110
- * The ID of the current route - e.g. for `src/routes/blog/[slug]`, it would be `/blog/[slug]`
1132
+ * The ID of the current route - e.g. for `src/routes/blog/[slug]`, it would be `/blog/[slug]`.
1111
1133
  */
1112
1134
  id: RouteId;
1113
1135
  };
1114
1136
  /**
1115
- * Http status code of the current page
1137
+ * HTTP status code of the current page.
1116
1138
  */
1117
1139
  status: number;
1118
1140
  /**
@@ -1297,6 +1319,7 @@ export interface SSRManifest {
1297
1319
  client: NonNullable<BuildData['client']>;
1298
1320
  nodes: SSRNodeLoader[];
1299
1321
  routes: SSRRoute[];
1322
+ prerendered_routes: Set<string>;
1300
1323
  matchers: () => Promise<Record<string, ParamMatcher>>;
1301
1324
  /** A `[file]: size` map of all assets imported by server code */
1302
1325
  server_assets: Record<string, number>;
@@ -47,7 +47,12 @@ export function build_server_nodes(out, kit, manifest_data, server_manifest, cli
47
47
  css.filter(asset => client_stylesheets.has(asset.fileName))
48
48
  .forEach((asset) => {
49
49
  if (asset.source.length < kit.inlineStyleThreshold) {
50
+ // We know that the names for entry points are numbers.
50
51
  const [index] = basename(asset.fileName).split('.');
52
+ // There can also be other CSS files from shared components
53
+ // for example, which we need to ignore here.
54
+ if (isNaN(+index)) return;
55
+
51
56
  const server_stylesheet = server_stylesheets.get(+index);
52
57
  const file = `${out}/server/stylesheets/${index}.js`;
53
58
 
@@ -136,7 +136,34 @@ export async function dev(vite, vite_config, svelte_config) {
136
136
  imports: [],
137
137
  stylesheets: [],
138
138
  fonts: [],
139
- uses_env_dynamic_public: true
139
+ uses_env_dynamic_public: true,
140
+ nodes:
141
+ svelte_config.kit.router.resolution === 'client'
142
+ ? undefined
143
+ : manifest_data.nodes.map((node, i) => {
144
+ if (node.component || node.universal) {
145
+ return `${svelte_config.kit.paths.base}${to_fs(svelte_config.kit.outDir)}/generated/client/nodes/${i}.js`;
146
+ }
147
+ }),
148
+ routes:
149
+ svelte_config.kit.router.resolution === 'client'
150
+ ? undefined
151
+ : compact(
152
+ manifest_data.routes.map((route) => {
153
+ if (!route.page) return;
154
+
155
+ return {
156
+ id: route.id,
157
+ pattern: route.pattern,
158
+ params: route.params,
159
+ layouts: route.page.layouts.map((l) =>
160
+ l !== undefined ? [!!manifest_data.nodes[l].server, l] : undefined
161
+ ),
162
+ errors: route.page.errors,
163
+ leaf: [!!manifest_data.nodes[route.page.leaf].server, route.page.leaf]
164
+ };
165
+ })
166
+ )
140
167
  },
141
168
  server_assets: new Proxy(
142
169
  {},
@@ -222,6 +249,7 @@ export async function dev(vite, vite_config, svelte_config) {
222
249
  return result;
223
250
  };
224
251
  }),
252
+ prerendered_routes: new Set(),
225
253
  routes: compact(
226
254
  manifest_data.routes.map((route) => {
227
255
  if (!route.page && !route.endpoint) return null;
@@ -13,7 +13,7 @@ import { load_config } from '../../core/config/index.js';
13
13
  import { generate_manifest } from '../../core/generate_manifest/index.js';
14
14
  import { build_server_nodes } from './build/build_server.js';
15
15
  import { build_service_worker } from './build/build_service_worker.js';
16
- import { assets_base, find_deps } from './build/utils.js';
16
+ import { assets_base, find_deps, resolve_symlinks } from './build/utils.js';
17
17
  import { dev } from './dev/index.js';
18
18
  import { is_illegal, module_guard } from './graph_analysis/index.js';
19
19
  import { preview } from './preview/index.js';
@@ -35,6 +35,7 @@ import {
35
35
  sveltekit_server
36
36
  } from './module_ids.js';
37
37
  import { resolve_peer_dependency } from '../../utils/import.js';
38
+ import { compact } from '../../utils/array.js';
38
39
 
39
40
  const cwd = process.cwd();
40
41
 
@@ -319,7 +320,8 @@ async function kit({ svelte_config }) {
319
320
  __SVELTEKIT_APP_VERSION_FILE__: s(`${kit.appDir}/version.json`),
320
321
  __SVELTEKIT_APP_VERSION_POLL_INTERVAL__: s(kit.version.pollInterval),
321
322
  __SVELTEKIT_DEV__: 'false',
322
- __SVELTEKIT_EMBEDDED__: kit.embedded ? 'true' : 'false'
323
+ __SVELTEKIT_EMBEDDED__: kit.embedded ? 'true' : 'false',
324
+ __SVELTEKIT_CLIENT_ROUTING__: kit.router.resolution === 'client' ? 'true' : 'false'
323
325
  };
324
326
 
325
327
  if (!secondary_build_started) {
@@ -329,7 +331,8 @@ async function kit({ svelte_config }) {
329
331
  new_config.define = {
330
332
  __SVELTEKIT_APP_VERSION_POLL_INTERVAL__: '0',
331
333
  __SVELTEKIT_DEV__: 'true',
332
- __SVELTEKIT_EMBEDDED__: kit.embedded ? 'true' : 'false'
334
+ __SVELTEKIT_EMBEDDED__: kit.embedded ? 'true' : 'false',
335
+ __SVELTEKIT_CLIENT_ROUTING__: kit.router.resolution === 'client' ? 'true' : 'false'
333
336
  };
334
337
 
335
338
  // These Kit dependencies are packaged as CommonJS, which means they must always be externalized.
@@ -421,15 +424,22 @@ async function kit({ svelte_config }) {
421
424
 
422
425
  const illegal_module = strip_virtual_prefix(relative);
423
426
 
427
+ const error_prefix = `Cannot import ${illegal_module} into client-side code. This could leak sensitive information.`;
428
+ const error_suffix = `
429
+ Tips:
430
+ - To resolve this error, ensure that no exports from ${illegal_module} are used, even transitively, in client-side code.
431
+ - If you're only using the import as a type, change it to \`import type\`.
432
+ - If you're not sure which module is causing this, try building your app -- it will create a more helpful error.`;
433
+
424
434
  if (import_map.has(illegal_module)) {
425
435
  const importer = path.relative(
426
436
  cwd,
427
437
  /** @type {string} */ (import_map.get(illegal_module))
428
438
  );
429
- throw new Error(`Cannot import ${illegal_module} into client-side code: ${importer}`);
439
+ throw new Error(`${error_prefix}\nImported by: ${importer}.${error_suffix}`);
430
440
  }
431
441
 
432
- throw new Error(`Cannot import ${illegal_module} into client-side code`);
442
+ throw new Error(`${error_prefix}${error_suffix}`);
433
443
  }
434
444
  }
435
445
 
@@ -472,12 +482,14 @@ async function kit({ svelte_config }) {
472
482
  return dedent`
473
483
  export const base = ${global}?.base ?? ${s(base)};
474
484
  export const assets = ${global}?.assets ?? ${assets ? s(assets) : 'base'};
485
+ export const app_dir = ${s(kit.appDir)};
475
486
  `;
476
487
  }
477
488
 
478
489
  return dedent`
479
490
  export let base = ${s(base)};
480
491
  export let assets = ${assets ? s(assets) : 'base'};
492
+ export const app_dir = ${s(kit.appDir)};
481
493
 
482
494
  export const relative = ${svelte_config.kit.paths.relative};
483
495
 
@@ -788,6 +800,7 @@ async function kit({ svelte_config }) {
788
800
  manifest_path,
789
801
  `export const manifest = ${generate_manifest({
790
802
  build_data,
803
+ prerendered: [],
791
804
  relative_path: '.',
792
805
  routes: manifest_data.routes
793
806
  })};\n`
@@ -870,6 +883,37 @@ async function kit({ svelte_config }) {
870
883
  (chunk) => chunk.type === 'chunk' && chunk.modules[env_dynamic_public]
871
884
  )
872
885
  };
886
+
887
+ // In case of server-side route resolution, we create a purpose-built route manifest that is
888
+ // similar to that on the client, with as much information computed upfront so that we
889
+ // don't need to include any code of the actual routes in the server bundle.
890
+ if (svelte_config.kit.router.resolution === 'server') {
891
+ build_data.client.nodes = manifest_data.nodes.map((node, i) => {
892
+ if (node.component || node.universal) {
893
+ return resolve_symlinks(
894
+ client_manifest,
895
+ `${kit.outDir}/generated/client-optimized/nodes/${i}.js`
896
+ ).chunk.file;
897
+ }
898
+ });
899
+
900
+ build_data.client.routes = compact(
901
+ manifest_data.routes.map((route) => {
902
+ if (!route.page) return;
903
+
904
+ return {
905
+ id: route.id,
906
+ pattern: route.pattern,
907
+ params: route.params,
908
+ layouts: route.page.layouts.map((l) =>
909
+ l !== undefined ? [metadata.nodes[l].has_server_load, l] : undefined
910
+ ),
911
+ errors: route.page.errors,
912
+ leaf: [metadata.nodes[route.page.leaf].has_server_load, route.page.leaf]
913
+ };
914
+ })
915
+ );
916
+ }
873
917
  } else {
874
918
  const start = deps_of(`${runtime_directory}/client/bundle.js`);
875
919
 
@@ -910,6 +954,7 @@ async function kit({ svelte_config }) {
910
954
  manifest_path,
911
955
  `export const manifest = ${generate_manifest({
912
956
  build_data,
957
+ prerendered: [],
913
958
  relative_path: '.',
914
959
  routes: manifest_data.routes
915
960
  })};\n`
@@ -941,6 +986,7 @@ async function kit({ svelte_config }) {
941
986
  `${out}/server/manifest.js`,
942
987
  `export const manifest = ${generate_manifest({
943
988
  build_data,
989
+ prerendered: prerendered.paths,
944
990
  relative_path: '.',
945
991
  routes: manifest_data.routes.filter((route) => prerender_map.get(route.id) !== true)
946
992
  })};\n`
@@ -32,6 +32,17 @@ import { BROWSER } from 'esm-env';
32
32
  * {/if}
33
33
  * ```
34
34
  *
35
+ * Changes to `page` are available exclusively with runes. (The legacy reactivity syntax will not reflect any changes)
36
+ *
37
+ * ```svelte
38
+ * <!--- file: +page.svelte --->
39
+ * <script>
40
+ * import { page } from '$app/state';
41
+ * const id = $derived(page.params.id); // This will correctly update id for usage on this page
42
+ * $: badId = page.params.id; // Do not use; will never update after initial load
43
+ * </script>
44
+ * ```
45
+ *
35
46
  * On the server, values can only be read during rendering (in other words _not_ in e.g. `load` functions). In the browser, the values can be read at any time.
36
47
  *
37
48
  * @type {import('@sveltejs/kit').Page}