vue-ssr-lite 0.2.14 → 0.2.17

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 (32) hide show
  1. package/README.md +223 -963
  2. package/dist/SsrApplicationRuntime.d.ts +2 -2
  3. package/dist/SsrApplicationRuntime.d.ts.map +1 -1
  4. package/dist/SsrBrowserRuntime.d.ts +4 -4
  5. package/dist/SsrBrowserRuntime.d.ts.map +1 -1
  6. package/dist/SsrConfigCompileRuntime.d.ts +43 -24
  7. package/dist/SsrConfigCompileRuntime.d.ts.map +1 -1
  8. package/dist/SsrConfigTypes.d.ts +45 -19
  9. package/dist/SsrConfigTypes.d.ts.map +1 -1
  10. package/dist/SsrDomainRuntime.d.ts.map +1 -1
  11. package/dist/SsrRenderRuntime.d.ts +2 -2
  12. package/dist/SsrRenderRuntime.d.ts.map +1 -1
  13. package/dist/SsrRuntimeTypes.d.ts +21 -6
  14. package/dist/SsrRuntimeTypes.d.ts.map +1 -1
  15. package/dist/chunks/SsrApplicationRuntime-DB2Prc-Z.mjs +285 -0
  16. package/dist/chunks/SsrConfigCompileRuntime-CSdKTwSe.mjs +546 -0
  17. package/dist/chunks/{SsrReactivityRuntime-Cug-AFwk.mjs → SsrReactivityRuntime-BpfY8EFp.mjs} +1 -1
  18. package/dist/chunks/{SsrServerRuntime-BbWemkgs.mjs → SsrServerRuntime-CtsLlBuD.mjs} +102 -102
  19. package/dist/cli/SsrCliOptions.d.ts +22 -0
  20. package/dist/cli/SsrCliOptions.d.ts.map +1 -0
  21. package/dist/cli.mjs +74 -57
  22. package/dist/client.mjs +37 -37
  23. package/dist/index.d.ts +2 -1
  24. package/dist/index.d.ts.map +1 -1
  25. package/dist/index.mjs +4 -4
  26. package/dist/server/SsrServerRuntime.d.ts.map +1 -1
  27. package/dist/server.mjs +93 -88
  28. package/dist/vite/SsrVitePlugin.d.ts.map +1 -1
  29. package/dist/vite.mjs +63 -58
  30. package/package.json +5 -8
  31. package/dist/chunks/SsrApplicationRuntime-CKjoPpvD.mjs +0 -272
  32. package/dist/chunks/SsrHtmlRuntime-BJUxjM2F.mjs +0 -484
package/README.md CHANGED
@@ -1,353 +1,103 @@
1
1
  # vue-ssr-lite
2
2
 
3
- A simple, production-ready SSR runtime for Vue 3 and Vite.
4
-
5
- Use it to run:
6
-
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
- - Generic `publicConfig` transport (Apollo, REST, Pinia, i18n stay in the app)
26
- - SEO metadata and status codes
27
- - Custom server endpoints
28
- - Health and readiness checks
29
- - Request timeouts
30
- - Optional response caching
31
- - Cookie allow / deny filtering (including deny-only)
32
- - Proxy-aware host and protocol resolution
33
- - Graceful shutdown
34
- - TypeScript support
35
-
36
- ## Package Entry Points
3
+ Convention-first SSR for Vue 3 and Vite that scales from one normal Vue app to
4
+ many independent applications in one repository. A website, admin, dashboard,
5
+ shop, or customer portal can each choose SSR or SPA and be selected by domain
6
+ or subdomain—without separate repositories or manual server/browser bootstraps.
7
+ `vue-ssr-lite` owns the unified runtime, host routing, router history,
8
+ hydration, request isolation, and Node lifecycle while your applications remain
9
+ normal Vue applications.
37
10
 
38
- | Import | Purpose |
39
- | --- | --- |
40
- | `vue-ssr-lite` | `defineSsrConfig`, `defineSsrApplication`, `useSsrDomain`, request context |
41
- | `vue-ssr-lite/client` | Browser hydration and SPA mount |
42
- | `vue-ssr-lite/server` | Managed Node server, config compile, host matching, cookies |
43
- | `vue-ssr-lite/vite` | Vite plugin that wires HTML templates and virtual client entries |
44
-
45
- CLI binary (auto-discovers `ssr.config.ts|.mts|.js|.mjs` in the project root):
11
+ ## Install
46
12
 
47
13
  ```bash
48
- vue-ssr-lite <dev|build|start> [--root .] [--config ssr.config.ts] [--server-output dist/server/SsrRuntime.js] [--hmr-port 31001]
14
+ yarn add vue-ssr-lite
49
15
  ```
50
16
 
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
17
+ or:
58
18
 
59
19
  ```bash
60
- npm install vue-ssr-lite vue vue-router @vue/server-renderer
61
- npm install --save-dev vite @vitejs/plugin-vue
20
+ npm install vue-ssr-lite
62
21
  ```
63
22
 
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
- ## Clean consumer (canonical path)
72
-
73
- Every project follows the same five pieces. `ssr.config` is the single source of
74
- truth — there is no `spaEntries`, no `kind`, no manual `main.ts` mount, and no
75
- `server.role`.
76
-
77
- | Piece | Role |
78
- | --- | --- |
79
- | `ssr.config.ts` | Apps, domains, `runtime`, cookies, endpoints |
80
- | `defineSsrApplication()` modules | One module per app (`application.module`) |
81
- | HTML shells | Mount node only — **no** bootstrap `<script type="module" src>` |
82
- | `vite.config.ts` | `vue()` + `vueSsrLite()` |
83
- | CLI scripts | `vue-ssr-lite dev \| build \| start` |
84
-
85
- Render modes (declared as `render` on each application):
86
-
87
- | `render` | Behavior |
88
- | --- | --- |
89
- | `spa` | Serves the HTML shell; the plugin generates the browser mount entry |
90
- | `ssr` | Server-renders Vue and generates the hydration client entry |
91
-
92
- `vueSsrLite()` defaults the client Vite `build.outDir` to `dist/client` so
93
- `vue-ssr-lite start` finds assets without a consumer override. Set
94
- `build.outDir` or `server.clientOutDir` only when you need a different path.
95
-
96
- Production compile requires top-level `runtime` (typically `APP_RUNTIME`) and
97
- each app’s `domain.production`. `publicConfig` is opaque — validating API URLs
98
- is the consumer’s job.
99
-
100
- For async config, export a factory that *returns* `defineSsrConfig(...)`:
101
-
102
- ```ts
103
- export default async () =>
104
- defineSsrConfig({
105
- name: 'my-platform',
106
- runtime: process.env.APP_RUNTIME,
107
- applications: { /* ... */ },
108
- })
109
- ```
110
-
111
- `defineSsrConfig` itself is synchronous and accepts a plain object.
112
-
113
- See `fixtures/clean-consumer` in this repo for a minimal SPA project that
114
- matches this contract.
115
-
116
- ## Quick Start: SPA and SSR Together
117
-
118
- The following example creates:
119
-
120
- - A dashboard SPA on `app.example.com`
121
- - A server-rendered website on all other hosts
122
-
123
- ## 1. Create the SPA Application
124
-
125
- Define the application once. The library owns router creation, plugin
126
- installation, public-config delivery, and mounting via the generated virtual
127
- client — do **not** add a `main.ts` bootstrap.
23
+ `vue-ssr-lite` is designed to be added to an existing Vue 3 + Vite
24
+ application. It uses the application's existing Vue and Vite installation; no
25
+ separate `@vue/server-renderer` installation is required. `vue-router` is
26
+ provided by `vue-ssr-lite` for the simple `routes` API because the runtime
27
+ creates that router. If application code directly imports Vue Router APIs for
28
+ the advanced router factory below, keep `vue-router` as a direct dependency of
29
+ the host application (existing routed applications should keep their current
30
+ dependency).
128
31
 
129
- ```ts
130
- // src/dashboard/DashboardBootstrap.ts
131
- import { defineSsrApplication } from 'vue-ssr-lite'
132
- import App from './App.vue'
133
- import routes from './routes'
134
-
135
- export const createDashboardApplication = defineSsrApplication({
136
- id: 'dashboard',
137
- rootComponent: App,
138
- routes,
139
- plugins: [/* your Vue plugins */],
140
- })
141
- ```
142
-
143
- Create the SPA root component:
32
+ Requires Node.js 20 or newer.
144
33
 
145
- ```vue
146
- <!-- src/dashboard/App.vue -->
147
- <script setup lang="ts">
148
- import { RouterView } from 'vue-router'
149
- </script>
34
+ ## One repo, one app or many
150
35
 
151
- <template>
152
- <RouterView />
153
- </template>
154
- ```
36
+ Start with the zero-config path when the repository contains one application.
37
+ When a product grows, add `applications` to keep multiple independently routed
38
+ Vue applications in the same codebase:
155
39
 
156
- Create the SPA HTML shell (no bootstrap script):
40
+ ```text
41
+ my-platform/
42
+ ├── src/
43
+ │ ├── website/main.ts → example.com → SSR
44
+ │ ├── admin/main.ts → admin.example.com → SPA
45
+ │ ├── shop/main.ts → shop.example.com → SSR
46
+ │ └── dashboard/main.ts → app.example.com → SPA
47
+ ├── index.html
48
+ ├── ssr.config.ts
49
+ ├── vite.config.ts
50
+ └── package.json
51
+ ```
52
+
53
+ One unified `vue-ssr-lite` runtime/server selects the application from the
54
+ incoming host; different subdomains do not require separate repositories or
55
+ separate SSR implementations:
157
56
 
158
- ```html
159
- <!-- index.html -->
160
- <!doctype html>
161
- <html lang="en">
162
- <head>
163
- <meta charset="UTF-8" />
164
- <meta
165
- name="viewport"
166
- content="width=device-width, initial-scale=1" />
167
- <title>Dashboard</title>
168
- </head>
169
-
170
- <body>
171
- <div id="app"></div>
172
- </body>
173
- </html>
57
+ ```text
58
+ one repository → vue-ssr-lite runtime
59
+ example.com → website → SSR
60
+ admin.example.com → admin → SPA
61
+ shop.example.com → shop → SSR
62
+ app.example.com → dashboard → SPA
174
63
  ```
175
64
 
176
- ## 2. Create the SSR Application
177
-
178
- Create the SSR root component:
179
-
180
- ```vue
181
- <!-- src/website/App.vue -->
182
- <script setup lang="ts">
183
- import { RouterView } from 'vue-router'
184
- </script>
65
+ ## Zero-config single application
185
66
 
186
- <template>
187
- <RouterView />
188
- </template>
189
- ```
190
-
191
- Create the SSR routes:
67
+ Start with the structure Vite already gives you:
192
68
 
193
- ```ts
194
- // src/website/routes.ts
195
- import type { RouteRecordRaw } from 'vue-router'
196
-
197
- import HomePage from './pages/HomePage.vue'
198
- import AboutPage from './pages/AboutPage.vue'
199
- import NotFoundPage from './pages/NotFoundPage.vue'
200
-
201
- export const websiteRoutes: RouteRecordRaw[] = [
202
- {
203
- path: '/',
204
- component: HomePage,
205
- },
206
- {
207
- path: '/about',
208
- component: AboutPage,
209
- },
210
- {
211
- path: '/:pathMatch(.*)*',
212
- component: NotFoundPage,
213
- },
214
- ]
69
+ ```text
70
+ my-vue-app/
71
+ ├── src/
72
+ │ ├── main.ts
73
+ │ ├── App.vue
74
+ │ ├── router/
75
+ │ └── ...
76
+ ├── index.html
77
+ ├── vite.config.ts
78
+ └── package.json
215
79
  ```
216
80
 
217
- Define the SSR application:
81
+ Rewrite the existing browser bootstrap as a universal application definition:
218
82
 
219
83
  ```ts
220
- // src/website/SsrApplication.ts
221
- import { defineSsrApplication } from 'vue-ssr-lite'
222
-
84
+ // src/main.ts
85
+ import { defineApplication } from 'vue-ssr-lite'
223
86
  import App from './App.vue'
224
- import { websiteRoutes } from './routes'
225
-
226
- export const websiteApplication = defineSsrApplication({
227
- id: 'website',
228
- rootComponent: App,
229
- routes: websiteRoutes,
230
- })
231
- ```
232
-
233
- Create its HTML template:
234
-
235
- ```html
236
- <!-- site.html -->
237
- <!doctype html>
238
- <html lang="en">
239
- <head>
240
- <meta charset="UTF-8" />
241
- <meta
242
- name="viewport"
243
- content="width=device-width, initial-scale=1" />
244
- </head>
245
-
246
- <body>
247
- <div id="app"></div>
248
- </body>
249
- </html>
250
- ```
251
-
252
- The SSR browser entry and hydration setup are added automatically.
253
-
254
- ## 3. Create `ssr.config.ts`
87
+ import routes from './router/routes'
255
88
 
256
- Every application is one self-contained object: runtime, domain, GraphQL,
257
- cookies, endpoints, and roles. The library expands domains, matches hosts by
258
- specificity, and exposes context through `useSsrDomain()`.
259
-
260
- ```ts
261
- // ssr.config.ts — the only application / domain registration source
262
- import { defineSsrConfig } from 'vue-ssr-lite'
263
-
264
- export default defineSsrConfig({
265
- name: 'my-platform',
266
- runtime: process.env.APP_RUNTIME, // required in production
267
- server: {
268
- host: '0.0.0.0',
269
- port: Number(process.env.PORT || 4173),
270
- trustProxy: true,
271
- },
272
- applications: {
273
- dashboard: {
274
- render: 'spa',
275
- application: {
276
- module: './src/dashboard/DashboardBootstrap.ts',
277
- exportName: 'createDashboardApplication',
278
- },
279
- template: 'index.html',
280
- roles: ['unified', 'dashboard'],
281
- domain: {
282
- development: 'localhost',
283
- production: process.env.VITE_ROOT_DOMAIN!,
284
- mode: 'root-and-subdomains',
285
- localAliases: true,
286
- params: {
287
- workspace: { source: 'last-subdomain-label' },
288
- },
289
- },
290
- publicConfig: {
291
- api: {
292
- endpoint: process.env.VITE_GRAPHQL_ENDPOINT!,
293
- timeout: 8_000,
294
- },
295
- },
296
- },
297
- website: {
298
- render: 'ssr',
299
- application: {
300
- module: './src/website/SsrApplication.ts',
301
- exportName: 'websiteApplication',
302
- },
303
- template: 'site.html',
304
- roles: ['unified', 'website'],
305
- domain: {
306
- development: 'shop.localhost',
307
- production: process.env.VITE_SHOP_BASE_DOMAIN!,
308
- mode: 'root-and-subdomains',
309
- customDomains: true,
310
- params: {
311
- storeDomain: { source: 'subdomain-or-hostname' },
312
- },
313
- },
314
- publicConfig: {
315
- api: {
316
- endpoint: process.env.VITE_GRAPHQL_ENDPOINT!,
317
- timeout: 8_000,
318
- },
319
- },
320
- },
321
- },
89
+ export default defineApplication({
90
+ root: App,
91
+ routes,
322
92
  })
323
93
  ```
324
94
 
325
- The object key under `applications` is the canonical application ID everywhere
326
- (routing, Apollo `applicationId`, hydration state).
327
-
328
- Host ownership is resolved by **specificity**, not application declaration order:
329
-
330
- 1. Exact hostname
331
- 2. Longest matching wildcard suffix (for example `*.shop.example.com` beats `*.example.com`)
332
- 3. Shorter matching wildcard suffix
333
- 4. Catch-all `*` (from `customDomains: true`)
334
- 5. `defaultApplicationId` when no host pattern matches
335
-
336
- Overlapping wildcards are valid. Duplicate exact or identical wildcard patterns across applications are rejected at startup.
337
-
338
- When the server starts, the console prints a ready message with a clickable local URL and the active role:
339
-
340
- ```text
341
- ✓ Server Ready
342
-
343
- ➜ Local: http://localhost:4173/
344
- ➜ Role: unified
345
- ```
346
-
347
- ## 4. Configure Vite
95
+ The application definition describes the Vue application. The runtime chooses
96
+ `createSSRApp`, request-safe memory history, web history, hydration, and plugin
97
+ installation for the active environment. Do not call `createApp().mount()` in
98
+ this file.
348
99
 
349
- Keep Vite-only concerns here (Vue, CSS, codegen, aliases). Application wiring
350
- stays in `ssr.config.ts`:
100
+ The normal Vite integration is one plugin line:
351
101
 
352
102
  ```ts
353
103
  // vite.config.ts
@@ -356,719 +106,229 @@ import vue from '@vitejs/plugin-vue'
356
106
  import { vueSsrLite } from 'vue-ssr-lite/vite'
357
107
 
358
108
  export default defineConfig({
359
- plugins: [
360
- vueSsrLite(),
361
- vue(),
362
- ],
109
+ plugins: [vue(), vueSsrLite()],
363
110
  })
364
111
  ```
365
112
 
366
- `vueSsrLite()` generates:
367
-
368
- - HTML Rollup inputs from each `template`
369
- - Client `build.outDir` defaulting to `dist/client` (aligned with the Node server)
370
- - `virtual:vue-ssr-lite/client/<id>` SPA mount / SSR hydrate entries
371
- - `virtual:vue-ssr-lite/runtime` for the Node server (SSR apps only; SPA modules are omitted)
372
-
373
- HTML templates should not include a manual `<script type="module" src="...">`
374
- bootstrap — the plugin strips those tags from matched templates and injects the
375
- generated client entry. Editing `ssr.config.*` invalidates the cached config and
376
- triggers a full reload in dev.
377
-
378
- ## 5. Add Commands
113
+ There is no `ssr.config.ts`, `app.ts`, `entry-client.ts`, `entry-server.ts`,
114
+ `site.html`, duplicate application ID, manual hydration, or explicit domain
115
+ configuration in this path. Add the normal lifecycle scripts:
379
116
 
380
117
  ```json
381
118
  {
382
- "scripts": {
383
- "dev": "vue-ssr-lite dev",
384
- "build": "vue-ssr-lite build",
385
- "start": "vue-ssr-lite start"
386
- }
119
+ "scripts": {
120
+ "dev": "vue-ssr-lite dev",
121
+ "build": "vue-ssr-lite build",
122
+ "start": "vue-ssr-lite start"
123
+ }
387
124
  }
388
125
  ```
389
126
 
390
- ## 6. Start Development
391
-
392
- ```bash
393
- npm run dev
394
- ```
395
-
396
- Build and run production:
127
+ The defaults are:
397
128
 
398
- ```bash
399
- npm run build
400
- npm run start
401
- ```
129
+ | Concern | Convention |
130
+ | --- | --- |
131
+ | Application | `./src/main.ts` |
132
+ | HTML template | `./index.html` |
133
+ | Mount target | `#app` |
134
+ | Render mode | `ssr` |
135
+ | Single-app host | incoming `Host` header (`*`) |
136
+ | Server port | `PORT`, then the library default |
137
+ | Runtime role | `unified` |
402
138
 
403
- ## SSR-Only Application
139
+ ## Existing `index.html` stays yours
404
140
 
405
- ```ts
406
- export default defineSsrConfig({
407
- name: 'my-website',
408
- runtime: process.env.APP_RUNTIME,
409
- applications: {
410
- website: {
411
- render: 'ssr',
412
- application: {
413
- module: './src/website/SsrApplication.ts',
414
- exportName: 'websiteApplication',
415
- },
416
- template: 'site.html',
417
- domain: {
418
- development: 'localhost',
419
- production: process.env.VITE_ROOT_DOMAIN!,
420
- customDomains: true,
421
- },
422
- publicConfig: {
423
- api: { endpoint: process.env.VITE_API_ENDPOINT!, timeout: 8_000 },
424
- },
425
- },
426
- },
427
- })
428
- ```
141
+ Keep the existing Vite HTML, including metadata, favicon links, styles,
142
+ analytics, verification tags, and unrelated module scripts:
429
143
 
430
- ## SPA-Only Application
431
-
432
- ```ts
433
- export default defineSsrConfig({
434
- name: 'my-dashboard',
435
- runtime: process.env.APP_RUNTIME,
436
- applications: {
437
- dashboard: {
438
- render: 'spa',
439
- application: {
440
- module: './src/dashboard/DashboardBootstrap.ts',
441
- exportName: 'createDashboardApplication',
442
- },
443
- template: 'index.html',
444
- domain: {
445
- development: 'localhost',
446
- production: process.env.VITE_ROOT_DOMAIN!,
447
- localAliases: true,
448
- },
449
- publicConfig: {
450
- api: { endpoint: process.env.VITE_API_ENDPOINT!, timeout: 8_000 },
451
- },
452
- },
453
- },
454
- })
144
+ ```html
145
+ <body>
146
+ <div id="app"></div>
147
+ <script type="module" src="/src/main.ts"></script>
148
+ <script type="module" src="/analytics.ts"></script>
149
+ </body>
455
150
  ```
456
151
 
457
- The library generates the SPA client entry from `application.module`. Keep
458
- `index.html` free of manual bootstrap scripts.
459
-
460
- ## Using the Same Vue Plugins
152
+ The plugin replaces only the configured application bootstrap and preserves the
153
+ other module scripts. It adds the generated hydration entry and the internal
154
+ SSR markers without requiring a second HTML file.
461
155
 
462
- Plugins read opaque `publicConfig` — the library does not know about GraphQL:
156
+ ## Optional `ssr.config.ts` overrides
463
157
 
464
- ```ts
465
- // src/config/apollo.ts
466
- import { defineApollo } from 'vue-apollo-client'
467
-
468
- export default defineApollo(({ publicConfig }) => ({
469
- endPoints: {
470
- default: publicConfig?.api?.endpoint,
471
- },
472
- requestTimeoutMs: publicConfig?.api?.timeout,
473
- }))
474
- ```
158
+ Configuration is an override layer. Unspecified values continue to use the
159
+ conventions above.
475
160
 
476
- Use the same configuration in SSR:
161
+ Custom application entry:
477
162
 
478
163
  ```ts
479
- // src/website/SsrApplication.ts
480
- import { defineSsrApplication } from 'vue-ssr-lite'
481
-
482
- import apollo from '../config/apollo'
483
- import App from './App.vue'
484
- import { websiteRoutes } from './routes'
164
+ import { defineSsrConfig } from 'vue-ssr-lite'
485
165
 
486
- export const websiteApplication = defineSsrApplication({
487
- id: 'website',
488
- rootComponent: App,
489
- routes: websiteRoutes,
490
- plugins: [apollo],
166
+ export default defineSsrConfig({
167
+ app: './src/platform/main.ts',
491
168
  })
492
169
  ```
493
170
 
494
- This allows one plugin configuration to support both application types.
495
-
496
- You can use the same approach with:
497
-
498
- - Apollo and GraphQL
499
- - Pinia
500
- - i18n
501
- - Theme providers
502
- - REST clients
503
- - Authentication plugins
504
- - Analytics plugins
505
- - Custom Vue plugins
506
-
507
- ## Using GraphQL in SSR
508
-
509
- Keep GraphQL operations in `.graphql` files:
510
-
511
- ```graphql
512
- query GetPosts {
513
- posts {
514
- id
515
- title
516
- }
517
- }
518
- ```
519
-
520
- Use the generated composable in the Vue page:
521
-
522
- ```vue
523
- <script setup lang="ts">
524
- import { useGetPostsQuery } from '../graphql'
525
-
526
- const { result, loading, error } = useGetPostsQuery(
527
- {},
528
- {
529
- ssr: true,
530
- fetchPolicy: 'cache-first',
531
- },
532
- )
533
- </script>
534
-
535
- <template>
536
- <main>
537
- <p v-if="loading">Loading...</p>
538
-
539
- <p v-else-if="error">
540
- {{ error.message }}
541
- </p>
542
-
543
- <article
544
- v-for="post in result?.posts ?? []"
545
- :key="post.id">
546
- <h2>{{ post.title }}</h2>
547
- </article>
548
- </main>
549
- </template>
550
- ```
551
-
552
- The same generated composable can also be used inside the SPA.
553
-
554
- Use `ssr: true` for public data that should be included in server-rendered HTML.
555
-
556
- Use `ssr: false` for browser-only or private queries.
557
-
558
- ## Using REST APIs
559
-
560
- REST APIs can be used through:
561
-
562
- - Native `fetch`
563
- - Axios
564
- - Ky
565
- - A custom Vue plugin
566
- - Another SSR-compatible data package
567
-
568
- Example:
569
-
570
- ```vue
571
- <script setup lang="ts">
572
- import { onServerPrefetch, ref } from 'vue'
573
-
574
- interface Post {
575
- id: string
576
- title: string
577
- }
578
-
579
- const posts = ref<Post[]>([])
580
-
581
- const loadPosts = async () => {
582
- const response = await fetch('https://api.example.com/posts')
583
-
584
- posts.value = await response.json()
585
- }
586
-
587
- onServerPrefetch(loadPosts)
588
- </script>
589
-
590
- <template>
591
- <article
592
- v-for="post in posts"
593
- :key="post.id">
594
- <h2>{{ post.title }}</h2>
595
- </article>
596
- </template>
597
- ```
598
-
599
- For reusable REST caching and hydration, register an SSR-compatible Vue plugin in the `plugins` array.
600
-
601
- ## Public Configuration
602
-
603
- Pass opaque, browser-safe values per application in `ssr.config`:
171
+ Custom template or mount target:
604
172
 
605
173
  ```ts
606
- applications: {
607
- website: {
608
- // ...
609
- publicConfig: {
610
- apiUrl: 'https://api.example.com',
611
- graphqlEndpoint: 'https://api.example.com/graphql',
612
- },
613
- },
614
- }
615
- ```
616
-
617
- Access them in a component:
618
-
619
- ```ts
620
- import { useSsrRequestContext } from 'vue-ssr-lite'
621
-
622
- const context = useSsrRequestContext()
623
- const apiUrl = context.publicConfig.apiUrl
624
- ```
625
-
626
- The library does not interpret `publicConfig` (no GraphQL/REST assumptions).
627
- Validate transport URLs in your own plugins or env checks.
628
-
629
- ## SEO
630
-
631
- Set page metadata from a component:
632
-
633
- ```ts
634
- import { useSsrRequestContext } from 'vue-ssr-lite'
635
-
636
- const context = useSsrRequestContext()
637
-
638
- context.head.value = {
639
- title: 'Products',
640
- description: 'Browse our latest products.',
641
- robots: 'index, follow',
642
- canonicalUrl: 'https://example.com/products',
643
- ogTitle: 'Products',
644
- ogDescription: 'Browse our latest products.',
645
- ogImage: 'https://example.com/social.jpg',
646
- twitterCard: 'summary_large_image',
647
- }
648
- ```
649
-
650
- ## Status Codes
651
-
652
- ```ts
653
- const context = useSsrRequestContext()
654
-
655
- context.response.statusCode = 404
656
- ```
657
-
658
- ## Redirects
659
-
660
- ```ts
661
- const context = useSsrRequestContext()
662
-
663
- context.response.redirect = {
664
- location: '/new-page',
665
- statusCode: 307,
666
- }
667
- ```
668
-
669
- ## Domain Routing
670
-
671
- Domain configuration lives on each application. The library owns normalization
672
- (ports, trailing dots, IPv4/IPv6 aliases), proxy header handling, environment
673
- selection (`development` vs `production`), host specificity matching, custom
674
- domains, context serialization, and hydration.
675
-
676
- ```ts
677
- import { defineSsrConfig, useSsrDomain } from 'vue-ssr-lite'
678
-
679
174
  export default defineSsrConfig({
680
- name: 'my-platform',
681
- runtime: process.env.APP_RUNTIME,
682
- server: { trustProxy: true },
683
- applications: {
684
- admin: {
685
- render: 'spa',
686
- application: {
687
- module: './src/admin/AdminBootstrap.ts',
688
- exportName: 'createAdminApplication',
689
- },
690
- template: 'index.html',
691
- domain: {
692
- development: 'localhost',
693
- production: process.env.VITE_ROOT_DOMAIN!,
694
- mode: 'root-and-subdomains',
695
- localAliases: true,
696
- params: {
697
- workspace: { source: 'last-subdomain-label' },
698
- },
699
- },
700
- publicConfig: {
701
- api: { endpoint: process.env.VITE_API_ENDPOINT!, timeout: 8_000 },
702
- },
703
- },
704
- website: {
705
- render: 'ssr',
706
- application: {
707
- module: './src/website/SsrApplication.ts',
708
- exportName: 'websiteApplication',
709
- },
710
- template: 'site.html',
711
- domain: {
712
- development: 'shop.localhost',
713
- production: process.env.VITE_SHOP_BASE_DOMAIN!,
714
- mode: 'root-and-subdomains',
715
- customDomains: true,
716
- params: {
717
- storeDomain: { source: 'subdomain-or-hostname' },
718
- },
719
- },
720
- publicConfig: {
721
- api: { endpoint: process.env.VITE_API_ENDPOINT!, timeout: 8_000 },
722
- },
723
- },
724
- },
175
+ template: './website.html',
176
+ mount: '#website',
725
177
  })
726
178
  ```
727
179
 
728
- Consume the resolved context anywhere (SSR, SPA, endpoints, hydration):
180
+ Flat advanced single-app options stay flat:
729
181
 
730
182
  ```ts
731
- const domain = useSsrDomain()
732
-
733
- domain.entry
734
- domain.hostname
735
- domain.baseDomain
736
- domain.subdomain
737
- domain.isCustomDomain
738
- domain.params.workspace
739
- domain.params.storeDomain
740
- domain.createUrl({ subdomain: 'department.acme', path: '/projects', query: { page: 2 } })
183
+ export default defineSsrConfig({
184
+ server: {
185
+ trustProxy: true,
186
+ },
187
+ publicConfig: {
188
+ apiUrl: process.env.API_URL,
189
+ },
190
+ cookies: {
191
+ allow: ['session'],
192
+ },
193
+ })
741
194
  ```
742
195
 
743
- ### Precedence
744
-
745
- | Priority | Pattern | Example winner |
746
- | --- | --- | --- |
747
- | 1 | Exact hostname | `shop.localhost` over `*.localhost` |
748
- | 2 | Longer wildcard suffix | `*.shop.localhost` over `*.localhost` |
749
- | 3 | Shorter wildcard suffix | `*.localhost` over `*` |
750
- | 4 | Catch-all `*` | custom domains with no specific rule |
751
- | 5 | `defaultApplicationId` | only when no pattern matches |
752
-
753
- Overlapping roots such as `*.localhost` and `*.shop.localhost` are supported
754
- because the longer suffix wins. Duplicate exact/wildcard ownership and multiple
755
- catch-all (`*`) applications are rejected at startup.
756
-
757
- Do not include protocols, paths, query strings, or ports in domain values.
758
-
759
- `localAliases: true` adds bare loopback hosts (`localhost`, `127.0.0.1`, …) only
760
- when the app’s development base is itself a loopback hostname. Subdomain bases
761
- such as `shop.localhost` already expand to `*.shop.localhost` and do not claim
762
- bare `localhost`, so multiple apps can enable `localAliases` safely.
196
+ Do not put single-application fields beside `applications`. Mixed configuration
197
+ is rejected early; move the field into the relevant application entry instead.
763
198
 
764
- 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.
199
+ ## Plugins and advanced routers
765
200
 
766
- ## Runtime Roles
767
-
768
- Roles let one codebase and one Docker image run as different process modes.
769
-
770
- - Set top-level `runtime` to the active mode for this process.
771
- - Set `roles` on each application to list which modes may serve it.
772
- - Omit `roles` (or omit `runtime`) to keep an application available in every mode.
773
-
774
- Role names are application-defined strings. Common patterns:
775
-
776
- | Mode | Typical use |
777
- | --- | --- |
778
- | `unified` | Local development or a single process that serves every application |
779
- | A private role | SPA / admin / back-office only |
780
- | A public role | SSR website / marketing / storefront only |
781
-
782
- Example:
201
+ Use a plugin factory for stateful integrations so each SSR request receives a
202
+ fresh Pinia, i18n, Apollo, or other request-sensitive instance:
783
203
 
784
204
  ```ts
785
- export default defineSsrConfig({
786
- name: 'my-platform',
787
- runtime: process.env.APP_RUNTIME, // required in production
788
- applications: {
789
- admin: {
790
- render: 'spa',
791
- application: {
792
- module: './src/admin/AdminBootstrap.ts',
793
- exportName: 'createAdminApplication',
794
- },
795
- template: 'index.html',
796
- roles: ['unified', 'admin'],
797
- domain: {
798
- development: 'localhost',
799
- production: process.env.VITE_ROOT_DOMAIN!,
800
- mode: 'root-and-subdomains',
801
- localAliases: true,
802
- },
803
- publicConfig: {
804
- api: { endpoint: process.env.VITE_API_ENDPOINT!, timeout: 8_000 },
805
- },
806
- },
807
- website: {
808
- render: 'ssr',
809
- application: {
810
- module: './src/website/SsrApplication.ts',
811
- exportName: 'websiteApplication',
812
- },
813
- template: 'site.html',
814
- roles: ['unified', 'website'],
815
- domain: {
816
- development: 'shop.localhost',
817
- production: process.env.VITE_SHOP_BASE_DOMAIN!,
818
- mode: 'root-and-subdomains',
819
- customDomains: true,
820
- },
821
- publicConfig: {
822
- api: { endpoint: process.env.VITE_API_ENDPOINT!, timeout: 8_000 },
823
- },
824
- },
825
- },
205
+ export default defineApplication({
206
+ root: App,
207
+ routes,
208
+ plugins: () => [
209
+ createPinia(),
210
+ createI18n(),
211
+ createApollo(),
212
+ ],
826
213
  })
827
214
  ```
828
215
 
829
- With that setup:
830
-
831
- - `APP_RUNTIME=unified` serves both applications (host routing still applies).
832
- - `APP_RUNTIME=admin` serves only the SPA application.
833
- - `APP_RUNTIME=website` serves only the SSR application.
834
-
835
- If a host matches an application that the current role does not allow, the server responds with `421`.
836
-
837
- Development may omit `runtime` (the compiler defaults the process role to
838
- `unified`). Production compilation rejects a missing `runtime` or
839
- `domain.production`. It does **not** validate `publicConfig` shape — pass
840
- whatever your plugins need and validate API URLs in the consumer.
841
-
842
- For async loading, export `async () => defineSsrConfig({ ... })`.
843
-
844
- ## Custom Endpoints
216
+ Stateless global-safe plugin arrays remain supported. For mature router setups,
217
+ the library supplies the environment-appropriate history while the application
218
+ keeps ownership of router options. If this factory directly imports
219
+ `createRouter` or other Vue Router APIs, the host application must declare
220
+ `vue-router` as a direct dependency; do not rely on another package's
221
+ transitive dependency under strict/non-hoisting package managers:
845
222
 
846
223
  ```ts
847
- endpoints: [
848
- {
849
- id: 'robots',
850
-
851
- match(request) {
852
- return request.pathname === '/robots.txt'
853
- },
854
-
855
- handle() {
856
- return {
857
- statusCode: 200,
858
- body: 'User-agent: *\nAllow: /',
859
- headers: {
860
- 'content-type': 'text/plain; charset=utf-8',
861
- },
862
- }
863
- },
864
- },
865
- ]
224
+ import { createRouter } from 'vue-router'
225
+
226
+ export default defineApplication({
227
+ root: App,
228
+ router: ({ history }) => createRouter({
229
+ history,
230
+ routes,
231
+ scrollBehavior,
232
+ }),
233
+ })
866
234
  ```
867
235
 
868
- Custom endpoints can be used for:
869
-
870
- - `robots.txt`
871
- - `sitemap.xml`
872
- - Verification files
873
- - Public JSON endpoints
236
+ `routes` and `router` are mutually exclusive. Router-less applications do not
237
+ create Vue Router history at all.
874
238
 
875
- ## Health Checks
239
+ ## Multi-application projects
876
240
 
877
- The server includes:
878
-
879
- ```text
880
- /healthz
881
- /readyz
882
- ```
883
-
884
- Add readiness checks:
241
+ Multi-app mode is for products where several Vue applications live in the same
242
+ repository. They can share components, packages, types, services, and
243
+ infrastructure while keeping independent roots, routes, rendering modes,
244
+ templates, and host routing. Introduce `applications` only when there are
245
+ actually multiple applications; a single app does not need this configuration.
246
+ Each object key is the canonical application ID; do not repeat it in
247
+ `src/*/main.ts`.
885
248
 
886
249
  ```ts
887
- readiness: [
888
- {
889
- id: 'api',
890
-
891
- async run() {
892
- const response = await fetch('https://api.example.com/health')
893
-
894
- if (!response.ok) {
895
- throw new Error('API is unavailable.')
896
- }
897
- },
898
- },
899
- ]
250
+ import { defineSsrConfig } from 'vue-ssr-lite'
251
+
252
+ export default defineSsrConfig({
253
+ applications: {
254
+ website: {
255
+ app: './src/website/main.ts',
256
+ host: 'example.com',
257
+ },
258
+ admin: {
259
+ app: './src/admin/main.ts',
260
+ render: 'spa',
261
+ host: 'admin.example.com',
262
+ },
263
+ shop: {
264
+ app: './src/shop/main.ts',
265
+ host: 'shop.example.com',
266
+ },
267
+ dashboard: {
268
+ app: './src/dashboard/main.ts',
269
+ render: 'spa',
270
+ host: 'app.example.com',
271
+ },
272
+ },
273
+ })
900
274
  ```
901
275
 
902
- ## Response Caching
276
+ Omitted `render` means SSR, so `website` and `shop` are SSR applications while
277
+ `admin` and `dashboard` are SPAs. Rendering mode belongs to each application;
278
+ one SPA does not change the mode of the others. Each application module
279
+ default-exports `defineApplication(...)`:
903
280
 
904
281
  ```ts
905
- import { createSsrMemoryResponseCache } from 'vue-ssr-lite/server'
282
+ // src/website/main.ts
283
+ import { defineApplication } from 'vue-ssr-lite'
284
+ import App from './App.vue'
906
285
 
907
- const responseCache = createSsrMemoryResponseCache({
908
- maxEntries: 500,
286
+ export default defineApplication({
287
+ root: App,
909
288
  })
910
289
  ```
911
290
 
912
- Add it on the application object:
291
+ For application host routing, use exact hosts such as `example.com`,
292
+ subdomains such as `admin.example.com`, wildcard hosts such as
293
+ `*.shop.example.com`, or the advanced `domain` options. Host specificity
294
+ determines the winner; duplicate ownership and ambiguous routing fail during
295
+ startup.
913
296
 
914
- ```ts
915
- website: {
916
- render: 'ssr',
917
- application: {
918
- module: './src/website/SsrApplication.ts',
919
- exportName: 'websiteApplication',
920
- },
921
- template: 'site.html',
922
- domain: {
923
- development: 'localhost',
924
- production: process.env.VITE_ROOT_DOMAIN!,
925
- customDomains: true,
926
- },
927
- responseCache: {
928
- store: responseCache,
929
- ttlMs: 60_000,
930
- },
931
- publicConfig: {
932
- api: { endpoint: process.env.VITE_API_ENDPOINT!, timeout: 8_000 },
933
- },
934
- }
935
- ```
297
+ ## Advanced capabilities
936
298
 
937
- ## Cookie Filtering
299
+ The normalized runtime still supports roles, domains and subdomains,
300
+ `publicConfig`, cookies, endpoints, response caching, readiness probes,
301
+ diagnostics, metrics, redirects, status codes, and proxy-aware host handling.
302
+ These belong in optional configuration and application code rather than in the
303
+ normal consumer bootstrap.
938
304
 
939
- SSR requests can forward a filtered `Cookie` header to upstream APIs. Configure
940
- cookies per application:
305
+ Request context and domain context are available to advanced integrations:
941
306
 
942
307
  ```ts
943
- cookies: {
944
- allow: ['session'],
945
- deny: ['admin_token', 'refresh_token'],
946
- }
947
- ```
948
-
949
- - Both empty → forward nothing (secure default when SSR needs no cookies).
950
- - `allow` empty and `deny` non-empty → forward all cookies except `deny`.
951
- - `allow` non-empty → forward `allow ∩ ¬deny`.
308
+ import { useSsrDomain, useSsrRequestContext } from 'vue-ssr-lite'
952
309
 
953
- ## Server Configuration
954
-
955
- ```ts
956
- server: {
957
- host: '0.0.0.0',
958
- port: 4173,
959
- trustProxy: false,
960
- requestTimeoutMs: 15_000,
961
- shutdownTimeoutMs: 10_000,
962
- healthPath: '/healthz',
963
- readinessPath: '/readyz',
964
- logger: {
965
- info: (event, details) => console.info(event, details ?? ''),
966
- warn: (event, details) => console.warn(event, details ?? ''),
967
- error: (event, details) => console.error(event, details ?? ''),
968
- },
969
- }
310
+ const domain = useSsrDomain()
311
+ const request = useSsrRequestContext()
970
312
  ```
971
313
 
972
- The active process role comes from top-level `runtime`, not `server.role`.
973
- `/healthz` and `/readyz` include that role in their JSON responses.
974
-
975
- ## Server-side data resolution
976
-
977
- `renderToString` awaits each component's native `onServerPrefetch`, which covers
978
- data consumed reactively inside the component that declared it. For work started
979
- OUTSIDE that lifecycle — a store action, an i18n loader, a lazy query — the
980
- package exposes a generic, API-client-neutral **resolution contract**.
981
-
982
- An installed plugin obtains it by injecting `SSR_REQUEST_RESOLUTION`
983
- (`Symbol.for('vue-ssr:request-resolution')`, resolvable without importing this
984
- package). It registers in-flight work with `track(promise)` and may call
985
- `requestAdditionalPass()`. After each render pass the renderer awaits registered
986
- work and re-renders when a plugin asked for another pass, up to
987
- `server.maxResolutionPasses` (default 4) and bounded by
988
- `server.resolutionDeadlineMs` and the request abort signal.
989
-
990
- A fully resolvable page completes in **one pass** — extra passes occur only when
991
- a plugin left work pending or requested one. `vue-ssr-lite` never inspects the
992
- work; it only awaits it.
993
-
994
- ### Deferred parent → child dependencies
314
+ `publicConfig` is opaque and browser-safe; validate API URLs and integration
315
+ details in the consuming application or plugin.
995
316
 
996
- A common pattern is: a parent query resolves, its result determines which child
997
- components mount, and each child runs its own async work. When a child's data is
998
- consumed DIRECTLY inside that child, a single pass suffices — Vue awaits the
999
- child's `onServerPrefetch` before rendering it.
317
+ ## Failure messages
1000
318
 
1001
- It does NOT suffice when the child's data is consumed INDIRECTLY — the child
1002
- writes into a shared store that a **sibling** component reads — because Vue does
1003
- not block a sibling's render on an earlier sibling's `onServerPrefetch`. The
1004
- sibling renders before the store is populated.
319
+ Convention discovery fails early with actionable guidance when `src/main.ts`,
320
+ `index.html`, or the configured mount target is missing. The compiler also
321
+ reports invalid application exports, mixed single-/multi-app configuration, and
322
+ unresolved multi-app host routing before the server handles traffic.
1005
323
 
1006
- **Applications write no orchestration for this.** Reconcile resolved data into
1007
- the store with `ssrWatch` exactly as you already would:
324
+ ## Package entry points
1008
325
 
1009
- ```ts
1010
- const { result } = useMyQuery(vars, { ssr: true })
1011
- ssrWatch(() => result.value, (data) => {
1012
- if (data) store.set(key, data)
1013
- }, { immediate: true })
1014
- ```
1015
-
1016
- `ssrWatch` automatically requests one more render pass when it fires from the
1017
- awaited prefetch (after a sibling already rendered). On the resumed pass the
1018
- data is warm in the API client's request cache — `vue-apollo-client` settles it
1019
- synchronously at setup — so the same `ssrWatch` runs on its immediate tick, the
1020
- sibling sees the store, and no further pass is requested. Bounded, and each
1021
- operation runs exactly once. Nothing app-specific, and nothing to repeat per
1022
- component or per project.
1023
-
1024
- ## SSR-safe reactivity
1025
-
1026
- During SSR, Vue does not flush the scheduler, so `watch(src, cb)` and
1027
- `watchEffect` run at most once and are then discarded — the most common cause of
1028
- "the shell renders but the content is missing" bugs. Server-safe APIs:
1029
-
1030
- | API | Server behaviour |
326
+ | Import | Purpose |
1031
327
  | --- | --- |
1032
- | `computed` | ✅ Lazily evaluated during render |
1033
- | `ssrWatch(src, cb)` / `ssrWatchEffect(fn)` | ✅ Sync flush, active during render |
1034
- | `watch(src, cb)` / `watchEffect(fn)` | ⚠️ Runs once (or never), then discarded |
1035
- | `onServerPrefetch(async fn)` | ✅ Awaited before the component renders |
1036
- | `onMounted` / `onUpdated` | ❌ Browser only |
1037
-
1038
- Use a `computed` to derive a value for the template; reach for `ssrWatch` /
1039
- `ssrWatchEffect` (exported from `vue-ssr-lite` and `vue-ssr-lite/client`) when
1040
- resolved data must drive an imperative side effect during the server render.
1041
-
1042
- ## Runtime configuration helpers
1043
-
1044
- `vue-ssr-lite/server` provides declarative helpers so a runtime definition is
1045
- configuration, not boilerplate: `ssrEnvBoolean`, `ssrEnvNumber`, `ssrEnvList`,
1046
- `requireSsrEnv`, `requireSsrHostname`, `requireSsrEnum`, `createSsrConsoleLogger`
1047
- and `createSsrSeoEndpoints` (`robots.txt` / `sitemap.xml` — `served`, `proxy` or
1048
- `disallow` by option). Host helpers such as `normalizeSsrHost` remain exported;
1049
- reuse them rather than re-implementing.
1050
-
1051
- ## Development diagnostics
1052
-
1053
- When diagnostics are enabled (`server.diagnostics`, default on outside
1054
- production) the renderer reports, with actionable messages: a loading
1055
- placeholder or `aria-busy="true"` still present after resolution, a route that
1056
- matched no component, and a body that resolved to nothing. These are dev-only
1057
- warnings; production render paths are untouched.
1058
-
1059
- ## Summary
1060
-
1061
- 1. Add `ssr.config.ts` with `defineSsrConfig({ name, runtime, applications })`.
1062
- 2. For each app: `defineSsrApplication()` module, HTML shell (no bootstrap script),
1063
- `render: 'spa' | 'ssr'`, and `application: { module, exportName }`.
1064
- 3. Wire Vite with `vue()` + `vueSsrLite()` (client assets land in `dist/client`).
1065
- 4. Run `vue-ssr-lite dev|build|start`.
1066
- 5. In production set `APP_RUNTIME` / `runtime` and every `domain.production`.
1067
-
1068
- Optional: restrict apps with `roles`, filter cookies, add endpoints, or pass
1069
- `server.onMetrics` / `server.renderError` through `ssr.config`.
1070
-
1071
- SPA and SSR apps share one project, one build, and one production server.
328
+ | `vue-ssr-lite` | `defineApplication`, `defineSsrConfig`, request/domain context |
329
+ | `vue-ssr-lite/client` | Internal browser hydration and SPA mounting |
330
+ | `vue-ssr-lite/server` | Managed server, compilation, host matching, endpoints |
331
+ | `vue-ssr-lite/vite` | Vite HTML and generated client/server entry integration |
1072
332
 
1073
333
  ## License
1074
334