Problem
Below Nuxt middleware
const inspectAuthentication: Middleware = async (): Promise<void> => {
await AuthenticationService.getInstance().inspectAuthentication();
};
is being executed on the server side before return each page's HTML and checks has been user authenticated. If has been, it stores the CurrentAuthenticatedUser
in Vuex module:
import {
VuexModule,
getModule as getVuexModule,
Module as VuexModuleConfiguration,
VuexAction,
VuexMutation
} from "nuxt-property-decorator";
@VuexModuleConfiguration({
name: "AuthenticationService",
store,
namespaced: true,
stateFactory: true,
dynamic: true
})
export default class AuthenticationService extends VuexModule {
public static getInstance(): AuthenticationService {
return getVuexModule(AuthenticationService);
}
private _currentAuthenticatedUser: CurrentAuthenticatedUser | null = null;
public get currentAuthenticatedUser(): CurrentAuthenticatedUser | null {
return this._currentAuthenticatedUser;
}
@VuexAction({ rawError: true })
public async inspectAuthentication(): Promise<boolean> {
// This condition is always falsy after page reloading
if (this.isAuthenticationInspectionSuccessfullyComplete) {
return isNotNull(this._currentAuthenticatedUser);
}
this.onAuthenticationInspectionStarted();
// The is no local storage on server side; use @nuxtjs/universal-storage instead
const accessToken: string | null = DependenciesInjector.universalStorageService.
getItem(AuthenticationService.ACCESS_TOKEN_KEY_IN_LOCAL_STORAGE);
if (isNull(accessToken)) {
this.completeAuthenticationInspection();
return false;
}
let currentAuthenticatedUser: CurrentAuthenticatedUser | null;
try {
currentAuthenticatedUser = await DependenciesInjector.gateways.authentication.getCurrentAuthenticatedUser(accessToken);
} catch (error: unknown) {
this.onAuthenticationInspectionFailed();
// error wrapping / rethrowing
}
if (isNull(currentAuthenticatedUser)) {
this.completeAuthenticationInspection();
return false;
}
this.completeAuthenticationInspection(currentAuthenticatedUser);
return true;
}
@VuexMutation
private completeAuthenticationInspection(currentAuthenticatedUser?: CurrentAuthenticatedUser): void {
if (isNotUndefined(currentAuthenticatedUser)) {
this._currentAuthenticatedUser = currentAuthenticatedUser;
DependenciesInjector.universalStorageService.setItem(
AuthenticationService.ACCESS_TOKEN_KEY_IN_LOCAL_STORAGE, currentAuthenticatedUser.accessToken
);
}
// ...
}
}
Above code works fine on server side, but then, on the client side, if to try to get AuthenticationService.getInstance().currentAuthenticatedUser
, it will be null
!
I expected that Nuxt.js synchronizes the Vuex store including AuthenticationService
with server side, however, it does not.
Target
AuthenticationService
must be synchronized with server side, so if user has been authenticated, in the client side AuthenticationService.getInstance().currentAuthenticatedUser
it must be non-null even after page reloading.
There no need to synchronize whole Vuex store in server side (for example, the module responsible floating notification bar is required in the client side only) but if the selective methodology has not been developed, at least synchronizing of whole Vuex store will be enough for now.
Please don't recommend me the libraries or Nuxt modules for authentication like Nuxt Auth module because here we are talking about synchronizing of the Vuex store with server, not about best Nuxt modules for authentication. Also, the syncronizing of the vuex store between client and server could be used not just for authentication.
Update
preserveState
solution attempt
Unfortunately,
import { store } from "~/Store";
import { VuexModule, Module as VuexModuleConfiguration } from "nuxt-property-decorator";
@VuexModuleConfiguration({
name: "AuthenticationService",
store,
namespaced: true,
stateFactory: true,
dynamic: true,
preserveState: true /* New */
})
export default class AuthenticationService extends VuexModule {}
causes
Cannot read property '_currentAuthenticatedUser' of undefined
error on the server side.
The error refers to
@VuexAction({ rawError: true })
public async inspectAuthentication(): Promise<boolean> {
if (this.isAuthenticationInspectionSuccessfullyComplete) {
// HERE ⇩
return isNotNull(this._currentAuthenticatedUser);
}
}
I checked this
value. It's a big object; I'll leave the the noticable part only:
{
store: Store {
_committing: false,
// === ✏ All actual action here
_actions: [Object: null prototype] {
'AuthenticationService/inspectAuthentication': [Array],
'AuthenticationService/signIn': [Array],
'AuthenticationService/applySignUp': [Array],
// ...
// === ✏ Some mutations ...
onAuthenticationInspectionStarted: [Function (anonymous)],
completeAuthenticationInspection: [Function (anonymous)],
// ...
context: {
dispatch: [Function (anonymous)],
commit: [Function (anonymous)],
getters: {
currentAuthenticatedUser: [Getter],
isAuthenticationInspectionSuccessfullyComplete: [Getter]
},
// === ✏ The state in undefined!
state: undefined
}
}
I suppose I need to tell how I initializing the vuex store. The working Nuxt methodology for dynamic modules is:
// store/index.ts
import Vue from "vue";
import Vuex, { Store } from "vuex";
Vue.use(Vuex);
export const store: Store<unknown> = new Vuex.Store<unknown>({});
nuxtServerInit
solution attempt
Here is the another problem - how to integrate nuxtServerInit
in above store initialization method? I suppose, to answer this question it's required the Vuex and vuex-module-decorators. In below store/index.ts
, the nuxtServerInit
even will not be called:
import Vue from "vue";
import Vuex, { Store } from "vuex";
Vue.use(Vuex);
export const store: Store<unknown> = new Vuex.Store<unknown>({
actions: {
nuxtServerInit(blackbox: unknown): void {
console.log("----------------");
console.log(blackbox);
}
}
});
I extracted this problem to other question.
This is one of the main challenges when working with SSR. So there's a process called Hydration that happens on the client after receiving the response with static HTML from the server. (you could read more on this Vue SSR guide)
Because of the way Nuxt is built and how the SSR/Client relationship works for hydration, what might happen is your server rendering an snapshot of your app, but the async data not being available before the client mounts the app, causing it to render a different store state, breaking the hydration.
The fact frameworks like Nuxt and Next (for React) implement their own components for Auth, and many others, is to deal with the manual conciliation process for correct hydration.
So going deeper on how to fix that without using Nuxt built-in auth module, there are a few things you could be aware of:
serverPrefetch
method, that will be called on the Server-side which will wait until the promise is resolved before sending to the client to render itrendered
hook, that's called when the app finishes the rendering, so the right moment to send your store state back to the client to reuse it during hydration processregisterModule
, it supports an attributepreserveState
, that's responsible for keeping the state injected by the server.For the examples on how to work those parts, you could check the code on this page
Last, more related to your user auth challenge, another option would be to use
nuxtServerInit
on store actions to run this auth processing, since it'll be passed directly to the client afterwards, as described on Nuxt docs.Updates
On the same page, the docs present that the first argument on the nextServerInit is the
context
, meaning you could get thestore
, for instance, from there.Also one important point to mention, is that on your original question, you've mentioned that you don't want 3rd party libs, but you're already using one that brings a lot of complexity to the table, namely the
nuxt-property-decorator
. So not only you're dealing with SSR that's as complicated as it gets when using frameworks, but you're not using pure Vue, but Next, and not using pure TS Nuxt, but adding another complexity with decorators for the store.Why am I mentioning it? Because taking a quick look on the lib issues, there are other people with the same issue of not accessing the
this
correctly.Coming from a background of someone that used both Nuxt (Vue) and Next (React), my suggestion with you is to try to reduce the complexity, before trying out a lot of different stuff. So I'd test running your app without this
nuxt-property-decorator
to check if this works with the out-of-the-box store implementation, ensuring it's not a bug caused on the lib not fully prepared to support SSR complexity.