@inertiajs/svelte 3.0.0-beta.1 → 3.0.0-beta.3

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.
@@ -35,6 +35,10 @@
35
35
  let page = $state({ ...initialPage, flash: initialPage.flash ?? {} })
36
36
  let renderProps = $derived.by<RenderProps>(() => resolveRenderProps(component, page, key))
37
37
 
38
+ // Synchronous initialization so the global page store is populated during SSR
39
+ // ($effect.pre does not run during Svelte 5 SSR)
40
+ setPage(page)
41
+
38
42
  // Reactively update the global page state when local page state changes
39
43
  $effect.pre(() => {
40
44
  setPage(page)
@@ -1,4 +1,4 @@
1
- import { type CreateInertiaAppOptions, type CreateInertiaAppOptionsForCSR, type InertiaAppSSRResponse, type Page, type PageProps } from '@inertiajs/core';
1
+ import { type CreateInertiaAppOptions, type CreateInertiaAppOptionsForCSR, type InertiaAppSSRResponse, type Page, type PageProps, type SharedPageProps } from '@inertiajs/core';
2
2
  import App, { type InertiaAppProps } from './components/App.svelte';
3
3
  import type { ComponentResolver, SvelteInertiaAppConfig } from './types';
4
4
  type SvelteRenderResult = {
@@ -10,14 +10,22 @@ type SetupOptions<SharedProps extends PageProps> = {
10
10
  App: typeof App;
11
11
  props: InertiaAppProps<SharedProps>;
12
12
  };
13
- type InertiaAppOptionsForCSR<SharedProps extends PageProps> = CreateInertiaAppOptionsForCSR<SharedProps, ComponentResolver, SetupOptions<SharedProps>, SvelteRenderResult | void, SvelteInertiaAppConfig>;
13
+ type InertiaAppOptionsForCSR<SharedProps extends PageProps> = CreateInertiaAppOptionsForCSR<SharedProps, ComponentResolver, SetupOptions<SharedProps>, SvelteRenderResult | void, SvelteInertiaAppConfig> & {
14
+ withApp?: (context: Map<any, any>, options: {
15
+ ssr: boolean;
16
+ }) => void;
17
+ };
14
18
  type InertiaAppOptionsAuto<SharedProps extends PageProps> = CreateInertiaAppOptions<ComponentResolver, SetupOptions<SharedProps>, SvelteRenderResult | void, SvelteInertiaAppConfig> & {
15
19
  page?: Page<SharedProps>;
20
+ withApp?: (context: Map<any, any>, options: {
21
+ ssr: boolean;
22
+ }) => void;
16
23
  };
17
24
  type SvelteServerRender = (component: typeof App, options: {
18
25
  props: InertiaAppProps<PageProps>;
26
+ context?: Map<any, any>;
19
27
  }) => SvelteRenderResult;
20
28
  type RenderFunction<SharedProps extends PageProps> = (page: Page<SharedProps>, render: SvelteServerRender) => Promise<InertiaAppSSRResponse>;
21
- export default function createInertiaApp<SharedProps extends PageProps = PageProps>(options: InertiaAppOptionsForCSR<SharedProps>): Promise<InertiaAppSSRResponse | void>;
22
- export default function createInertiaApp<SharedProps extends PageProps = PageProps>(options?: InertiaAppOptionsAuto<SharedProps>): Promise<void | RenderFunction<SharedProps>>;
29
+ export default function createInertiaApp<SharedProps extends PageProps = PageProps & SharedPageProps>(options: InertiaAppOptionsForCSR<SharedProps>): Promise<InertiaAppSSRResponse | void>;
30
+ export default function createInertiaApp<SharedProps extends PageProps = PageProps & SharedPageProps>(options?: InertiaAppOptionsAuto<SharedProps>): Promise<void | RenderFunction<SharedProps>>;
23
31
  export {};
@@ -2,7 +2,7 @@ import { buildSSRBody, getInitialPageFromDOM, http as httpModule, router, setupP
2
2
  import { hydrate, mount } from 'svelte';
3
3
  import App, {} from './components/App.svelte';
4
4
  import { config } from './index';
5
- export default async function createInertiaApp({ id = 'app', resolve, setup, progress = {}, page, defaults = {}, http, layout, } = {}) {
5
+ export default async function createInertiaApp({ id = 'app', resolve, setup, progress = {}, page, defaults = {}, http, layout, withApp, } = {}) {
6
6
  config.replace(defaults);
7
7
  if (http) {
8
8
  httpModule.setClient(http);
@@ -27,7 +27,11 @@ export default async function createInertiaApp({ id = 'app', resolve, setup, pro
27
27
  svelteApp = result;
28
28
  }
29
29
  else {
30
- svelteApp = render(App, { props });
30
+ const context = new Map();
31
+ if (withApp) {
32
+ withApp(context, { ssr: true });
33
+ }
34
+ svelteApp = render(App, { props, context });
31
35
  }
32
36
  const body = buildSSRBody(id, page, svelteApp.body);
33
37
  return {
@@ -60,11 +64,17 @@ export default async function createInertiaApp({ id = 'app', resolve, setup, pro
60
64
  if (setup) {
61
65
  await setup({ el: target, App, props });
62
66
  }
63
- else if (target.hasAttribute('data-server-rendered')) {
64
- hydrate(App, { target, props });
65
- }
66
67
  else {
67
- mount(App, { target, props });
68
+ const context = new Map();
69
+ if (withApp) {
70
+ withApp(context, { ssr: false });
71
+ }
72
+ if (target.hasAttribute('data-server-rendered')) {
73
+ hydrate(App, { target, props, context });
74
+ }
75
+ else {
76
+ mount(App, { target, props, context });
77
+ }
68
78
  }
69
79
  if (progress) {
70
80
  setupProgress(progress);
@@ -1,6 +1,5 @@
1
- import { type Readable } from 'svelte/store';
2
1
  export declare function setLayoutProps(props: Record<string, unknown>): void;
3
2
  export declare function setLayoutPropsFor(name: string, props: Record<string, unknown>): void;
4
3
  export declare function resetLayoutProps(): void;
5
4
  export declare const LAYOUT_CONTEXT_KEY: unique symbol;
6
- export declare function useLayoutProps<T extends Record<string, unknown>>(defaults: T): Readable<T>;
5
+ export declare function useLayoutProps<T extends Record<string, unknown>>(defaults: T): T;
@@ -1,7 +1,15 @@
1
1
  import { createLayoutPropsStore, mergeLayoutProps } from '@inertiajs/core';
2
2
  import { getContext } from 'svelte';
3
- import { readable } from 'svelte/store';
4
3
  const store = createLayoutPropsStore();
4
+ const storeState = $state({
5
+ shared: {},
6
+ named: {},
7
+ });
8
+ store.subscribe(() => {
9
+ const snapshot = store.get();
10
+ storeState.shared = snapshot.shared;
11
+ storeState.named = snapshot.named;
12
+ });
5
13
  export function setLayoutProps(props) {
6
14
  store.set(props);
7
15
  }
@@ -17,9 +25,12 @@ export function useLayoutProps(defaults) {
17
25
  const resolve = () => {
18
26
  const staticProps = context?.staticProps ?? {};
19
27
  const name = context?.name;
20
- const { shared, named } = store.get();
21
- const dynamicProps = name ? { ...shared, ...named[name] } : shared;
28
+ const dynamicProps = name ? { ...storeState.shared, ...(storeState.named[name] ?? {}) } : storeState.shared;
22
29
  return mergeLayoutProps(defaults, staticProps, dynamicProps);
23
30
  };
24
- return readable(resolve(), (set) => store.subscribe(() => set(resolve())));
31
+ const state = $state(resolve());
32
+ $effect.pre(() => {
33
+ Object.assign(state, resolve());
34
+ });
35
+ return state;
25
36
  }
package/dist/types.d.ts CHANGED
@@ -1,7 +1,7 @@
1
- import { type Page } from '@inertiajs/core';
1
+ import { type Page, type SharedPageProps } from '@inertiajs/core';
2
2
  import type { Component } from 'svelte';
3
3
  import type { RenderFunction, RenderProps } from './components/Render.svelte';
4
- export type ComponentResolver = (name: string, page?: Page) => ResolvedComponent | Promise<ResolvedComponent>;
4
+ export type ComponentResolver = (name: string, page?: Page<SharedPageProps>) => ResolvedComponent | Promise<ResolvedComponent>;
5
5
  export type LayoutResolver = (h: RenderFunction, page: RenderProps) => RenderProps;
6
6
  export type LayoutTuple = [Component, Record<string, unknown>?];
7
7
  export type LayoutObject = {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@inertiajs/svelte",
3
- "version": "3.0.0-beta.1",
3
+ "version": "3.0.0-beta.3",
4
4
  "license": "MIT",
5
5
  "description": "The Svelte adapter for Inertia.js",
6
6
  "contributors": [
@@ -54,9 +54,9 @@
54
54
  },
55
55
  "dependencies": {
56
56
  "@types/lodash-es": "^4.17.12",
57
- "laravel-precognition": "2.0.0-beta.2",
57
+ "laravel-precognition": "2.0.0-beta.3",
58
58
  "lodash-es": "^4.17.23",
59
- "@inertiajs/core": "3.0.0-beta.1"
59
+ "@inertiajs/core": "3.0.0-beta.3"
60
60
  },
61
61
  "scripts": {
62
62
  "build": "pnpm package && svelte-check --tsconfig ./tsconfig.json && publint",
@@ -1,6 +1,6 @@
1
1
  ---
2
2
  name: inertia-svelte-development
3
- description: "Develops Inertia.js v2 Svelte client-side applications. Activates when creating Svelte pages, forms, or navigation; using Link, Form, or router; working with deferred props, prefetching, or polling; or when user mentions Svelte with Inertia, Svelte pages, Svelte forms, or Svelte navigation."
3
+ description: "Develops Inertia.js v3 Svelte 5 client-side applications. Activates when creating Svelte pages, forms, or navigation; using Link, Form, useForm, useHttp, useLayoutProps, or router; working with deferred props, prefetching, optimistic updates, instant visits, or polling; or when user mentions Svelte with Inertia, Svelte pages, Svelte forms, or Svelte navigation."
4
4
  license: MIT
5
5
  metadata:
6
6
  author: laravel
@@ -15,14 +15,24 @@ metadata:
15
15
  Activate this skill when:
16
16
 
17
17
  - Creating or modifying Svelte page components for Inertia
18
- - Working with forms in Svelte (using `<Form>` or `useForm`)
18
+ - Working with forms in Svelte (using `<Form>`, `useForm`, or `useHttp`)
19
19
  - Implementing client-side navigation with `<Link>` or `router`
20
- - Using v2 features: deferred props, prefetching, WhenVisible, InfiniteScroll, once props, flash data, or polling
20
+ - Using v3 features: deferred props, prefetching, optimistic updates, instant visits, layout props, HTTP requests, WhenVisible, InfiniteScroll, once props, flash data, or polling
21
21
  - Building Svelte-specific features with the Inertia protocol
22
22
 
23
+ ## Important: Svelte 5 Required
24
+
25
+ Inertia v3 requires Svelte 5. All code must use Svelte 5 runes syntax:
26
+
27
+ - Use `let { prop } = $props()` (not `export let prop`)
28
+ - Use `onclick` (not `on:click`)
29
+ - Use `$derived()` for reactive values (not `$:`)
30
+ - Use `{#snippet}` for named slots (not `slot="name"`)
31
+ - Use `{@render children()}` for default slot content
32
+
23
33
  ## Documentation
24
34
 
25
- Use `search-docs` for detailed Inertia v2 Svelte patterns and documentation.
35
+ Use `search-docs` for detailed Inertia v3 Svelte patterns and documentation.
26
36
 
27
37
  ## Basic Usage
28
38
 
@@ -34,7 +44,7 @@ Svelte page components should be placed in the `{{ $assist->inertia()->pagesDire
34
44
 
35
45
  @boostsnippet("Basic Svelte Page Component", "svelte")
36
46
  <script>
37
- export let users
47
+ let { users } = $props()
38
48
  </script>
39
49
 
40
50
  <div>
@@ -111,31 +121,33 @@ function createUser() {
111
121
  @if($assist->inertia()->hasFormComponent())
112
122
  ### Form Component (Recommended)
113
123
 
114
- The recommended way to build forms is with the `<Form>` component:
124
+ The recommended way to build forms is with the `<Form>` component. In Svelte 5, use `{#snippet}` for the default slot:
115
125
 
116
126
  @boostsnippet("Form Component Example", "svelte")
117
127
  <script>
118
128
  import { Form } from '@inertiajs/svelte'
119
129
  </script>
120
130
 
121
- <Form action="/users" method="post" let:errors let:processing let:wasSuccessful>
122
- <input type="text" name="name" />
123
- {#if errors.name}
124
- <div>{errors.name}</div>
125
- {/if}
126
-
127
- <input type="email" name="email" />
128
- {#if errors.email}
129
- <div>{errors.email}</div>
130
- {/if}
131
-
132
- <button type="submit" disabled={processing}>
133
- {processing ? 'Creating...' : 'Create User'}
134
- </button>
135
-
136
- {#if wasSuccessful}
137
- <div>User created!</div>
138
- {/if}
131
+ <Form action="/users" method="post">
132
+ {#snippet children({ errors, processing, wasSuccessful })}
133
+ <input type="text" name="name" />
134
+ {#if errors.name}
135
+ <div>{errors.name}</div>
136
+ {/if}
137
+
138
+ <input type="email" name="email" />
139
+ {#if errors.email}
140
+ <div>{errors.email}</div>
141
+ {/if}
142
+
143
+ <button type="submit" disabled={processing}>
144
+ {processing ? 'Creating...' : 'Create User'}
145
+ </button>
146
+
147
+ {#if wasSuccessful}
148
+ <div>User created!</div>
149
+ {/if}
150
+ {/snippet}
139
151
  </Form>
140
152
  @endboostsnippet
141
153
 
@@ -146,40 +158,40 @@ import { Form } from '@inertiajs/svelte'
146
158
  import { Form } from '@inertiajs/svelte'
147
159
  </script>
148
160
 
149
- <Form
150
- action="/users"
151
- method="post"
152
- let:errors
153
- let:hasErrors
154
- let:processing
155
- let:progress
156
- let:wasSuccessful
157
- let:recentlySuccessful
158
- let:clearErrors
159
- let:resetAndClearErrors
160
- let:defaults
161
- let:isDirty
162
- let:reset
163
- let:submit
164
- >
165
- <input type="text" name="name" value={defaults.name} />
166
- {#if errors.name}
167
- <div>{errors.name}</div>
168
- {/if}
169
-
170
- <button type="submit" disabled={processing}>
171
- {processing ? 'Saving...' : 'Save'}
172
- </button>
173
-
174
- {#if progress}
175
- <progress value={progress.percentage} max="100">
176
- {progress.percentage}%
177
- </progress>
178
- {/if}
179
-
180
- {#if wasSuccessful}
181
- <div>Saved!</div>
182
- {/if}
161
+ <Form action="/users" method="post">
162
+ {#snippet children({
163
+ errors,
164
+ hasErrors,
165
+ processing,
166
+ progress,
167
+ wasSuccessful,
168
+ recentlySuccessful,
169
+ clearErrors,
170
+ resetAndClearErrors,
171
+ defaults,
172
+ isDirty,
173
+ reset,
174
+ submit
175
+ })}
176
+ <input type="text" name="name" value={defaults.name} />
177
+ {#if errors.name}
178
+ <div>{errors.name}</div>
179
+ {/if}
180
+
181
+ <button type="submit" disabled={processing}>
182
+ {processing ? 'Saving...' : 'Save'}
183
+ </button>
184
+
185
+ {#if progress}
186
+ <progress value={progress.percentage} max="100">
187
+ {progress.percentage}%
188
+ </progress>
189
+ {/if}
190
+
191
+ {#if wasSuccessful}
192
+ <div>Saved!</div>
193
+ {/if}
194
+ {/snippet}
183
195
  </Form>
184
196
  @endboostsnippet
185
197
 
@@ -199,23 +211,17 @@ Use the `search-docs` tool with a query of `form component resetting` for detail
199
211
  import { Form } from '@inertiajs/svelte'
200
212
  </script>
201
213
 
202
- <Form
203
- action="/users"
204
- method="post"
205
- resetOnSuccess
206
- setDefaultsOnSuccess
207
- let:errors
208
- let:processing
209
- let:wasSuccessful
210
- >
211
- <input type="text" name="name" />
212
- {#if errors.name}
213
- <div>{errors.name}</div>
214
- {/if}
215
-
216
- <button type="submit" disabled={processing}>
217
- Submit
218
- </button>
214
+ <Form action="/users" method="post" resetOnSuccess setDefaultsOnSuccess>
215
+ {#snippet children({ errors, processing, wasSuccessful })}
216
+ <input type="text" name="name" />
217
+ {#if errors.name}
218
+ <div>{errors.name}</div>
219
+ {/if}
220
+
221
+ <button type="submit" disabled={processing}>
222
+ Submit
223
+ </button>
224
+ {/snippet}
219
225
  </Form>
220
226
  @endboostsnippet
221
227
  @else
@@ -244,36 +250,166 @@ const form = useForm({
244
250
  password: '',
245
251
  })
246
252
 
247
- function submit() {
248
- $form.post('/users', {
249
- onSuccess: () => $form.reset('password'),
253
+ function submit(e) {
254
+ e.preventDefault()
255
+ form.post('/users', {
256
+ onSuccess: () => form.reset('password'),
250
257
  })
251
258
  }
252
259
  </script>
253
260
 
254
- <form on:submit|preventDefault={submit}>
255
- <input type="text" bind:value={$form.name} />
256
- {#if $form.errors.name}
257
- <div>{$form.errors.name}</div>
261
+ <form onsubmit={submit}>
262
+ <input type="text" bind:value={form.name} />
263
+ {#if form.errors.name}
264
+ <div>{form.errors.name}</div>
258
265
  {/if}
259
266
 
260
- <input type="email" bind:value={$form.email} />
261
- {#if $form.errors.email}
262
- <div>{$form.errors.email}</div>
267
+ <input type="email" bind:value={form.email} />
268
+ {#if form.errors.email}
269
+ <div>{form.errors.email}</div>
263
270
  {/if}
264
271
 
265
- <input type="password" bind:value={$form.password} />
266
- {#if $form.errors.password}
267
- <div>{$form.errors.password}</div>
272
+ <input type="password" bind:value={form.password} />
273
+ {#if form.errors.password}
274
+ <div>{form.errors.password}</div>
268
275
  {/if}
269
276
 
270
- <button type="submit" disabled={$form.processing}>
277
+ <button type="submit" disabled={form.processing}>
271
278
  Create User
272
279
  </button>
273
280
  </form>
274
281
  @endboostsnippet
275
282
 
276
- ## Inertia v2 Features
283
+ ## Inertia v3 Features
284
+
285
+ ### HTTP Requests
286
+
287
+ Use the `useHttp` hook for standalone HTTP requests that do not trigger Inertia page visits. It provides the same developer experience as `useForm`, but for plain JSON endpoints.
288
+
289
+ @boostsnippet("useHttp Example", "svelte")
290
+ <script>
291
+ import { useHttp } from '@inertiajs/svelte'
292
+
293
+ const http = useHttp({
294
+ query: '',
295
+ })
296
+
297
+ function search() {
298
+ http.get('/api/search', {
299
+ onSuccess: (response) => {
300
+ console.log(response)
301
+ },
302
+ })
303
+ }
304
+ </script>
305
+
306
+ <input bind:value={http.query} oninput={search} />
307
+ {#if http.processing}
308
+ <div>Searching...</div>
309
+ {/if}
310
+ @endboostsnippet
311
+
312
+ ### Optimistic Updates
313
+
314
+ Apply data changes instantly before the server responds, with automatic rollback on failure:
315
+
316
+ @boostsnippet("Optimistic Update with Router", "svelte")
317
+ <script>
318
+ import { router } from '@inertiajs/svelte'
319
+
320
+ function like(post) {
321
+ router.optimistic((props) => ({
322
+ post: {
323
+ ...props.post,
324
+ likes: props.post.likes + 1,
325
+ },
326
+ })).post(`/posts/${post.id}/like`)
327
+ }
328
+ </script>
329
+ @endboostsnippet
330
+
331
+ Optimistic updates also work with `useForm` and the `<Form>` component:
332
+
333
+ @boostsnippet("Optimistic Update with Form Component", "svelte")
334
+ <script>
335
+ import { Form } from '@inertiajs/svelte'
336
+ </script>
337
+
338
+ <Form
339
+ action="/todos"
340
+ method="post"
341
+ optimistic={(props, data) => ({
342
+ todos: [...props.todos, { id: Date.now(), name: data.name, done: false }],
343
+ })}
344
+ >
345
+ {#snippet children({ processing })}
346
+ <input type="text" name="name" />
347
+ <button type="submit" disabled={processing}>Add Todo</button>
348
+ {/snippet}
349
+ </Form>
350
+ @endboostsnippet
351
+
352
+ ### Instant Visits
353
+
354
+ Navigate to a new page immediately without waiting for the server response. The target component renders right away with shared props, while page-specific props load in the background.
355
+
356
+ @verbatim
357
+ @boostsnippet("Instant Visit with Link", "svelte")
358
+ <script>
359
+ import { Link, inertia } from '@inertiajs/svelte'
360
+ </script>
361
+
362
+ <Link href="/dashboard" component="Dashboard">Dashboard</Link>
363
+
364
+ <a href="/dashboard" use:inertia={{ component: 'Dashboard' }}>Dashboard</a>
365
+
366
+ <Link
367
+ href="/posts/1"
368
+ component="Posts/Show"
369
+ pageProps={{ post: { id: 1, title: 'My Post' } }}
370
+ >
371
+ View Post
372
+ </Link>
373
+ @endboostsnippet
374
+ @endverbatim
375
+
376
+ ### Layout Props
377
+
378
+ Share dynamic data between pages and persistent layouts:
379
+
380
+ @boostsnippet("Layout Props in Layout", "svelte")
381
+ <script>
382
+ import { useLayoutProps } from '@inertiajs/svelte'
383
+
384
+ const layout = useLayoutProps({
385
+ title: 'My App',
386
+ showSidebar: true,
387
+ })
388
+
389
+ let { children } = $props()
390
+ </script>
391
+
392
+ <header>{layout.title}</header>
393
+ {#if layout.showSidebar}
394
+ <aside>Sidebar</aside>
395
+ {/if}
396
+ <main>
397
+ {@render children()}
398
+ </main>
399
+ @endboostsnippet
400
+
401
+ @boostsnippet("Setting Layout Props from Page", "svelte")
402
+ <script>
403
+ import { setLayoutProps } from '@inertiajs/svelte'
404
+
405
+ setLayoutProps({
406
+ title: 'Dashboard',
407
+ showSidebar: false,
408
+ })
409
+ </script>
410
+
411
+ <h1>Dashboard</h1>
412
+ @endboostsnippet
277
413
 
278
414
  ### Deferred Props
279
415
 
@@ -281,7 +417,7 @@ Use deferred props to load data after initial page render:
281
417
 
282
418
  @boostsnippet("Deferred Props with Empty State", "svelte")
283
419
  <script>
284
- export let users
420
+ let { users } = $props()
285
421
  </script>
286
422
 
287
423
  <div>
@@ -308,20 +444,16 @@ Automatically refresh data at intervals:
308
444
  @boostsnippet("Polling Example", "svelte")
309
445
  <script>
310
446
  import { router } from '@inertiajs/svelte'
311
- import { onMount, onDestroy } from 'svelte'
312
-
313
- export let stats
447
+ import { onMount } from 'svelte'
314
448
 
315
- let interval
449
+ let { stats } = $props()
316
450
 
317
451
  onMount(() => {
318
- interval = setInterval(() => {
452
+ const interval = setInterval(() => {
319
453
  router.reload({ only: ['stats'] })
320
- }, 5000) // Poll every 5 seconds
321
- })
454
+ }, 5000)
322
455
 
323
- onDestroy(() => {
324
- clearInterval(interval)
456
+ return () => clearInterval(interval)
325
457
  })
326
458
  </script>
327
459
 
@@ -339,22 +471,21 @@ Lazy-load a prop when an element scrolls into view. Useful for deferring expensi
339
471
  <script>
340
472
  import { WhenVisible } from '@inertiajs/svelte'
341
473
 
342
- export let stats
474
+ let { stats } = $props()
343
475
  </script>
344
476
 
345
477
  <div>
346
478
  <h1>Dashboard</h1>
347
479
 
348
- <!-- stats prop is loaded only when this section scrolls into view -->
349
480
  <WhenVisible data="stats" buffer={200}>
481
+ {#snippet fallback()}
482
+ <div class="animate-pulse">Loading stats...</div>
483
+ {/snippet}
484
+
350
485
  <div>
351
486
  <p>Total Users: {stats.total_users}</p>
352
487
  <p>Revenue: {stats.revenue}</p>
353
488
  </div>
354
-
355
- <svelte:fragment slot="fallback">
356
- <div class="animate-pulse">Loading stats...</div>
357
- </svelte:fragment>
358
489
  </WhenVisible>
359
490
  </div>
360
491
  @endboostsnippet
@@ -366,7 +497,10 @@ Server-side patterns (Inertia::render, props, middleware) are covered in inertia
366
497
  ## Common Pitfalls
367
498
 
368
499
  - Using traditional `<a>` links instead of Inertia's `<Link>` component (breaks SPA behavior)
500
+ - Using Svelte 4 syntax (`export let`, `on:click`, `$:`, `slot`) instead of Svelte 5 runes (`$props()`, `onclick`, `$derived()`, `{#snippet}`)
369
501
  - Forgetting to add loading states (skeleton screens) when using deferred props
370
502
  - Not handling the `undefined` state of deferred props before data loads
371
- - Using `<form>` without preventing default submission (use `<Form>` component or `on:submit|preventDefault`)
503
+ - Using `<form>` without preventing default submission (use `<Form>` component or call `e.preventDefault()` in the `onsubmit` handler)
372
504
  - Forgetting to check if `<Form>` component is available in your Inertia version
505
+ - Using `router.cancel()` instead of `router.cancelAll()` (v3 breaking change)
506
+ - Using `router.on('invalid', ...)` or `router.on('exception', ...)` instead of the renamed `httpException` and `networkError` events