UVue offers some basic tools to build a SSR application more easly Most of available API in UVue is exposed with plugins. Bellow sections will indicate path to these plugins.
You can use them when they are installed in uvue.config.js
located in the root
folder of your application:
export default {
plugins: [
// Install asyncData plugin
'@uvue/core/plugins/asyncData',
// Install Vuex plugin
[
'@uvue/core/plugins/vuex',
{
// Some options...
},
],
],
};
TIP
Order of plugins matters
@uvue/core/plugins/vuex
This plugin will offer you the possibility to inject data from server-side to your Vuex store.
fetch()
method
In a page component (e.g. src/views/Home.vue
) :
export default {
async fetch({ store }) {
const data = await api.get('some-data');
// Populate your vuex store
store.commit('SET_DATA', data);
},
};
This method receive a context as first argument.
WARNING
This method is only called on your pages components (defined in your router)
onHttpRequest()
store action
In your src/store.js
store file you add an action called onHttpRequest
This action will be called before your application is ready. The second argument is a
context
variable. The purpose of this action is to fill your Vuex store
on each HTTP requests and will not be not called on future client navigations.
In SPA mode too this action is called before application is ready.
In your Vuex store (e.g. src/store.js
):
export default () => {
return new Vuex.Store({
actions: {
async onHttpRequest({ commit }) {
const data = await api('/some-data');
commit('SET_DATA', data);
},
},
});
};
WARNING
This method is only called on a real HTTP request to show current page. Future navigation (from client side) will not trigger this action.
DANGER
For your Vuex main store or modules, don't forget to initialize state with a factory function.
export default {
namespaced: true,
state: () => ({
// Some data...
}),
};
@uvue/core/plugins/asyncData
You can setup an asyncData()
method on your view components. This method will be
called before the page is created. This method receives a context as first argument and you can populate
your Vuex store and your view's data.
You can do the same in a component's fetch()
method, minus the ability to inject data. It is only used to populate your store.
Examples:
src/views/Example.vue
<template>
<div>Data from asyncData: {{ foo }}</div>
</template>
<script>
export default {
async asyncData({ store }) {
const data = await api.get('some-data');
// Of course you can populate your Vuex store
store.commit('SET_DATA', data.vuex);
// And send data to current component
return {
foo: data.foo,
};
},
};
</script>
WARNING
The asyncData method is only called on your view components (defined in your router).
src/components/Example.vue
<template>
<div>Data from fetch: {{ $store.state.foo }}</div>
</template>
<script>
export default {
async fetch({ store }) {
await store.dispatch('GET_DATA');
},
};
</script>
DANGER
You cannot use this
in either of these methods, because the component is not created yet.
Vue 2.6 introduce a new hook to fetch data before the server-side rendering. This hook can be used at any component level.
You can read official docs here: https://ssr.vuejs.org/guide/data.html
@uvue/core/plugins/prefetch
As you can see in Vue SSR docs above, serverPrefetch
hook is mostly used with Vuex
and need some checks on client side to determine if data is already loaded or not.
This plugin symplify this hook, and create another: prefetch
hook.
With this, you can populate the data of your component, and you don't have to worry about the client check.
Example:
<template>
<div>
Data from prefetch: {{ value }}
</div>
</template>
<script>
export default {
data: () => ({
value: null,
}),
async prefetch() {
// We can use `this` here
this.value = await this.$http.get('...');
}
}
</script>
@uvue/core/plugins/middlewares
You can apply global or per-route middlewares. The main purpose of this plugin is to quickly setup functions to run before all others plugins (e.g. check authorizations on your routes):
In src/some/middleware.js
export default async ({ store, redirect }) => {
// Use is not logged
if (!store.getters.user) {
// Here we use error handler system
error('Forbidden', 403);
}
};
You can declare middlewares via Routes metas object:
export default () => {
return new Router({
routes: [
{
path: '/some-path',
component: () => import('./MyComponent.vue'),
meta: {
middlewares: [checkUser],
},
},
],
});
};
You can also apply global middlewares called on all routes:
In uvue.config.js
export default {
plugins: [
[
'@uvue/core/plugins/middlewares',
{
middlewares: [globalMiddleware],
},
],
],
};
Each middleware will receive a context as first argument.
TIP
Others UVue plugins ca define global middlewares too:
// some-plugin.js
export default {
middlewares() {
return: [
globalMiddleware
];
},
};
Vue meta plugin is automatically managed by UVue in SSR. Things to do to use this plugin is to install it:
npm install vue-meta
Then you need to setup it in your application:
import VueMeta from 'vue-meta';
Vue.use(VueMeta, {
// some options
});
To use it, you can define global metas (applied to all routes) in your src/App.vue
file:
export default {
metaInfo: {
title: 'App',
meta: [
{ charset: 'utf-8' },
{ name: 'viewport', content: 'width=device-width, initial-scale=1' }
// Use vmid here for pages that needs to override this value
{ vmid: 'description', name: 'description', content: 'My awesome application' },
],
},
};
And finally you can override these settings on each page component:
export default {
metaInfo: {
title: 'About !',
meta: [{ vmid: 'description', name: 'description', content: 'About my application' }],
},
};
If you want to assign a metaInfo dynamically or by user input you have to define it as a function return:
export default {
// Fetch some data on your API
async asyncData() {
return {
variable: 'test',
};
},
// Set metas with these infos
metaInfo() {
return {
title: this.variable,
};
},
};
TIP
To use with TypeScript, you will need to setup types definitions, in src/vue-shims.d.ts
:
import { MetaInfo } from 'vue-meta';
/**
* Vue meta
*/
declare module 'vue/types/vue' {
interface Vue {
metaInfo?: MetaInfo | (() => MetaInfo);
}
}
declare module 'vue/types/options' {
interface ComponentOptions<V extends Vue> {
metaInfo?: MetaInfo | (() => MetaInfo);
}
}
@uvue/core/plugins/errorHandler
This plugin will try to catch errors from UVue plugins. If any error is thrown during a asyncData
, fetch
,
onHttpRequest
or a middleware call, error handler will stop execution of your page and populate a store.
Next you can do what you want with this data (catched error) and, for example, display an error page.
For example in src/App.vue
:
<template>
<div id="app">
<router-view v-if="!$errorHandler.error" />
<div v-else class="error-page">Oups, something go wrong !</div>
</div>
</template>
If you write your own UVue plugins, or a custom middleware, you can use a helper function to throw errors:
// In a asyncData of a page component
export const MyComponent = {
async asyncData({ error }) {
error('Access forbidden', 403);
},
};
// My custom middleware
export const middleware = ({ error }) => {
error('Access forbidden', 403);
};
// My cutom plugin
export default {
async routeResolve({ error }) {
error('Access forbidden', 403);
},
};
In some cases you don't need or want SSR for some pages of your application (e.g. administration dashboard, logged pages...)
It's possible to disable it with a simple configuration, in server.config.js
:
export default {
spaPaths: ['/admin', '/admin/**/*'],
};
As you can see glob patterns are supported.
You can generate a static website with a simple command:
npm run ssr:static
Default configuration will parse your application routes and generate a HTML file for each.
You can configure pre-rendering behavior in server.config.js
:
export default {
// Default values
static: {
paths: [],
scanRouter: true,
params: {},
},
};
Options
paths
: Useful if scanRouter
is disabled or a route is not rendered. Fill this
variable with an array of route pathsscanRouter
: Automatic fetch routes paths from you application router (src/router.js
)params
: Define possibles values for your dynamic routes.
Example:{
path: '/:lang/about',
component: AboutPage,
}
{
params: {
lang: [null, 'en', 'fr'],
},
}
/about
/en/about
/fr/about
You will regulary see some libraries are not SSR compatible. In this case your server
side will throw an error of type window is unedefined
.
To avoid these issues you will have some options:
A JS library require window on import
Use process.client
variable only require lib on client side:
if (process.client) {
require('some/lib/that/require/window');
}
Or you can try to load your library in beforeMount()
or mounted()
in your components
with a dynamic import:
export default {
beforeMount() {
import('some/lib/that/require/window').then(module => {
// ...
});
},
};
Finally, you can use imports
config in uvue.config.js
to only import a script
on client side:
export default {
imports: [
// Import your lib automatically
{
src: 'some/lib/that/require/window',
ssr: false,
},
],
};
A Vue component is not SSR compatible
Use vue-no-ssr package to not render a component on server side:
<template>
<div>
<no-ssr> <NotCompatibleWithSSR /> </no-ssr>
</div>
</template>
Some good articles on it:
You can use modern mode of Vue CLI 3 to build lighter bundles for modern
browsers. Just add an argument to ssr:build
command:
npm run ssr:build -- --modern
Then you need to add modernBuild
plugin to your server.config.js
file:
export default {
plugins: {
['@uvue/server/plugins/modernBuild']
},
};
Purpose of this plugin is to rewrite the final HTML output of your pages to include correctly modern bundle and legacy bundle for old browsers.