March, 31st 2022
Tree-shaking Vuex State in Nuxt Document response
The problem
The way Nuxt delivers state data from server to client can play a bad joke with your loading performance. All of your Vuex state data on the server will be printed in the response Document as JS object.
As a result, your response Document grows in size and client-side startup timings. Notice that app-related scripts are located after the NUXT object. Thus although the content might be partially rendered, the page would not be interactive.
/* html */
<script>
window.__NUXT__=(function(a,b,c,d,e,f,g,h,i,j,k,...){
return {layout:"default",data:[{home:{id:"XqITqBEAACUAX90s",uid:z,url:b,type:z,href: "..."}
};
</script>
<script src="/_nuxt/06bfe48.js" defer=""></script>
<script src="/_nuxt/aa3cf27.js" defer=""></script>
<script src="/_nuxt/ae9dc44.js" defer=""></script>
<script src="/_nuxt/f2e9789.js" defer=""></script>
The other way
Vuex state printing might be the only simple way to delivery data from the server to client in Nuxt. I just gave a single example, but you may have many other various reasons to trim your response Document: translations data if your site is multilingual, large ecommerce catalog, etc.
Instead of putting things in Document, you might like to fetch via API request or from localStorage. Nuxt doesn’t provide an obvious way do reduce that big state object.
The idea is trim the object Nuxt stores the data in right before it’s going to print into the response and add the data back before Nuxt is initialised on client. The data is stored in context.nuxt property. So we can use the module to remove heavy properties from it. The trick is to use the right hook to do it at the right moment, let’s register module with this code:
/* js */
import { Module } from '@nuxt/types'
const MyTrimmingModule: Module = function () {
this.nuxt.hook('vue-renderer:ssr:context', (context: any) => {
context.nuxt = {
...context.nuxt,
state: {
app: {
...context.nuxt.state ? context.nuxt.state.app : {},
languages: [],
platforms: []
}
}
}
})
}
export default MyTrimmingModule
For that purpose Nuxt Renderer has vue-renderer:ssr:context event which is called right before the page is rendered. Idea of this code is to make sure languages and platforms properties are delivered to client as empty arrays. I rewrote the code into spread operator syntax so it’s more obvious to read the structure. For better performance I suggest to mutate languages and platforms properties directly.
Load the data back
Now, since we have our context state trimmed we can benefit from smaller server Document response, but Nuxt most likely still relies on this data to be there by the time initialisation starts. In my example of translations data, we need those translations to be in place so when we navigate to another page client-side, it also get’s translated.
We can do that using a plugin. Let’s say we already have that translation data cached in localStorage, so the only thing that’s left to do is to add it back to Vuex at the moment it becomes available, but before Nuxt if initialised. Let’s use plugin for that:
/* js */
import { Plugin } from '@nuxt/types'
const plugin: Plugin = (context, _inject) => {
const translationData = localStorage.getItem('translations')
store.commit('app/setTranslations', translationsData)
}
But since the page comes already rendered by the server, you can do even better and delay setting the translations to the moment actually need it, for example before page navigation or once the page becomes idle.
Summary
Working with SSR, the way we deliver data to the client should be smart. Measure performance, cache as much as possible and avoid delivering many things twice. When page is already rendered based on some data, maybe you don’t need that data right away. Smaller response size guarantees faster load times and less server load.
However you should be generally careful with this approach as well. Loading data from API will delay the start of the application and dramatically impact Time To Interactive and other. If you are not relying on heavy caching, you should consider other ways as well.