@orieljs/framework 0.1.0-alpha.1 → 0.1.0-alpha.11

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 (88) hide show
  1. package/NOTICE +7 -0
  2. package/README.md +10 -1
  3. package/TRADEMARKS.md +33 -0
  4. package/dist/{chunk-WI7E7Q46.js → chunk-24673JQF.js} +8 -4
  5. package/dist/chunk-24673JQF.js.map +1 -0
  6. package/dist/{chunk-K6MAA3RM.js → chunk-HTJKT2R2.js} +9 -1
  7. package/dist/chunk-HTJKT2R2.js.map +1 -0
  8. package/dist/chunk-KDE5BLI3.js +8776 -0
  9. package/dist/chunk-KDE5BLI3.js.map +1 -0
  10. package/dist/chunk-MURTCTRH.js +3253 -0
  11. package/dist/chunk-MURTCTRH.js.map +1 -0
  12. package/dist/contracts.d.ts +852 -168
  13. package/dist/contracts.js +9 -1
  14. package/dist/index.d-WQpfdIbG.d.ts +630 -0
  15. package/dist/index.d.ts +3015 -5160
  16. package/dist/index.js +7628 -2640
  17. package/dist/index.js.map +1 -1
  18. package/dist/{jsx-runtime-B8J6xq-7.d.ts → jsx-runtime-6um4Mwfb.d.ts} +99 -149
  19. package/dist/jsx-runtime.d.ts +3 -1
  20. package/dist/jsx-runtime.js +3 -3
  21. package/dist/testing-CKwfWJGn.d.ts +1586 -0
  22. package/dist/testing.d.ts +3 -1408
  23. package/dist/testing.js +29 -5
  24. package/docs/authentication.md +487 -0
  25. package/docs/authorization.md +435 -0
  26. package/docs/cache.md +647 -0
  27. package/docs/collections.md +2270 -0
  28. package/docs/configuration.md +334 -0
  29. package/docs/console.md +654 -0
  30. package/docs/container.md +430 -0
  31. package/docs/controllers.md +406 -0
  32. package/docs/csrf.md +126 -0
  33. package/docs/database-testing.md +144 -0
  34. package/docs/database.md +236 -0
  35. package/docs/deployment.md +371 -0
  36. package/docs/eloquent-collections.md +218 -0
  37. package/docs/eloquent-factories.md +255 -0
  38. package/docs/eloquent-mutators.md +254 -0
  39. package/docs/eloquent-observers.md +145 -0
  40. package/docs/eloquent-relationships.md +557 -0
  41. package/docs/eloquent.md +494 -0
  42. package/docs/encryption.md +104 -0
  43. package/docs/errors.md +318 -0
  44. package/docs/events.md +336 -0
  45. package/docs/export.md +93 -0
  46. package/docs/facades.md +278 -0
  47. package/docs/hashing.md +118 -0
  48. package/docs/helpers.md +1281 -0
  49. package/docs/http-tests.md +386 -0
  50. package/docs/inertia.md +485 -0
  51. package/docs/installation.md +219 -0
  52. package/docs/lifecycle.md +234 -0
  53. package/docs/live-tests.md +226 -0
  54. package/docs/live.md +910 -0
  55. package/docs/logging.md +255 -0
  56. package/docs/mail.md +619 -0
  57. package/docs/middleware.md +385 -0
  58. package/docs/migrations.md +416 -0
  59. package/docs/mocking.md +264 -0
  60. package/docs/navigation.json +277 -0
  61. package/docs/notifications.md +225 -0
  62. package/docs/pagination.md +247 -0
  63. package/docs/passwords.md +207 -0
  64. package/docs/providers.md +187 -0
  65. package/docs/queries.md +600 -0
  66. package/docs/queues.md +330 -0
  67. package/docs/requests.md +533 -0
  68. package/docs/responses.md +444 -0
  69. package/docs/routing.md +738 -0
  70. package/docs/scheduling.md +277 -0
  71. package/docs/seeding.md +122 -0
  72. package/docs/session.md +409 -0
  73. package/docs/starter-kits.md +399 -0
  74. package/docs/storage.md +192 -0
  75. package/docs/structure.md +264 -0
  76. package/docs/testing.md +165 -0
  77. package/docs/time-travel.md +138 -0
  78. package/docs/validation.md +1792 -0
  79. package/docs/verification.md +152 -0
  80. package/docs/views.md +566 -0
  81. package/docs/vite.md +400 -0
  82. package/package.json +37 -30
  83. package/dist/chunk-AI2RFMBT.js +0 -745
  84. package/dist/chunk-AI2RFMBT.js.map +0 -1
  85. package/dist/chunk-BZPCRKFQ.js +0 -2941
  86. package/dist/chunk-BZPCRKFQ.js.map +0 -1
  87. package/dist/chunk-K6MAA3RM.js.map +0 -1
  88. package/dist/chunk-WI7E7Q46.js.map +0 -1
@@ -0,0 +1,485 @@
1
+ # Inertia
2
+
3
+ Inertia lets you write your pages as React, Vue, Svelte, Solid or Angular components, while your routes and controllers
4
+ stay on the server, with no API to build in between.
5
+
6
+ ## Introduction
7
+
8
+ An Inertia page is a component in `resources/js/pages`. Your controller returns the page's name and its props; Inertia
9
+ renders the component with them, in the browser on later visits and, with server-side rendering, on the server for the
10
+ first one. Links and forms make requests as a single-page app does, and the server answers with the next page's props as
11
+ JSON.
12
+
13
+ Oriel follows `inertia-laravel` on the server and speaks the protocol of the official Inertia clients. Its adapters,
14
+ one per framework, add typed pages and Oriel's `route()`:
15
+
16
+ | Framework | Package | Starter kit |
17
+ | --- | --- | --- |
18
+ | React | `@orieljs/inertia-react` | `--inertia=react` |
19
+ | Vue | `@orieljs/inertia-vue` | `--inertia=vue` |
20
+ | Svelte | `@orieljs/inertia-svelte` | `--inertia=svelte` |
21
+ | Solid | `@orieljs/inertia-solid` | `--inertia=solid` |
22
+ | Angular | `@orieljs/inertia-angular` | `--inertia=angular` |
23
+
24
+ The React, Vue and Svelte adapters are the official Inertia adapters, re-exported with Oriel's additions. The Solid
25
+ and Angular adapters are Oriel's own, with the same API in each framework's idiom. A new app from an Inertia
26
+ [starter kit](starter-kits.md) has everything below set up.
27
+
28
+ ## Server-side setup
29
+
30
+ ### Creating responses
31
+
32
+ An action returns a page with `inertia()`, its component's name and its props:
33
+
34
+ ```ts title="app/Http/Controllers/PostController.ts"
35
+ import { inertia } from '@orieljs/framework'
36
+ import { Post } from '../../Models/Post'
37
+ import { Controller } from './Controller'
38
+
39
+ export class PostController extends Controller {
40
+ /** Show the blog's posts. */
41
+ async index() {
42
+ return inertia('Posts/Index', {
43
+ posts: await Post.latest().get(),
44
+ })
45
+ }
46
+ }
47
+ ```
48
+
49
+ The component's name is its path under `resources/js/pages`, without the extension: `Posts/Index` is
50
+ `resources/js/pages/Posts/Index.tsx` (or `.vue`, `.svelte`, `.component.ts`). Models in the props reach the page as
51
+ their JSON, so their `hidden` attributes stay on the server.
52
+
53
+ A page with no data of its own needs no controller: `Route.inertia()` routes a URI to it.
54
+
55
+ ```ts title="routes/web.ts"
56
+ import { Route } from '@orieljs/framework'
57
+
58
+ Route.inertia('/about', 'About').name('about')
59
+ ```
60
+
61
+ ### The root view
62
+
63
+ The first visit loads the root view, `resources/views/app.tsx`, which writes the HTML document around the page. It
64
+ places the page's data with `inertiaBody(page)`, the server-rendered head with `inertiaHead(page)`, and your scripts and
65
+ styles with [`vite()`](vite.md). A React app adds `viteReactRefresh()` before `vite()`:
66
+
67
+ ```tsx title="resources/views/app.tsx"
68
+ import { inertiaBody, inertiaHead, vite, viteReactRefresh } from '@orieljs/framework'
69
+ import type { InertiaPage } from '@orieljs/framework/contracts'
70
+
71
+ export default function App({ page }: { page: InertiaPage }) {
72
+ return (
73
+ <html lang="en">
74
+ <head>
75
+ <meta charset="utf-8" />
76
+ <meta name="viewport" content="width=device-width, initial-scale=1" />
77
+ {inertiaHead(page)}
78
+ {viteReactRefresh()}
79
+ {vite(['resources/css/app.css', 'resources/js/app.tsx'])}
80
+ </head>
81
+ <body>{inertiaBody(page)}</body>
82
+ </html>
83
+ )
84
+ }
85
+ ```
86
+
87
+ ### The middleware
88
+
89
+ `HandleInertiaRequests`, in `app/Http/Middleware`, extends `InertiaMiddleware`, and `bootstrap/app.ts` appends it to the
90
+ `web` group. It answers in the Inertia protocol: a JSON page on Inertia requests, a 409 when your assets changed since
91
+ the page was loaded, and a 303 after a `PUT`, `PATCH` or `DELETE` that redirects. `node oriel inertia:middleware` writes
92
+ one for an app that hasn't.
93
+
94
+ Its `share()` gives every page some props, merged with the validation errors it shares itself:
95
+
96
+ ```ts title="app/Http/Middleware/HandleInertiaRequests.ts"
97
+ import { Auth, config, InertiaMiddleware } from '@orieljs/framework'
98
+ import type { HttpRequest } from '@orieljs/framework/contracts'
99
+ import type { User } from '../../Models/User'
100
+
101
+ export class HandleInertiaRequests extends InertiaMiddleware {
102
+ /** The root view loaded on the first page visit: resources/views/app.tsx. */
103
+ protected override rootViewName = 'app'
104
+
105
+ /** Props shared with every page. */
106
+ override async share(request: HttpRequest) {
107
+ const user = await Auth.user<User>()
108
+
109
+ return {
110
+ ...(await super.share(request)),
111
+ name: String(config('app.name')),
112
+ auth: { user: user === undefined ? null : { id: user.id, name: user.name, email: user.email } },
113
+ }
114
+ }
115
+ }
116
+ ```
117
+
118
+ `version()` returns your assets' version: a hash of `app.asset_url` when you set one, else of
119
+ `public/build/manifest.json`. A page loaded with an older version is reloaded in full on its next visit.
120
+
121
+ ### Shared data
122
+
123
+ Besides the middleware's `share()`, `Inertia.share()` shares props from anywhere in a request, such as a controller:
124
+
125
+ ```ts
126
+ Inertia.share('appName', 'Blog')
127
+ ```
128
+
129
+ `Inertia.flash()` stores data for the next page only, which reads it as `page.flash`, as session flash data works.
130
+
131
+ ### Redirects
132
+
133
+ Redirect as anywhere else in your app: `redirect().route('posts.index')`, or `back()`. Inertia follows the redirect
134
+ and shows the next page. To leave the app, to an external site or a page that isn't Inertia's, `Inertia.location()`
135
+ makes the browser load the URL in full:
136
+
137
+ ```ts
138
+ Route.get('/billing', () => Inertia.location('https://example.com/billing'))
139
+ ```
140
+
141
+ ### Props: deferred, optional and merged
142
+
143
+ A prop can be a function, which runs only when the prop is sent. These helpers choose when that is:
144
+
145
+ | Helper | The prop is sent |
146
+ | --- | --- |
147
+ | `Inertia.defer(fn, group?)` | In a request the page makes right after it renders, so the page shows sooner; props of one group come together |
148
+ | `Inertia.optional(fn)` | Only in a partial reload that asks for it |
149
+ | `Inertia.always(value)` | In every response, even a partial reload that didn't ask for it |
150
+ | `Inertia.merge(value)`, `Inertia.deepMerge(value)` | And merged into the page's current value on a reload, such as the next page of a list |
151
+ | `Inertia.once(fn)` | Once: the client keeps it for the next pages |
152
+ | `Inertia.scroll(paginator)` | As a page of an infinite scroll, with its page numbers |
153
+
154
+ ```ts title="app/Http/Controllers/DashboardController.ts"
155
+ import { Inertia, inertia } from '@orieljs/framework'
156
+ import { Post } from '../../Models/Post'
157
+ import { Controller } from './Controller'
158
+
159
+ export class DashboardController extends Controller {
160
+ async index() {
161
+ return inertia('Dashboard', {
162
+ latest: await Post.latest().limit(5).get(),
163
+ drafts: Inertia.defer(() => Post.whereNull('published_at').get()),
164
+ stats: Inertia.optional(() => Post.count()),
165
+ })
166
+ }
167
+ }
168
+ ```
169
+
170
+ A partial reload asks the server for some props only, such as `router.reload({ only: ['stats'] })` in the page.
171
+
172
+ ## Client-side setup
173
+
174
+ The browser entry, `resources/js/app.tsx` (`app.ts` in the Vue, Svelte and Angular kits), starts Inertia with your
175
+ pages' folder:
176
+
177
+ ```tsx title="resources/js/app.tsx"
178
+ import { createInertiaApp } from '@orieljs/inertia-react'
179
+
180
+ const appName = import.meta.env.VITE_APP_NAME || 'Oriel'
181
+
182
+ createInertiaApp({
183
+ pages: './pages',
184
+ title: (title) => (title ? `${title} – ${appName}` : appName),
185
+ })
186
+ ```
187
+
188
+ `title` formats every page's title. The `oriel()` [Vite](vite.md) plugin sets up each framework's own Vite plugin.
189
+
190
+ ## Pages
191
+
192
+ A page component receives its props. They are typed: `node oriel inertia:types` reads the `inertia()` calls of your
193
+ controllers and routes and writes `resources/js/oriel/pages.d.ts`, so each page knows its props, and the props your
194
+ middleware shares. The kits' Vite plugin runs it when Vite starts, and again when your server code changes.
195
+
196
+ ```tsx tab="React" title="resources/js/pages/Posts/Index.tsx"
197
+ import { Head, Link, route, type PageProps } from '@orieljs/inertia-react'
198
+
199
+ export default function Index({ posts }: PageProps<'Posts/Index'>) {
200
+ return (
201
+ <>
202
+ <Head title="Posts" />
203
+ <ul>
204
+ {posts.map((post) => (
205
+ <li key={post.id}>
206
+ <Link href={route('posts.show', post.slug)}>{post.title}</Link>
207
+ </li>
208
+ ))}
209
+ </ul>
210
+ </>
211
+ )
212
+ }
213
+ ```
214
+ ```vue tab="Vue" title="resources/js/pages/Posts/Index.vue"
215
+ <script setup lang="ts">
216
+ import { Head, Link, route, usePage } from '@orieljs/inertia-vue'
217
+
218
+ const page = usePage<'Posts/Index'>()
219
+ </script>
220
+
221
+ <template>
222
+ <Head title="Posts" />
223
+ <ul>
224
+ <li v-for="post in page.props.posts" :key="post.id">
225
+ <Link :href="route('posts.show', post.slug)">{{ post.title }}</Link>
226
+ </li>
227
+ </ul>
228
+ </template>
229
+ ```
230
+ ```svelte tab="Svelte" title="resources/js/pages/Posts/Index.svelte"
231
+ <script lang="ts">
232
+ import { Link, route, type PageProps } from '@orieljs/inertia-svelte'
233
+
234
+ let { posts }: PageProps<'Posts/Index'> = $props()
235
+ </script>
236
+
237
+ <svelte:head>
238
+ <title>Posts</title>
239
+ </svelte:head>
240
+
241
+ <ul>
242
+ {#each posts as post (post.id)}
243
+ <li><Link href={route('posts.show', post.slug)}>{post.title}</Link></li>
244
+ {/each}
245
+ </ul>
246
+ ```
247
+ ```tsx tab="Solid" title="resources/js/pages/Posts/Index.tsx"
248
+ import { Head, Link, route, type PageProps } from '@orieljs/inertia-solid'
249
+ import { For } from 'solid-js'
250
+
251
+ export default function Index(props: PageProps<'Posts/Index'>) {
252
+ return (
253
+ <>
254
+ <Head title="Posts" />
255
+ <ul>
256
+ <For each={props.posts}>
257
+ {(post) => (
258
+ <li>
259
+ <Link href={route('posts.show', post.slug)}>{post.title}</Link>
260
+ </li>
261
+ )}
262
+ </For>
263
+ </ul>
264
+ </>
265
+ )
266
+ }
267
+ ```
268
+ ```ts tab="Angular" title="resources/js/pages/Posts/Index.component.ts"
269
+ import { Component, input } from '@angular/core'
270
+ import { Head, Link, route } from '@orieljs/inertia-angular'
271
+
272
+ @Component({
273
+ selector: 'posts-index',
274
+ imports: [Head, Link],
275
+ template: `
276
+ <o-head title="Posts" />
277
+ <ul>
278
+ @for (post of posts(); track post.id) {
279
+ <li><a oLink [href]="route('posts.show', post.slug)">{{ post.title }}</a></li>
280
+ }
281
+ </ul>
282
+ `,
283
+ })
284
+ export default class Index {
285
+ readonly posts = input.required<{ id: number; title: string; slug: string }[]>()
286
+
287
+ protected readonly route = route
288
+ }
289
+ ```
290
+
291
+ > [!NOTE]
292
+ > A Vue page reads its props with `usePage<'Posts/Index'>()`, typed by the page's name, where Inertia's own Vue docs
293
+ > declare them with `defineProps`: Vue's compiler can't resolve the generated page types in
294
+ > `defineProps<PageProps<'Posts/Index'>>()`. An Angular page receives each prop as a signal input. Svelte has no
295
+ > Inertia `Head` component: the kits use `<svelte:head>`.
296
+
297
+ ### Links and the route helper
298
+
299
+ `Link` (in Angular, the `oLink` directive on an `<a>`) visits a page without reloading the document. `route()` gives a
300
+ named route's URL, typed: once `node oriel route:types` has run, a wrong name or a missing parameter is a type error.
301
+ `isCurrentRoute('posts.*')` tells you whether the current page's route matches, for an active link. Both work during
302
+ server-side rendering, from the URL being rendered.
303
+
304
+ `router` makes visits from code: `router.visit(url)`, `router.post(url, data)`, `router.reload({ only: ['stats'] })`.
305
+
306
+ ## Forms
307
+
308
+ `useForm()` holds a form's data, sends it, and gives back the validation errors, one per field, and whether the
309
+ request is in flight:
310
+
311
+ ```tsx tab="React" title="resources/js/pages/Posts/Create.tsx"
312
+ import { route, useForm } from '@orieljs/inertia-react'
313
+ import type { FormEvent } from 'react'
314
+
315
+ export default function Create() {
316
+ const { data, setData, post, processing, errors } = useForm<{ title: string }>({ title: '' })
317
+
318
+ const submit = (event: FormEvent) => {
319
+ event.preventDefault()
320
+ post(route('posts.store'))
321
+ }
322
+
323
+ return (
324
+ <form onSubmit={submit}>
325
+ <input value={data.title} onChange={(event) => setData('title', event.target.value)} />
326
+ {errors.title && <p className="error">{errors.title}</p>}
327
+
328
+ <button type="submit" disabled={processing}>
329
+ Publish
330
+ </button>
331
+ </form>
332
+ )
333
+ }
334
+ ```
335
+ ```vue tab="Vue" title="resources/js/pages/Posts/Create.vue"
336
+ <script setup lang="ts">
337
+ import { route, useForm } from '@orieljs/inertia-vue'
338
+
339
+ const form = useForm({
340
+ title: '',
341
+ })
342
+
343
+ const submit = () => {
344
+ form.post(route('posts.store'))
345
+ }
346
+ </script>
347
+
348
+ <template>
349
+ <form @submit.prevent="submit">
350
+ <input v-model="form.title" />
351
+ <p v-if="form.errors.title" class="error">{{ form.errors.title }}</p>
352
+
353
+ <button type="submit" :disabled="form.processing">Publish</button>
354
+ </form>
355
+ </template>
356
+ ```
357
+ ```svelte tab="Svelte" title="resources/js/pages/Posts/Create.svelte"
358
+ <script lang="ts">
359
+ import { route, useForm } from '@orieljs/inertia-svelte'
360
+
361
+ const form = useForm({
362
+ title: '',
363
+ })
364
+
365
+ function submit(event: SubmitEvent) {
366
+ event.preventDefault()
367
+ form.post(route('posts.store'))
368
+ }
369
+ </script>
370
+
371
+ <form onsubmit={submit}>
372
+ <input bind:value={form.title} />
373
+ {#if form.errors.title}
374
+ <p class="error">{form.errors.title}</p>
375
+ {/if}
376
+
377
+ <button type="submit" disabled={form.processing}>Publish</button>
378
+ </form>
379
+ ```
380
+ ```tsx tab="Solid" title="resources/js/pages/Posts/Create.tsx"
381
+ import { route, useForm } from '@orieljs/inertia-solid'
382
+ import { Show } from 'solid-js'
383
+
384
+ export default function Create() {
385
+ const form = useForm<{ title: string }>({ title: '' })
386
+
387
+ const submit = (event: SubmitEvent) => {
388
+ event.preventDefault()
389
+ form.post(route('posts.store'))
390
+ }
391
+
392
+ return (
393
+ <form onSubmit={submit}>
394
+ <input value={form.data.title} onInput={(event) => form.setData('title', event.currentTarget.value)} />
395
+ <Show when={form.errors.title}>
396
+ <p class="error">{form.errors.title}</p>
397
+ </Show>
398
+
399
+ <button type="submit" disabled={form.processing}>
400
+ Publish
401
+ </button>
402
+ </form>
403
+ )
404
+ }
405
+ ```
406
+ ```ts tab="Angular" title="resources/js/pages/Posts/Create.component.ts"
407
+ import { Component } from '@angular/core'
408
+ import { route, useForm } from '@orieljs/inertia-angular'
409
+
410
+ @Component({
411
+ selector: 'posts-create',
412
+ template: `
413
+ <form (submit)="submit($event)">
414
+ <input #title [value]="form.data.title" (input)="form.setData('title', title.value)" />
415
+ @if (form.errors.title) {
416
+ <p class="error">{{ form.errors.title }}</p>
417
+ }
418
+
419
+ <button type="submit" [disabled]="form.processing">Publish</button>
420
+ </form>
421
+ `,
422
+ })
423
+ export default class Create {
424
+ protected readonly form = useForm<{ title: string }>({ title: '' })
425
+
426
+ protected submit(event: SubmitEvent): void {
427
+ event.preventDefault()
428
+ this.form.post(route('posts.store'))
429
+ }
430
+ }
431
+ ```
432
+
433
+ A failed validation sends the user back to the page, with the errors (see
434
+ [Displaying errors in Inertia pages](validation.md#displaying-errors-in-inertia-pages)). Each adapter also has a `Form`
435
+ component (Angular's `form[oForm]`), which sends a plain HTML form's fields without `useForm()`.
436
+
437
+ ### Precognition
438
+
439
+ On a route with the `precognitive` middleware, the React, Vue and Svelte forms can validate a field while the user fills
440
+ it in: give `useForm()` the method and the URL first, `useForm('post', route('posts.store'), { title: '' })`, then call
441
+ `form.validate('title')`; `Form` has a `validate()` too. The Solid and Angular forms don't offer Precognition yet.
442
+ [Precognition](validation.md#precognition) describes the server side, and why the action validates first.
443
+
444
+ ## More Inertia features
445
+
446
+ Each adapter has Inertia's components and hooks for the props above, under Inertia's names (Angular's are directives):
447
+
448
+ | Feature | React, Vue, Svelte, Solid | Angular |
449
+ | --- | --- | --- |
450
+ | Deferred props, with a fallback while they load | `Deferred` | `oDeferred` directive |
451
+ | Loading props when an element scrolls into view | `WhenVisible` | `oWhenVisible` directive |
452
+ | Infinite scrolling of an `Inertia.scroll()` prop | `InfiniteScroll` | `<o-infinite-scroll>` |
453
+ | Reloading props on an interval | `usePoll()` | `usePoll()` |
454
+ | Prefetching pages before a click | `usePrefetch()`, `prefetch` on `Link` | `usePrefetch()`, `prefetch` on `oLink` |
455
+ | Keeping local state in the browser's history | `useRemember()` | `useRemember()` |
456
+ | The page's title and meta tags | `Head` (Svelte: `<svelte:head>`) | `<o-head>` |
457
+
458
+ They work as Inertia's documentation describes, at [inertiajs.com](https://inertiajs.com).
459
+
460
+ ## Server-side rendering
461
+
462
+ The React, Vue, Svelte and Solid kits render the first visit on the server, so it arrives as HTML. `npm run build`
463
+ builds the server bundle from `resources/js/ssr.*` into `bootstrap/ssr`, and `node oriel inertia:start-ssr` runs it;
464
+ when no SSR server answers, the pages are rendered in the browser. [Inertia SSR](starter-kits.md#inertia-ssr) and
465
+ [The SSR server](deployment.md#the-ssr-server) cover running it. `inertia.ssr.enabled`, from `INERTIA_SSR_ENABLED`,
466
+ turns it off.
467
+
468
+ ## History encryption
469
+
470
+ Inertia keeps each page's props in the browser's history, so the back button can show them again. To keep a signed-in
471
+ user's data out of it after they sign out, encrypt it: `INERTIA_ENCRYPT_HISTORY=true` encrypts every page's state, and
472
+ the `inertia.encrypt` middleware does it for some routes. `Inertia.clearHistory()` clears the encrypted history, such
473
+ as when a user signs out.
474
+
475
+ ## Adapter differences
476
+
477
+ | | React, Vue, Svelte | Solid | Angular |
478
+ | --- | --- | --- | --- |
479
+ | Server-side rendering | Yes | Yes | No |
480
+ | Precognition | Yes | No | No |
481
+ | `useHttp()` | Yes | No | No |
482
+
483
+ The Angular adapter's other limits are in its package's README: track a form's array rows by `$index` or a stable id,
484
+ not by the item, since `useForm` copies the objects along a dotted path on each write; and once a page uses `<o-head>`,
485
+ a later `Title.setTitle()` is overwritten.