vue-ssr-lite 0.1.7 → 0.1.9

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.
package/README.md CHANGED
@@ -1,130 +1,942 @@
1
1
  # vue-ssr-lite
2
2
 
3
- An API-client-neutral managed Vue 3 SSR runtime. It owns server creation, host
4
- classification, isolated Vue applications and routers, rendering, generic
5
- plugin hydration state, browser hydration, assets, errors, timeouts, metrics,
6
- and request cleanup.
3
+ A simple, production-ready SSR runtime for Vue 3 and Vite.
7
4
 
8
- It does not import or understand Apollo, GraphQL, REST clients, authentication,
9
- or application queries.
5
+ Use it to run:
10
6
 
11
- ## Minimal application
7
+ - A server-rendered Vue website
8
+ - A normal Vue SPA
9
+ - Multiple SSR applications
10
+ - SPA and SSR applications together
11
+ - Applications on different domains or subdomains
12
+
13
+ It includes routing, browser hydration, production builds, a managed Node server, health checks, custom endpoints, caching, timeouts, and Vue plugin support.
14
+
15
+ ## Features
16
+
17
+ - Vue 3 server-side rendering
18
+ - Normal Vue SPA support
19
+ - SPA and SSR in one project
20
+ - Vue Router support
21
+ - Automatic browser hydration
22
+ - Multiple applications and HTML entries
23
+ - Domain and subdomain routing
24
+ - Runtime roles for one image / many process modes
25
+ - Apollo, GraphQL, REST, Pinia, and i18n support
26
+ - SEO metadata and status codes
27
+ - Custom server endpoints
28
+ - Health and readiness checks
29
+ - Request timeouts
30
+ - Optional response caching
31
+ - Cookie allowlists and denylists
32
+ - Proxy-aware host and protocol resolution
33
+ - Graceful shutdown
34
+ - TypeScript support
35
+
36
+ ## Package Entry Points
37
+
38
+ | Import | Purpose |
39
+ | --- | --- |
40
+ | `vue-ssr-lite` | `defineSsrApplication`, `defineSsrRuntime`, request context, hydration helpers |
41
+ | `vue-ssr-lite/client` | Browser hydration only |
42
+ | `vue-ssr-lite/server` | Managed Node server, host matching, cookies, response cache |
43
+ | `vue-ssr-lite/vite` | Vite plugin that wires HTML templates and virtual client entries |
44
+
45
+ CLI binary:
46
+
47
+ ```bash
48
+ vue-ssr-lite <dev|build|start> [--root .] [--runtime src/SsrRuntime.ts] [--server-output dist/server/SsrRuntime.js] [--hmr-port 31001]
49
+ ```
50
+
51
+ Requires Node.js 20 or newer.
52
+
53
+ Development servers use an operating-system-assigned HMR WebSocket port, so
54
+ multiple `vue-ssr-lite dev` processes can run concurrently. Use `--hmr-port`
55
+ or `VUE_SSR_LITE_HMR_PORT` when a proxy or container requires a fixed port.
56
+
57
+ ## Installation
58
+
59
+ ```bash
60
+ npm install vue-ssr-lite vue vue-router @vue/server-renderer
61
+ npm install --save-dev vite @vitejs/plugin-vue
62
+ ```
63
+
64
+ Using Yarn:
65
+
66
+ ```bash
67
+ yarn add vue-ssr-lite vue vue-router @vue/server-renderer
68
+ yarn add --dev vite @vitejs/plugin-vue
69
+ ```
70
+
71
+ ## Choose Your Application Type
72
+
73
+ `vue-ssr-lite` supports two application types.
74
+
75
+ ### SPA
76
+
77
+ Use `kind: 'spa'` for a normal Vue application that runs in the browser.
78
+
79
+ Examples:
80
+
81
+ - Admin dashboard
82
+ - Internal application
83
+ - Editor
84
+ - Authenticated application
85
+ - Client-side portal
86
+
87
+ A SPA keeps its normal Vite `main.ts` entry.
88
+
89
+ ### SSR
90
+
91
+ Use `kind: 'ssr'` for a Vue application that should render on the server and hydrate in the browser.
92
+
93
+ Examples:
94
+
95
+ - Public website
96
+ - Storefront
97
+ - Blog
98
+ - Marketing website
99
+ - Documentation website
100
+ - SEO-focused application
101
+
102
+ An SSR application is defined with `defineSsrApplication()`.
103
+
104
+ ## Quick Start: SPA and SSR Together
105
+
106
+ The following example creates:
107
+
108
+ - A dashboard SPA on `app.example.com`
109
+ - A server-rendered website on all other hosts
110
+
111
+ ## 1. Create the SPA Application
112
+
113
+ Create a normal Vue entry:
114
+
115
+ ```ts
116
+ // src/spa/main.ts
117
+ import { createApp } from 'vue'
118
+ import App from './App.vue'
119
+ import router from './router'
120
+
121
+ const app = createApp(App)
122
+
123
+ app.use(router)
124
+ app.mount('#app')
125
+ ```
126
+
127
+ Create the SPA root component:
128
+
129
+ ```vue
130
+ <!-- src/spa/App.vue -->
131
+ <script setup lang="ts">
132
+ import { RouterView } from 'vue-router'
133
+ </script>
134
+
135
+ <template>
136
+ <RouterView />
137
+ </template>
138
+ ```
139
+
140
+ Create the SPA HTML entry:
141
+
142
+ ```html
143
+ <!-- index.html -->
144
+ <!doctype html>
145
+ <html lang="en">
146
+ <head>
147
+ <meta charset="UTF-8" />
148
+ <meta
149
+ name="viewport"
150
+ content="width=device-width, initial-scale=1" />
151
+ <title>Dashboard</title>
152
+ </head>
153
+
154
+ <body>
155
+ <div id="app"></div>
156
+
157
+ <script
158
+ type="module"
159
+ src="/src/spa/main.ts"></script>
160
+ </body>
161
+ </html>
162
+ ```
163
+
164
+ This remains a normal Vue and Vite SPA. No special SPA application definition is required.
165
+
166
+ ## 2. Create the SSR Application
167
+
168
+ Create the SSR root component:
169
+
170
+ ```vue
171
+ <!-- src/website/App.vue -->
172
+ <script setup lang="ts">
173
+ import { RouterView } from 'vue-router'
174
+ </script>
175
+
176
+ <template>
177
+ <RouterView />
178
+ </template>
179
+ ```
180
+
181
+ Create the SSR routes:
182
+
183
+ ```ts
184
+ // src/website/routes.ts
185
+ import type { RouteRecordRaw } from 'vue-router'
186
+
187
+ import HomePage from './pages/HomePage.vue'
188
+ import AboutPage from './pages/AboutPage.vue'
189
+ import NotFoundPage from './pages/NotFoundPage.vue'
190
+
191
+ export const websiteRoutes: RouteRecordRaw[] = [
192
+ {
193
+ path: '/',
194
+ component: HomePage,
195
+ },
196
+ {
197
+ path: '/about',
198
+ component: AboutPage,
199
+ },
200
+ {
201
+ path: '/:pathMatch(.*)*',
202
+ component: NotFoundPage,
203
+ },
204
+ ]
205
+ ```
206
+
207
+ Define the SSR application:
12
208
 
13
209
  ```ts
210
+ // src/website/SsrApplication.ts
14
211
  import { defineSsrApplication } from 'vue-ssr-lite'
15
- import apolloConfiguration from './config/ApolloConfiguration'
16
- import StorefrontApp from './StorefrontApp.vue'
17
- import { storefrontRoutes } from './routes'
18
-
19
- export const storefrontApplication = defineSsrApplication({
20
- id: 'storefront',
21
- rootComponent: StorefrontApp,
22
- routes: storefrontRoutes,
23
- plugins: [apolloConfiguration],
212
+
213
+ import App from './App.vue'
214
+ import { websiteRoutes } from './routes'
215
+
216
+ export const websiteApplication = defineSsrApplication({
217
+ id: 'website',
218
+ rootComponent: App,
219
+ routes: websiteRoutes,
24
220
  })
25
221
  ```
26
222
 
27
- `plugins` contains ordinary Vue plugins. `vue-ssr-lite` provides the generic
28
- request and hydration contexts before installing them, so each plugin can
29
- derive request scope and contribute serializable state without a framework
30
- adapter or application `install` callback.
223
+ Create its HTML template:
224
+
225
+ ```html
226
+ <!-- site.html -->
227
+ <!doctype html>
228
+ <html lang="en">
229
+ <head>
230
+ <meta charset="UTF-8" />
231
+ <meta
232
+ name="viewport"
233
+ content="width=device-width, initial-scale=1" />
234
+ </head>
235
+
236
+ <body>
237
+ <div id="app"></div>
238
+ </body>
239
+ </html>
240
+ ```
241
+
242
+ The SSR browser entry and hydration setup are added automatically.
243
+
244
+ ## 3. Create the Runtime
31
245
 
32
- Declare SPA/SSR host routing and minimal public configuration:
246
+ The runtime tells the package which application should handle each host.
33
247
 
34
248
  ```ts
249
+ // src/SsrRuntime.ts
35
250
  import { defineSsrRuntime } from 'vue-ssr-lite'
36
- import { storefrontApplication } from './StorefrontApplication'
251
+
252
+ import { websiteApplication } from './website/SsrApplication'
37
253
 
38
254
  export default defineSsrRuntime({
39
- name: 'unified-app',
40
- entries: [
41
- {
42
- id: 'admin',
43
- kind: 'spa',
44
- template: 'index.html',
45
- hosts: ['admin.example.com'],
46
- },
47
- {
48
- id: 'storefront',
49
- kind: 'ssr',
50
- template: 'site.html',
51
- hosts: ['*'],
52
- application: storefrontApplication,
53
- },
54
- ],
55
- server: {
56
- publicConfig: { shopBaseDomain: 'shop.example.com' },
57
- },
255
+ name: 'my-platform',
256
+
257
+ entries: [
258
+ {
259
+ id: 'dashboard',
260
+ kind: 'spa',
261
+ template: 'index.html',
262
+ hosts: ['app.example.com', 'localhost'],
263
+ },
264
+ {
265
+ id: 'website',
266
+ kind: 'ssr',
267
+ template: 'site.html',
268
+ hosts: ['*'],
269
+ application: websiteApplication,
270
+ },
271
+ ],
272
+
273
+ defaultEntryId: 'website',
274
+
275
+ server: {
276
+ host: '0.0.0.0',
277
+ port: Number(process.env.PORT || 4173),
278
+ role: process.env.APP_RUNTIME || 'unified',
279
+
280
+ publicConfig: {
281
+ apiUrl: process.env.PUBLIC_API_URL || 'http://localhost:4000',
282
+ },
283
+ },
58
284
  })
59
285
  ```
60
286
 
61
- Register the SSR browser entry in Vite:
287
+ The specific SPA hostname is checked first. The `*` SSR entry handles all remaining hosts.
288
+ `defaultEntryId` is used when no host pattern matches.
289
+
290
+ When the server starts, the console prints a ready message with a clickable local URL and the active role:
291
+
292
+ ```text
293
+ ✓ Server Ready
294
+
295
+ ➜ Local: http://localhost:4173/
296
+ ➜ Role: unified
297
+ ```
298
+
299
+ ## 4. Configure Vite
300
+
301
+ Register the SSR application and the normal SPA HTML entry:
62
302
 
63
303
  ```ts
304
+ // vite.config.ts
305
+ import { defineConfig } from 'vite'
306
+ import vue from '@vitejs/plugin-vue'
307
+
64
308
  import { vueSsrLite } from 'vue-ssr-lite/vite'
65
309
 
66
- export default {
67
- plugins: [
68
- vueSsrLite({
69
- applications: [{
70
- id: 'storefront',
71
- definition: 'src/StorefrontApplication.ts',
72
- exportName: 'storefrontApplication',
73
- template: 'site.html',
74
- }],
75
- spaEntries: { admin: 'index.html' },
76
- }),
77
- ],
310
+ export default defineConfig({
311
+ plugins: [
312
+ vue(),
313
+
314
+ vueSsrLite({
315
+ applications: [
316
+ {
317
+ id: 'website',
318
+ definition: 'src/website/SsrApplication.ts',
319
+ exportName: 'websiteApplication',
320
+ template: 'site.html',
321
+ },
322
+ ],
323
+
324
+ spaEntries: {
325
+ dashboard: 'index.html',
326
+ },
327
+ }),
328
+ ],
329
+ })
330
+ ```
331
+
332
+ ### `applications`
333
+
334
+ Contains applications that use server-side rendering and browser hydration.
335
+
336
+ ### `spaEntries`
337
+
338
+ Contains normal Vite SPA HTML entries.
339
+
340
+ Do not add SPA applications to the `applications` array.
341
+
342
+ ## 5. Add Commands
343
+
344
+ ```json
345
+ {
346
+ "scripts": {
347
+ "dev": "vue-ssr-lite dev --runtime src/SsrRuntime.ts",
348
+ "build": "vue-ssr-lite build --runtime src/SsrRuntime.ts",
349
+ "start": "vue-ssr-lite start"
350
+ }
78
351
  }
79
352
  ```
80
353
 
81
- The package Vite plugin owns Vue/Router deduplication and keeps
82
- `vue-ssr-lite` external so the managed server and consumer bundle share the
83
- same runtime. API-client Vite plugins own their own dependency identity.
354
+ ## 6. Start Development
355
+
356
+ ```bash
357
+ npm run dev
358
+ ```
359
+
360
+ Build and run production:
84
361
 
85
- ## Request lifecycle
362
+ ```bash
363
+ npm run build
364
+ npm run start
365
+ ```
86
366
 
87
- For every SSR request the package:
367
+ ## SSR-Only Application
88
368
 
89
- 1. classifies the normalized host and deployment role;
90
- 2. creates a new Vue app, memory router, state, head, response, abort signal,
91
- request context, and hydration controller;
92
- 3. provides globally stable request/hydration injection keys;
93
- 4. installs generic application plugins;
94
- 5. resolves the route and waits for native Vue server-prefetch work;
95
- 6. renders HTML and collects plugin hydration state;
96
- 7. serializes escaped state into the application document; and
97
- 8. disposes all request-owned plugin resources.
369
+ For a project that only needs SSR:
98
370
 
99
- Browser hydration restores the serialized application/plugin state before
100
- component setup and mounts through the same application definition.
371
+ ```ts
372
+ export default defineSsrRuntime({
373
+ name: 'my-website',
101
374
 
102
- ## Public package boundaries
375
+ entries: [
376
+ {
377
+ id: 'website',
378
+ kind: 'ssr',
379
+ template: 'site.html',
380
+ hosts: ['*'],
381
+ application: websiteApplication,
382
+ },
383
+ ],
103
384
 
104
- - `vue-ssr-lite`: isomorphic definitions, request context, hydration contract,
105
- serialization, and public types.
106
- - `vue-ssr-lite/client`: browser hydration only.
107
- - `vue-ssr-lite/server`: renderer, Node lifecycle, host/proxy/cookie handling,
108
- assets, readiness, response caching, and shutdown.
109
- - `vue-ssr-lite/vite`: HTML transformation, virtual browser entries, framework
110
- identity, and build inputs.
385
+ server: {
386
+ publicConfig: {},
387
+ },
388
+ })
389
+ ```
111
390
 
112
- Application code may define routes, generic plugins, typed state/extensions,
113
- head/status/redirect behavior, host entries, response caches, endpoints, and
114
- service readiness. It never calls `renderToString`, manages API-client caches,
115
- injects hydration HTML, starts the server, or performs request cleanup.
391
+ Vite configuration:
116
392
 
117
- ## Commands
393
+ ```ts
394
+ vueSsrLite({
395
+ applications: [
396
+ {
397
+ id: 'website',
398
+ definition: 'src/website/SsrApplication.ts',
399
+ exportName: 'websiteApplication',
400
+ template: 'site.html',
401
+ },
402
+ ],
403
+ })
404
+ ```
118
405
 
119
- ```json
406
+ ## SPA-Only Application
407
+
408
+ A runtime can also serve a normal SPA without an SSR application:
409
+
410
+ ```ts
411
+ export default defineSsrRuntime({
412
+ name: 'my-dashboard',
413
+
414
+ entries: [
415
+ {
416
+ id: 'dashboard',
417
+ kind: 'spa',
418
+ template: 'index.html',
419
+ hosts: ['*'],
420
+ },
421
+ ],
422
+
423
+ server: {
424
+ publicConfig: {},
425
+ },
426
+ })
427
+ ```
428
+
429
+ Vite configuration still requires at least one SSR application in the current `vueSsrLite()` API. For a completely SPA-only project, use normal Vite without the `vueSsrLite()` plugin.
430
+
431
+ ## Using the Same Vue Plugins
432
+
433
+ You may use the same plugin configuration in your SPA and SSR applications.
434
+
435
+ For example:
436
+
437
+ ```ts
438
+ // src/config/apollo.ts
439
+ import { defineApollo } from 'vue-apollo-client'
440
+
441
+ export default defineApollo(({ publicConfig }) => ({
442
+ endPoints: {
443
+ default:
444
+ publicConfig?.graphqlEndpoint ||
445
+ import.meta.env.VITE_GRAPHQL_ENDPOINT,
446
+ },
447
+ }))
448
+ ```
449
+
450
+ Use it in the SPA:
451
+
452
+ ```ts
453
+ // src/spa/main.ts
454
+ import { createApp } from 'vue'
455
+
456
+ import apollo from '../config/apollo'
457
+ import App from './App.vue'
458
+ import router from './router'
459
+
460
+ const app = createApp(App)
461
+
462
+ app.use(apollo)
463
+ app.use(router)
464
+ app.mount('#app')
465
+ ```
466
+
467
+ Use the same configuration in SSR:
468
+
469
+ ```ts
470
+ // src/website/SsrApplication.ts
471
+ import { defineSsrApplication } from 'vue-ssr-lite'
472
+
473
+ import apollo from '../config/apollo'
474
+ import App from './App.vue'
475
+ import { websiteRoutes } from './routes'
476
+
477
+ export const websiteApplication = defineSsrApplication({
478
+ id: 'website',
479
+ rootComponent: App,
480
+ routes: websiteRoutes,
481
+ plugins: [apollo],
482
+ })
483
+ ```
484
+
485
+ This allows one plugin configuration to support both application types.
486
+
487
+ You can use the same approach with:
488
+
489
+ - Apollo and GraphQL
490
+ - Pinia
491
+ - i18n
492
+ - Theme providers
493
+ - REST clients
494
+ - Authentication plugins
495
+ - Analytics plugins
496
+ - Custom Vue plugins
497
+
498
+ ## Using GraphQL in SSR
499
+
500
+ Keep GraphQL operations in `.graphql` files:
501
+
502
+ ```graphql
503
+ query GetPosts {
504
+ posts {
505
+ id
506
+ title
507
+ }
508
+ }
509
+ ```
510
+
511
+ Use the generated composable in the Vue page:
512
+
513
+ ```vue
514
+ <script setup lang="ts">
515
+ import { useGetPostsQuery } from '../graphql'
516
+
517
+ const { result, loading, error } = useGetPostsQuery(
518
+ {},
519
+ {
520
+ ssr: true,
521
+ fetchPolicy: 'cache-first',
522
+ },
523
+ )
524
+ </script>
525
+
526
+ <template>
527
+ <main>
528
+ <p v-if="loading">Loading...</p>
529
+
530
+ <p v-else-if="error">
531
+ {{ error.message }}
532
+ </p>
533
+
534
+ <article
535
+ v-for="post in result?.posts ?? []"
536
+ :key="post.id">
537
+ <h2>{{ post.title }}</h2>
538
+ </article>
539
+ </main>
540
+ </template>
541
+ ```
542
+
543
+ The same generated composable can also be used inside the SPA.
544
+
545
+ Use `ssr: true` for public data that should be included in server-rendered HTML.
546
+
547
+ Use `ssr: false` for browser-only or private queries.
548
+
549
+ ## Using REST APIs
550
+
551
+ REST APIs can be used through:
552
+
553
+ - Native `fetch`
554
+ - Axios
555
+ - Ky
556
+ - A custom Vue plugin
557
+ - Another SSR-compatible data package
558
+
559
+ Example:
560
+
561
+ ```vue
562
+ <script setup lang="ts">
563
+ import { onServerPrefetch, ref } from 'vue'
564
+
565
+ interface Post {
566
+ id: string
567
+ title: string
568
+ }
569
+
570
+ const posts = ref<Post[]>([])
571
+
572
+ const loadPosts = async () => {
573
+ const response = await fetch('https://api.example.com/posts')
574
+
575
+ posts.value = await response.json()
576
+ }
577
+
578
+ onServerPrefetch(loadPosts)
579
+ </script>
580
+
581
+ <template>
582
+ <article
583
+ v-for="post in posts"
584
+ :key="post.id">
585
+ <h2>{{ post.title }}</h2>
586
+ </article>
587
+ </template>
588
+ ```
589
+
590
+ For reusable REST caching and hydration, register an SSR-compatible Vue plugin in the `plugins` array.
591
+
592
+ ## Public Configuration
593
+
594
+ Pass public values from the runtime:
595
+
596
+ ```ts
597
+ server: {
598
+ publicConfig: {
599
+ apiUrl:
600
+ 'https://api.example.com',
601
+ graphqlEndpoint:
602
+ 'https://api.example.com/graphql',
603
+ },
604
+ }
605
+ ```
606
+
607
+ Access them in an SSR component:
608
+
609
+ ```ts
610
+ import { useSsrRequestContext } from 'vue-ssr-lite'
611
+
612
+ const context = useSsrRequestContext()
613
+
614
+ const apiUrl = context.publicConfig.apiUrl
615
+ ```
616
+
617
+ Only include values that are safe to expose to the browser.
618
+
619
+ ## SEO
620
+
621
+ Set page metadata from a component:
622
+
623
+ ```ts
624
+ import { useSsrRequestContext } from 'vue-ssr-lite'
625
+
626
+ const context = useSsrRequestContext()
627
+
628
+ context.head.value = {
629
+ title: 'Products',
630
+ description: 'Browse our latest products.',
631
+ robots: 'index, follow',
632
+ canonicalUrl: 'https://example.com/products',
633
+ ogTitle: 'Products',
634
+ ogDescription: 'Browse our latest products.',
635
+ ogImage: 'https://example.com/social.jpg',
636
+ twitterCard: 'summary_large_image',
637
+ }
638
+ ```
639
+
640
+ ## Status Codes
641
+
642
+ ```ts
643
+ const context = useSsrRequestContext()
644
+
645
+ context.response.statusCode = 404
646
+ ```
647
+
648
+ ## Redirects
649
+
650
+ ```ts
651
+ const context = useSsrRequestContext()
652
+
653
+ context.response.redirect = {
654
+ location: '/new-page',
655
+ statusCode: 307,
656
+ }
657
+ ```
658
+
659
+ ## Domain Routing
660
+
661
+ Exact hostname:
662
+
663
+ ```ts
664
+ hosts: ['example.com']
665
+ ```
666
+
667
+ Subdomains:
668
+
669
+ ```ts
670
+ hosts: ['*.example.com']
671
+ ```
672
+
673
+ Exact hostname and subdomains:
674
+
675
+ ```ts
676
+ hosts: ['example.com', '*.example.com']
677
+ ```
678
+
679
+ Catch-all:
680
+
681
+ ```ts
682
+ hosts: ['*']
683
+ ```
684
+
685
+ Do not include protocols or paths in host values.
686
+
687
+ Set `server.trustProxy` to `true` only when the Node process sits behind a trusted reverse proxy. Then `X-Forwarded-Host` and `X-Forwarded-Proto` are used for host matching and absolute URLs. Leave it `false` for direct local traffic.
688
+
689
+ ## Runtime Roles
690
+
691
+ Roles let one codebase and one Docker image run as different process modes.
692
+
693
+ - Set `server.role` to the active mode for this process.
694
+ - Set `roles` on each entry to list which modes may serve that entry.
695
+ - Omit `roles` (or omit `server.role`) to keep an entry available in every mode.
696
+
697
+ Role names are application-defined strings. Common patterns:
698
+
699
+ | Mode | Typical use |
700
+ | --- | --- |
701
+ | `unified` | Local development or a single process that serves every entry |
702
+ | A private role | SPA / admin / back-office only |
703
+ | A public role | SSR website / marketing / storefront only |
704
+
705
+ Example:
706
+
707
+ ```ts
708
+ export default defineSsrRuntime({
709
+ name: 'my-platform',
710
+
711
+ entries: [
712
+ {
713
+ id: 'admin',
714
+ kind: 'spa',
715
+ template: 'index.html',
716
+ hosts: ['app.example.com', 'localhost'],
717
+ roles: ['unified', 'admin'],
718
+ },
719
+ {
720
+ id: 'website',
721
+ kind: 'ssr',
722
+ template: 'site.html',
723
+ hosts: ['*'],
724
+ roles: ['unified', 'website'],
725
+ application: websiteApplication,
726
+ },
727
+ ],
728
+
729
+ server: {
730
+ role: process.env.APP_RUNTIME || 'unified',
731
+ publicConfig: {},
732
+ },
733
+ })
734
+ ```
735
+
736
+ With that setup:
737
+
738
+ - `APP_RUNTIME=unified` serves both entries (host routing still applies).
739
+ - `APP_RUNTIME=admin` serves only the SPA entry.
740
+ - `APP_RUNTIME=website` serves only the SSR entry.
741
+
742
+ If a host matches an entry that the current role does not allow, the server responds with `421`.
743
+
744
+ You can also load role-specific code only when needed:
745
+
746
+ ```ts
747
+ export default async () => {
748
+ const role = process.env.APP_RUNTIME || 'unified'
749
+ const websiteApplication =
750
+ role === 'admin'
751
+ ? undefined
752
+ : (await import('./website/SsrApplication')).websiteApplication
753
+
754
+ return defineSsrRuntime({
755
+ name: 'my-platform',
756
+ entries: [
757
+ {
758
+ id: 'admin',
759
+ kind: 'spa',
760
+ template: 'index.html',
761
+ hosts: ['app.example.com'],
762
+ roles: ['unified', 'admin'],
763
+ },
764
+ {
765
+ id: 'website',
766
+ kind: 'ssr',
767
+ template: 'site.html',
768
+ hosts: ['*'],
769
+ roles: ['unified', 'website'],
770
+ application: websiteApplication,
771
+ },
772
+ ],
773
+ server: {
774
+ role,
775
+ publicConfig: {},
776
+ },
777
+ })
778
+ }
779
+ ```
780
+
781
+ `defineSsrRuntime` accepts either a plain object or an async factory function.
782
+
783
+ ## Custom Endpoints
784
+
785
+ ```ts
786
+ endpoints: [
787
+ {
788
+ id: 'robots',
789
+
790
+ match(request) {
791
+ return request.pathname === '/robots.txt'
792
+ },
793
+
794
+ handle() {
795
+ return {
796
+ statusCode: 200,
797
+ body: 'User-agent: *\nAllow: /',
798
+ headers: {
799
+ 'content-type': 'text/plain; charset=utf-8',
800
+ },
801
+ }
802
+ },
803
+ },
804
+ ]
805
+ ```
806
+
807
+ Custom endpoints can be used for:
808
+
809
+ - `robots.txt`
810
+ - `sitemap.xml`
811
+ - Verification files
812
+ - Public JSON endpoints
813
+
814
+ ## Health Checks
815
+
816
+ The server includes:
817
+
818
+ ```text
819
+ /healthz
820
+ /readyz
821
+ ```
822
+
823
+ Add readiness checks:
824
+
825
+ ```ts
826
+ readiness: [
827
+ {
828
+ id: 'api',
829
+
830
+ async run() {
831
+ const response = await fetch('https://api.example.com/health')
832
+
833
+ if (!response.ok) {
834
+ throw new Error('API is unavailable.')
835
+ }
836
+ },
837
+ },
838
+ ]
839
+ ```
840
+
841
+ ## Response Caching
842
+
843
+ ```ts
844
+ import { createSsrMemoryResponseCache } from 'vue-ssr-lite/server'
845
+
846
+ const responseCache = createSsrMemoryResponseCache({
847
+ maxEntries: 500,
848
+ })
849
+ ```
850
+
851
+ Add it to an SSR entry:
852
+
853
+ ```ts
120
854
  {
121
- "scripts": {
122
- "dev": "vue-ssr-lite dev --runtime src/SsrRuntime.ts",
123
- "build": "vue-ssr-lite build --runtime src/SsrRuntime.ts",
124
- "start": "vue-ssr-lite start"
125
- }
855
+ id: 'website',
856
+ kind: 'ssr',
857
+ template: 'site.html',
858
+ hosts: ['*'],
859
+ application: websiteApplication,
860
+
861
+ responseCache: {
862
+ store: responseCache,
863
+ ttlMs: 60_000,
864
+ },
126
865
  }
127
866
  ```
128
867
 
129
- Production emits the consumer server runtime at
130
- `dist/server/SsrRuntime.js` and browser assets under `dist/client`.
868
+ ## Cookie Filtering
869
+
870
+ SSR requests can forward a filtered `Cookie` header to upstream APIs.
871
+
872
+ ```ts
873
+ server: {
874
+ cookieAllowlist: ['session'],
875
+ cookieDenylist: ['admin_token', 'refresh_token'],
876
+ publicConfig: {},
877
+ }
878
+ ```
879
+
880
+ - If `cookieAllowlist` is non-empty, only listed cookies are forwarded.
881
+ - `cookieDenylist` always removes matching cookies.
882
+ - Leave both empty when the browser talks to the API directly and SSR needs no cookies.
883
+
884
+ ## Server Configuration
885
+
886
+ ```ts
887
+ server: {
888
+ host: '0.0.0.0',
889
+ port: 4173,
890
+ role: 'unified',
891
+
892
+ publicConfig: {},
893
+
894
+ requestTimeoutMs: 15_000,
895
+ shutdownTimeoutMs: 10_000,
896
+
897
+ healthPath: '/healthz',
898
+ readinessPath: '/readyz',
899
+
900
+ trustProxy: false,
901
+
902
+ cookieAllowlist: [],
903
+ cookieDenylist: [],
904
+
905
+ logger: {
906
+ info: (event, details) => console.info(event, details ?? ''),
907
+ warn: (event, details) => console.warn(event, details ?? ''),
908
+ error: (event, details) => console.error(event, details ?? ''),
909
+ },
910
+ }
911
+ ```
912
+
913
+ `/healthz` and `/readyz` include the active `role` in their JSON responses.
914
+
915
+ ## Summary
916
+
917
+ For a SPA:
918
+
919
+ 1. Create a normal Vue `main.ts`.
920
+ 2. Create a normal HTML entry.
921
+ 3. Register the HTML file in `spaEntries`.
922
+ 4. Add a runtime entry with `kind: 'spa'`.
923
+
924
+ For SSR:
925
+
926
+ 1. Create the Vue root component and routes.
927
+ 2. Define it with `defineSsrApplication()`.
928
+ 3. Create an SSR HTML template.
929
+ 4. Register it in `applications`.
930
+ 5. Add a runtime entry with `kind: 'ssr'`.
931
+
932
+ Optional for multi-mode deployments:
933
+
934
+ 1. Choose role names for your project.
935
+ 2. Set `server.role` from an environment variable.
936
+ 3. Restrict each entry with `roles`.
937
+
938
+ Both application types can run from the same project, build process, and production server.
939
+
940
+ ## License
941
+
942
+ MIT