Similarly to Nuxt,
we define a new page by creating a new .page.vue
file.
<!-- /pages/index.page.vue -->
<!-- Environment: Browser, Node.js -->
<template>
This page is rendered to HTML and interactive: <Counter />
</template>
<script setup>
import Counter from '../components/Counter.vue'
</script>
By default, vite-plugin-ssr
does Filesystem Routing.
FILESYSTEM URL
/pages/index.page.vue /
/pages/about.page.vue /about
We can also define a page's route with a Route String (for parameterized routes such as /movies/@id
) or a Route Function (for full programmatic flexibility).
// /pages/index.page.route.js
// Environment: Node.js (and Browser if we choose Client Routing)
// Note how the two files share the same base `/pages/index.page.`; this is how `vite-plugin-ssr`
// knows that `/pages/index.page.route.js` defines the route of `/pages/index.page.vue`.
// Route Function
export default pageContext => pageContext.urlPathname === '/'
// If we don't create a `.page.route.js` file then vite-plugin-ssr does Filesystem Routing
Unlike Nuxt, we control how our pages are rendered.
// /renderer/_default.page.server.js
// Environment: Node.js
import { createSSRApp, h } from 'vue'
import { renderToString } from '@vue/server-renderer'
import { escapeInject, dangerouslySkipEscape } from 'vite-plugin-ssr/server'
export { render }
async function render(pageContext) {
const { Page, pageProps } = pageContext
const app = createSSRApp({
render: () => h(Page, pageProps)
})
const appHtml = await renderToString(app)
const title = 'Vite SSR'
return escapeInject`<!DOCTYPE html>
<html>
<head>
<title>${title}</title>
</head>
<body>
<div id="app">${dangerouslySkipEscape(appHtml)}</div>
</body>
</html>`
}
// /renderer/_default.page.client.js
// Environment: Browser
import { createSSRApp, h } from 'vue'
export { render }
async function render(pageContext) {
const { Page, pageProps } = pageContext
const app = createSSRApp({
render: () => h(Page, pageProps)
})
app.mount('#app')
}
You may see
import { createSSRApp, h } from 'vue'
for the first time, but every SSR app actually usescreateSSRApp
andh
.Nuxt abstracts this away from us and gives us a slightly faster getting started. But this also means that we lose control over a central piece of our app architecture and we'll eventually lose more time circumventing Nuxt's limiting black box.
This control enables us to easily and naturally integrate any tool we want (Vuex, GraphQL, Service Worker, ...).
There are four page file suffixes:
.page.js
: runs in the browser as well as in Node.js.page.client.js
: runs only in the browser.page.server.js
: runs only in Node.js.page.route.js
: defines the page's Route String or Route Function.Instead of creating a .page.client.js
and .page.server.js
file for each page,
we can create /renderer/_default.page.client.js
and /renderer/_default.page.server.js
which apply as default for all pages.
The last two files we created are actually /renderer/_default.page.client.js
and /renderer/_default.page.server.js
,
which means that we can now create a new page just by defining a new .page.vue
file (the .page.route.js
file is optional).
The _default.page.
files can be overridden. For example, we can overriding our render()
hooks for rendering some of our pages with a completely different UI framework such as React.
Let's now have a look at how to fetch data.
<!-- /pages/star-wars/movie.page.vue -->
<!-- Environment: Browser, Node.js -->
<template>
<h1>{{movie.title}}</h1>
<p>Release Date: {{movie.release_date}}</p>
<p>Director: {{movie.director}}</p>
</template>
<script lang="js">
const pageProps = ['movie']
export default { props: pageProps }
</script>
// /pages/star-wars/movie.page.route.js
// Environment: Node.js
// Route String
export default '/star-wars/@movieId'
// /pages/star-wars/movie.page.server.js
// Environment: Node.js
import fetch from 'node-fetch'
export async function onBeforeRender(pageContext) {
// The route parameter of `/star-wars/@movieId` is available at `pageContext.routeParams`
const { movieId } = pageContext.routeParams
// `.page.server.js` files always run in Node.js; we could use SQL/ORM queries here.
const response = await fetch(`https://swapi.dev/api/films/${movieId}`)
let movie = await response.json()
// Our render and hydrate functions we defined earlier pass `pageContext.pageProps` to
// the root Vue component `Page`; this is where we define `pageProps`.
const pageProps = { movie }
// We make `pageProps` available as `pageContext.pageProps`
return {
pageContext: {
pageProps
}
}
}
// By default `pageContext.*` are available only on the server. But our hydrate function
// we defined earlier runs in the browser and needs `pageContext.pageProps`; we use
// `passToClient` to tell `vite-plugin-ssr` to serialize and make `pageContext.pageProps`
// available to the browser.
export const passToClient = ['pageProps']
That's it for the tour and we have actually already seen most of the interface;
not only is vite-plugin-ssr
flexible but it's also simple to use!
$ npm init vite-plugin-ssr
$ npm init vite-plugin-ssr
to scaffold a new Vite/vite-plugin-ssr
app, or add vite-plugin-ssr
to your existing app by following the instructions here.