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 +903 -91
- package/dist/chunks/{SsrServerRuntime-DvSz8oSq.mjs → SsrServerRuntime-ChrpJneS.mjs} +76 -63
- package/dist/cli/SsrCliHmrPort.d.ts +4 -0
- package/dist/cli/SsrCliHmrPort.d.ts.map +1 -0
- package/dist/cli.mjs +70 -46
- package/dist/server/SsrServerRuntime.d.ts.map +1 -1
- package/dist/server.mjs +1 -1
- package/package.json +4 -2
package/README.md
CHANGED
|
@@ -1,130 +1,942 @@
|
|
|
1
1
|
# vue-ssr-lite
|
|
2
2
|
|
|
3
|
-
|
|
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
|
-
|
|
9
|
-
or application queries.
|
|
5
|
+
Use it to run:
|
|
10
6
|
|
|
11
|
-
|
|
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
|
-
|
|
16
|
-
import
|
|
17
|
-
import {
|
|
18
|
-
|
|
19
|
-
export const
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
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
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
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
|
-
|
|
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
|
-
|
|
251
|
+
|
|
252
|
+
import { websiteApplication } from './website/SsrApplication'
|
|
37
253
|
|
|
38
254
|
export default defineSsrRuntime({
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
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
|
-
|
|
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
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
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
|
-
|
|
82
|
-
|
|
83
|
-
|
|
354
|
+
## 6. Start Development
|
|
355
|
+
|
|
356
|
+
```bash
|
|
357
|
+
npm run dev
|
|
358
|
+
```
|
|
359
|
+
|
|
360
|
+
Build and run production:
|
|
84
361
|
|
|
85
|
-
|
|
362
|
+
```bash
|
|
363
|
+
npm run build
|
|
364
|
+
npm run start
|
|
365
|
+
```
|
|
86
366
|
|
|
87
|
-
|
|
367
|
+
## SSR-Only Application
|
|
88
368
|
|
|
89
|
-
|
|
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
|
-
|
|
100
|
-
|
|
371
|
+
```ts
|
|
372
|
+
export default defineSsrRuntime({
|
|
373
|
+
name: 'my-website',
|
|
101
374
|
|
|
102
|
-
|
|
375
|
+
entries: [
|
|
376
|
+
{
|
|
377
|
+
id: 'website',
|
|
378
|
+
kind: 'ssr',
|
|
379
|
+
template: 'site.html',
|
|
380
|
+
hosts: ['*'],
|
|
381
|
+
application: websiteApplication,
|
|
382
|
+
},
|
|
383
|
+
],
|
|
103
384
|
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
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
|
-
|
|
130
|
-
|
|
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
|