Getting SSR to work with Quasar Framework with Vue Composition as the State Management Choice
Hello readers,
With the release of the Vue Composition API and Vue 3 we now have some amazing methods that will make our lives so much easier. Namely, reactive({})
and computed(() => {})
.
I have been using reactive and computed methods for my state management system in Vue for the past 1–2 years in personal and company apps and it has worked great. I create a file where I create a global store (so I can access the state through the console) and then make sure to include that store in my returned properties in the `setup` method of all the Vue components that need access. Alternately, a better method may be to use the provide/inject
functionality of Vue as has been documented in several trending articles lately.
My favorite framework in Vue is Quasar Framework but currently it doesn’t support Vue Composition API as your State Management choice if you wish to get prefetch and SSR working in your components.
https://quasar.dev/quasar-cli/prefetch-feature
Prefetch is important if you need to make an API call before displaying the page to fetch data that should be visible to SEO bots.
After a few hours of research and digging into Quasar’s source code, I discovered a way you can get this to work with minimal effort. Granted, I would probably consider this a hack and you should generally be wary of hacks. When you use a framework or library in a way the designer did not intend it to be used, the designer might write code/updates in the future that break your hacks. An additional concern is such hacks have a greater risk of incurring bugs and perhaps of the kind that you can’t find solutions for on StackOverflow.
However, since many programmers are interested in using reactive
as their state management choice, Quasar will likely support this in the future without the need for my hack allowing you to replace this code. In the meantime, this is a simple hack that has worked well in our project.
Let’s start with an overview of how SSR works, prefetch, and client hydration.
Why Server Side Rendering?
One of Vue’s selling features is that it’s super fast and uses AJAX technologies to replace data on the page without needing a full page refresh after the page has loaded. This means your scripts, images, CSS and page structure don’t have to update every time you navigate within the site and instead only the data that is expected to change updates.
Unfortunately, Google SEO bots only take their snapshot of the SEO of the page the moment the page finishes loading, but since Vue often doesn’t start fetching data until after the page is loaded, good SEO strategies fail. To fix the lack of good SEO on Vue.js sites, Server Side Rendering was invented. SSR allows you to render a Vue app as a string on a server before it’s ever sent to the client. When the client receives the string, it displays it as html before on the client before the page has finished loading. This allows SEO bots to take a snapshot of the page with all the necessary information.
What is Prefetch?
Often pages need external data to display properly. When you click on a product on Amazon you expect to see prices, descriptions, reviews, and images belonging specifically to that product. So do SEO bots. But since that information is likely saved in your database, you have to make an API/AJAX call to fetch it and then include it in your template. prefetch
allows you to fetch data necessary for the proper display and SEO of a page from your server before the page is finished loading so bots can index it.
What is client side hydration?
After the client receives the rendered html string from the server with all the necessary data included and displaying it, it attempts to rebuild the page client side so the javascript is functional. (This allows you to click buttons, navigate and such). But this leads to our first hurdle. When the client renders, it needs the same data that SSR already fetched on the server before it was sent to the client. We don’t really want to fetch all that information a second time because it puts twice the load on our server and database. So instead the server includes a global variable containing the prefetched data for the page embedded in the html string sent to the client that the client can take and use without having the fetch the data itself a second time.
Vue saves this prefetched data in window.__INITIAL_STATE__
. It is the responsibility of the client to take the data in this variable and merge it with the state management store right after the page loads.
So here’s the hack
In Quasar, the prefetch
option is tightly integrated with the Vuex store. In order for prefetch
to be able to modify the state of our global store on the server and client, Quasar expects a file named src/store/index.js
to exist. The trick is to make our reactive
store to look like a Vuex store in this file. This file returns a function that returns an object that must contain a state
property and replaceState
method. Whatever state
here equals will be what the server saves to window.__INITIAL_STATE__
when it loads the client, you’ll want to make the state
property reference (not clone) your reactive
global store.
The replaceState
is necessary for client side hydration. It should take whatever value is retrieved in window.__INITIAL_STATE__
and merge those properties with those of your reactive
store object. I recommend looping through the initial_states’ properties and assigning them to the client’s store one by one. It’s also important that you don’t try to assign a computed property that may be in your initial state with the store on the client or else it will complain. I like to save all my computed properties in a sub-object in the store under the property compute
so I can easily weed them out
Something like this should suffice:
This assumes you have a file somewhere that looks like this:
import { reactive, computed } from '@vue/composition-api'/*** Data that will be shared between multiple pages should be added here* We make this store global so we can access it for inspection in the console*/global.store = reactive({ current_page: 'home', drawerOpen: false, computed: {
filtered_results: computed(() => { ... }) }, ...
})export default store
To make it work with prefetch in your page component you have to leave out the store
argument.
<script>
export default { preFetch ({
// store, //don't include this argument currentRoute,
previousRoute,
redirect,
ssrContext,
urlPath,
publicPath
}) { return fetch_product(currentRoute.params, currentRoute.fullPath) .catch(err => { console.log(`Fetching Product Err`, err) redirect(previousRoute.path) }) }
}
</script>
Here is the Quasar Source Code that warrants this hack:
Quasar here is looking for a default export in src/store/index.js
.
Here Quasar calls this exported store method as it is performing Server Side Rendering and saves it in a variable named store.
Whatever is stored in context.state
will be added to window.__INITIAL_STATE__
before being sent to the client. Since Quasar is taking that store
variable it fetched earlier and including whatever data that’s on its state
property in context.state
that data effectively gets saved to window.__INITIAL_STATE__
. That’s why we make it reference our reactive
store.
Finally, unless you’ve enabled manualHydration in the quasar.conf.js
, Quasar will automatically call the replaceState
method of the store
variable passing in window.__INITIAL_STATE__.
You should write the replaceState
method to accept this argument and use it’s information to modify your reactive client store however you like or as I show in the screenshot.
Remember, Prefetch only works in pages not components.
There you go. A minimal hack to get your Vue Composition API reactive store working in Quasar with SSR Prefetch.