Skip to content

Commit 84c81dc

Browse files
hi-ogawapi0
andauthored
feat: add vue-router-ssr example with SSR assets API (#5)
Co-authored-by: Pooya Parsa <[email protected]>
1 parent 39478ac commit 84c81dc

File tree

20 files changed

+449
-1
lines changed

20 files changed

+449
-1
lines changed

README.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,7 @@
2121
| `standard` | [examples/standard](./examples/standard/) | [stackblitz](https://stackblitz.com/fork/github/nitrojs/nitro-vite-examples/tree/main/examples/standard?startScript=dev&file=vite.config.mjs,server.ts) | `npx giget gh:nitrojs/vite-examples/examples/standard standard-app` |
2222
| `tanstack-router-react` | [examples/tanstack-router-react](./examples/tanstack-router-react/) | [stackblitz](https://stackblitz.com/fork/github/nitrojs/nitro-vite-examples/tree/main/examples/tanstack-router-react?startScript=dev&file=vite.config.mjs,server.ts) | `npx giget gh:nitrojs/vite-examples/examples/tanstack-router-react tanstack-router-react-app` |
2323
| `tanstack-start-react` | [examples/tanstack-start-react](./examples/tanstack-start-react/) | [stackblitz](https://stackblitz.com/fork/github/nitrojs/nitro-vite-examples/tree/main/examples/tanstack-start-react?startScript=dev&file=vite.config.mjs,server.ts) | `npx giget gh:nitrojs/vite-examples/examples/tanstack-start-react tanstack-start-react-app` |
24+
| `vue-router-ssr` | [examples/vue-router-ssr](./examples/vue-router-ssr/) | [stackblitz](https://stackblitz.com/fork/github/nitrojs/nitro-vite-examples/tree/main/examples/vue-router-ssr?startScript=dev&file=vite.config.mjs,server.ts) | `npx giget gh:nitrojs/vite-examples/examples/vue-router-ssr vue-router-ssr-app` |
2425
| `vue-ssr` | [examples/vue-ssr](./examples/vue-ssr/) | [stackblitz](https://stackblitz.com/fork/github/nitrojs/nitro-vite-examples/tree/main/examples/vue-ssr?startScript=dev&file=vite.config.mjs,server.ts) | `npx giget gh:nitrojs/vite-examples/examples/vue-ssr vue-ssr-app` |
2526

2627
<!-- /automd -->

examples/vue-router-ssr/.gitignore

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
dist
2+
node_modules
3+
*.tsbuildinfo
4+
.output
5+
.nitro
Lines changed: 49 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,49 @@
1+
<script setup lang="ts">
2+
import { RouterLink, RouterView } from "vue-router";
3+
import "./styles.css";
4+
</script>
5+
6+
<template>
7+
<nav>
8+
<ul>
9+
<li>
10+
<RouterLink to="/" exact-active-class="active">Home</RouterLink>
11+
</li>
12+
<li>
13+
<RouterLink to="/about" active-class="active">About</RouterLink>
14+
</li>
15+
</ul>
16+
</nav>
17+
<RouterView />
18+
</template>
19+
20+
<style scoped>
21+
nav {
22+
background: white;
23+
box-shadow: 0 1px 3px rgba(0, 0, 0, 0.1);
24+
padding: 1rem;
25+
}
26+
27+
nav ul {
28+
list-style: none;
29+
margin: 0;
30+
padding: 0;
31+
display: flex;
32+
gap: 2rem;
33+
max-width: 800px;
34+
margin: 0 auto;
35+
}
36+
37+
nav a {
38+
color: #666;
39+
text-decoration: none;
40+
}
41+
42+
nav a:hover {
43+
color: #333;
44+
}
45+
46+
nav a.active {
47+
color: #646cff;
48+
}
49+
</style>
Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,14 @@
1+
import { createSSRApp } from "vue";
2+
import { RouterView, createRouter, createWebHistory } from "vue-router";
3+
import { routes } from "./routes";
4+
5+
async function main() {
6+
const app = createSSRApp(RouterView);
7+
const router = createRouter({ history: createWebHistory(), routes });
8+
app.use(router);
9+
10+
await router.isReady();
11+
app.mount("#root");
12+
}
13+
14+
main();
Lines changed: 67 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,67 @@
1+
import { createSSRApp } from "vue";
2+
import { renderToString } from "vue/server-renderer";
3+
import { RouterView, createMemoryHistory, createRouter } from "vue-router";
4+
5+
import { createHead, transformHtmlTemplate } from "unhead/server";
6+
7+
import { routes } from "./routes";
8+
9+
// @ts-ignore
10+
import clientEntry from "./entry-client.ts?assets=client";
11+
12+
async function handler(request: Request): Promise<Response> {
13+
const app = createSSRApp(RouterView);
14+
const router = createRouter({ history: createMemoryHistory(), routes });
15+
app.use(router);
16+
17+
const url = new URL(request.url);
18+
const href = url.href.slice(url.origin.length);
19+
20+
await router.push(href);
21+
await router.isReady();
22+
23+
const assets = clientEntry.merge(
24+
...(await Promise.all(
25+
router.currentRoute.value.matched
26+
.map((to) => to.meta.assets)
27+
.filter(Boolean)
28+
.map((fn) => fn!().then((m) => m.default)),
29+
)),
30+
);
31+
32+
const head = createHead();
33+
34+
head.push({
35+
link: [
36+
...assets.css.map((attrs: any) => ({ rel: "stylesheet", ...attrs })),
37+
...assets.js.map((attrs: any) => ({ rel: "modulepreload", ...attrs })),
38+
],
39+
script: [{ type: "module", src: clientEntry.entry }],
40+
});
41+
42+
const renderedApp = await renderToString(app);
43+
44+
const html = await transformHtmlTemplate(head, htmlTemplate(renderedApp));
45+
46+
return new Response(html, {
47+
headers: { "Content-Type": "text/html;charset=utf-8" },
48+
});
49+
}
50+
51+
function htmlTemplate(body: string): string {
52+
return /* html */ `\<!DOCTYPE html>
53+
<html lang="en">
54+
<head>
55+
<meta charset="UTF-8" />
56+
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
57+
<title>Vue Router Custom Framework</title>
58+
</head>
59+
<body>
60+
<div id="root">${body}</div>
61+
</body>
62+
</html>`;
63+
}
64+
65+
export default {
66+
fetch: handler,
67+
};
Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,11 @@
1+
<template>
2+
<main>
3+
<h1>About</h1>
4+
<div class="card">
5+
<p>
6+
This is a simple Vue Router demo app built with Vite Plugin Fullstack.
7+
</p>
8+
<p>It demonstrates basic routing and server-side rendering.</p>
9+
</div>
10+
</main>
11+
</template>
Lines changed: 49 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,49 @@
1+
<script setup lang="ts">
2+
import { ref } from "vue";
3+
4+
const count = ref(0);
5+
6+
function increment() {
7+
count.value++;
8+
}
9+
</script>
10+
11+
<template>
12+
<main>
13+
<div class="hero">
14+
<h1>Vue Router Custom Framework</h1>
15+
<p class="subtitle">A simple demo app with Vite</p>
16+
</div>
17+
18+
<div class="card counter-card">
19+
<p>Count: {{ count }}</p>
20+
<button @click="increment">Increment</button>
21+
</div>
22+
</main>
23+
</template>
24+
25+
<style scoped>
26+
.hero {
27+
text-align: center;
28+
margin-bottom: 2rem;
29+
}
30+
31+
.hero h1 {
32+
color: rgb(100, 108, 255);
33+
}
34+
35+
.counter-card {
36+
text-align: center;
37+
}
38+
39+
.counter-card h2 {
40+
color: #646cff;
41+
margin-bottom: 1rem;
42+
}
43+
44+
.counter-card p {
45+
font-size: 1.5rem;
46+
font-weight: bold;
47+
margin: 1rem 0;
48+
}
49+
</style>
Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
<template>
2+
<main>
3+
<h1>Not Found 404</h1>
4+
</main>
5+
</template>
Lines changed: 38 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,38 @@
1+
import type { RouteRecordRaw } from "vue-router";
2+
3+
export const routes: RouteRecordRaw[] = [
4+
{
5+
path: "/",
6+
name: "app",
7+
component: () => import("./app.vue"),
8+
meta: {
9+
assets: () => import("./app.vue?assets"),
10+
},
11+
children: [
12+
{
13+
path: "/",
14+
name: "home",
15+
component: () => import("./pages/index.vue"),
16+
meta: {
17+
assets: () => import("./pages/index.vue?assets"),
18+
},
19+
},
20+
{
21+
path: "/about",
22+
name: "about",
23+
component: () => import("./pages/about.vue"),
24+
meta: {
25+
assets: () => import("./pages/about.vue?assets"),
26+
},
27+
},
28+
{
29+
path: "/:catchAll(.*)",
30+
name: "not-found",
31+
component: () => import("./pages/not-found.vue"),
32+
meta: {
33+
assets: () => import("./pages/not-found.vue?assets"),
34+
},
35+
},
36+
],
37+
},
38+
];
Lines changed: 49 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,49 @@
1+
* {
2+
box-sizing: border-box;
3+
}
4+
5+
body {
6+
margin: 0;
7+
font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", sans-serif;
8+
background: #f5f5f5;
9+
color: #333;
10+
}
11+
12+
main {
13+
max-width: 800px;
14+
margin: 0 auto;
15+
padding: 2rem;
16+
}
17+
18+
h1 {
19+
font-size: 2.5rem;
20+
margin-bottom: 0.5rem;
21+
}
22+
23+
.card {
24+
background: white;
25+
border-radius: 8px;
26+
padding: 2rem;
27+
box-shadow: 0 2px 4px rgba(0, 0, 0, 0.1);
28+
margin: 2rem 0;
29+
}
30+
31+
button {
32+
background: rgb(83, 91, 242);
33+
color: white;
34+
border: none;
35+
padding: 0.5rem 1rem;
36+
border-radius: 4px;
37+
font-size: 1rem;
38+
cursor: pointer;
39+
}
40+
41+
button:hover {
42+
background: #535bf2;
43+
}
44+
45+
.subtitle {
46+
color: #666;
47+
font-size: 1.1rem;
48+
margin-bottom: 2rem;
49+
}

0 commit comments

Comments
 (0)