Nuxt - How can I run a code in client-side after server-side-rendering? - vue.js

I created a plugin injecting a noty (https://ned.im/noty/#/) so I can use it globally, it looks like this:
export default ({ app }, inject) => {
const notify = function (options = {}) {
if (process.client) {
new Noty(options).show();
}
}
app.$notify = notify;
inject('notify', notify);
}
This plugin shows a noty only on the client-side. On the server-side a noty does not appear, cause it can be displayed only in browser.
I have a page with product details and I am receiving data in asyncData method. When the product was not found I would like to show a noty with proper message and redirect user to a product list page. When I change a route in client-side everything works awesome. However on the first page load (eg. I change an url manually in the browser) which happens on the server-side a noty does not appear, only a redirect works.
My question is: how to show a noty in this case? How to create a noty in the browser after SSR or what is the best other solution to my problem?
Is there any way to run some code after client-side is already rendered (after server-side-rendering)?

You could just disable ssr for that plugin.
plugins: [
...,
{ src: '~plugins/yourplugin.js', ssr: false }
]

Okay, I found a module for that: https://github.com/potato4d/nuxt-client-init-module

it's not possible right know (nuxt <= 2.14.0 when i answering)
but you can combine client plugin and client middleware to achieve that
please take a look at this link:
https://github.com/nuxt/nuxt.js/issues/2653#issuecomment-390588837

Related

Custom PageLayoutComponent

To have specific layout for some pages at our project we create few custom PageLayoutComponent's. Some contfiguration example:
{
// #ts-ignore
path: null,
canActivate: [CmsPageGuard],
component: CartPageLayoutComponent,
data: {
cxRoute: 'cart',
cxContext: {
[ORDER_ENTRIES_CONTEXT]: ActiveCartOrderEntriesContextToken,
},
},
},
All work fine with storefront until you will not try to select specific page at smartedit. As result it not use our custom CartPageLayoutComponent, but will use PageLayoutComponent for rendering.
Probably this is because it's not a normal route navigation. Can somebody from spartacus team suggest how this bug can be fixed?
Probably this is because it's not a normal route navigation
I believe your Route should be recognized normally, there is nothing special in adding a custom Angular Route.
So I guess there is something special about the page or URL of Spartacus Storefront that runs in your SmartEdit.
It's hard to tell the reason of your problem without more debugging.
You said your app works as expected when run differently (locally?), but when used in SmartEdit, then there is a problem. Please identify factors that makes the run SmartEdit different from your (local?) run. And try to isolate them. Guesses from top of my head:
production vs dev mode of the build?
exact URL of the cart page?
any difference in configuration between a local version and deployed one to be used in SmartEdit?
I would also add some debugging code to better know which routes configs are available and which one is used for the current route. For debugging purposes please add the following constructor logic in your AppModule:
export class AppModule {
// on every page change, log:
// - active url
// - the Route object that was matched with the active URL
// - all the Route objects provided to Angular Router
constructor(router: Router) {
router.events.subscribe((event) => {
if (event instanceof NavigationEnd) {
console.log({
activeUrl: router.url,
activeRouteConfig:
router.routerState.snapshot.root.firstChild.routeConfig,
allRoutesConfigs: router.config,
});
}
});
}
}
The pages opened in SmartEdit have the same route of cx-preview (e.g. to open faq page in smartedit, request is https://localhost:4200/electronics-spa/en/USD/cx-preview?cmsTicketId=xxxxx. backend can get page id from cmsTicketId). If you want to change the page layout, you can consider use PageLayoutHandler. Spartacus has some PageLayoutHandlers, e.g.
{
provide: PAGE_LAYOUT_HANDLER,
useExisting: CartPageLayoutHandler,
multi: true,
},

Redirecting to previous route after login - Couldn't redirect for Product Detail

After successfully logging in, I need to redirect the user to route that they came from. As I have read AuthRedirectService is built for this but when I move into product detail then hit login and do a successful login, it just redirects me into root page instead of product detail page. But it works for other pages in the application. anybody can help me about that ? thanks
The recommended way to protect CMS pages is to configure a guard for any CMS component that appears in this page. For instance, you can protect Product Details Page, by configuring the AuthGuard for ProdcutIntroComponent (that is likely appearing only in PDP), i.e. in your app.module:
import { ConfigModule, AuthGuard } from '#spartacus/core';
/*...*/
imports: [
ConfigModule.withConfig({
cmsComponents: {
ProductIntroComponent: {
guards: [AuthGuard]
},
},
}),
],
There is no need to customize or touch AuthRedirectService.
The redirect URL in Spartacus is added by the NotAuthGuard, that is by default configured on the ReturningCustomerLoginComponent CMS component. Perhaps you have a customised version of the login component and have not configured this guard?
You can follow the default code from the following pointers:
https://github.com/SAP/spartacus/blob/develop/projects/storefrontlib/src/cms-components/user/login-form/login-form.module.ts
https://github.com/SAP/spartacus/blob/develop/projects/core/src/auth/guards/not-auth.guard.ts
https://github.com/SAP/spartacus/blob/develop/projects/core/src/auth/guards/auth-redirect.service.ts#L59

created hook for vuex / nuxtClientInit?

I was wondering whats the best way to do something like nuxtClientInit. I'm trying to load the Auth State as early as possible on the client and save it in my vuex store but not on the server side. It would be great if vuex had something like the created hook for components but that doesn't exist to my knowledge.
How could I achieve this behavior? One way could be calling an action from a layout but is that the best way?
I understand the nuxt team are working on a nuxtClientInit feature but before they release that you could just make your own. To understand the workflow that nuxt undertakes when there is a request you can look at the lifecycle here. This shows that nuxtServerInit is called first then middleware. During this middleware call nuxt.config.js is served and this contains your custom configuration. Part of this is 'plugins' which as the docs say,
This option lets you define JavaScript plugins that should be run
before instantiating the root Vue.js application.
So if you write a plugin to call a store action you can then get and set your local storage from there. So, start with a nuxt-client-init plugin:
//nuxt-client-init.client.js
export default async context => {
await context.store.dispatch('nuxtClientInit', context)
}
then add the plugin to nuxt.config.js:
//nuxt.config.js
plugins: [
'~/plugins/nuxt-client-init.client.js'
],
If you notice the naming convention here, the .client.js part of the plugin tells nuxt this is a client only plugin and is shorthand for '{ src: '~/plugins/nuxt-client-init.js', mode: 'client' }' which since nuxt 2.4 is the way to define the old '{ src: '~/plugins/nuxt-client-init.js', ssr: false }'. Anyway, you now have a call to your store so you can have an action to call from local storage and set a state item.
//store/index.js
export const actions = {
nuxtClientInit({ commit }, { req }) {
const autho = localStorage.getItem('auth._token.local') //or whatever yours is called
commit('SET_AUTHO', autho)
console.log('From nuxtClientInit - '+autho)
}
}
You probably need to restart your app for that to all take effect but you are then getting and using your Auth State without any of that pesky nuxtServerInit business.

“window is not defined” in Nuxt.js

I get an error porting from Vue.js to Nuxt.js.
I am trying to use vue-session in node_modules. It compiles successfully, but in the browser I see the error:
ReferenceError window is not defined
node_modules\vue-session\index.js:
VueSession.install = function(Vue, options) {
if (options && 'persist' in options && options.persist) STORAGE = window.localStorage;
else STORAGE = window.sessionStorage;
Vue.prototype.$session = {
flash: {
parent: function() {
return Vue.prototype.$session;
},
so, I followed this documentation:
rewardadd.vue:
import VueSession from 'vue-session';
Vue.use(VueSession);
if (process.client) {
require('vue-session');
}
nuxt.config.js:
build: {
vendor: ['vue-session'],
But I still cannot solve this problem.
UPDATED AUGUST 2021
The Window is not defined error results from nodejs server side scripts not recognising the window object which is native to browsers only.
As of nuxt v2.4 you don't need to add the process.client or process.browser object.
Typically your nuxt plugin directory is structured as below:
~/plugins/myplugin.js
import Vue from 'vue';
// your imported custom plugin or in this scenario the 'vue-session' plugin
import VueSession from 'vue-session';
Vue.use(VueSession);
And then in your nuxt.config.js you can now add plugins to your project using the two methods below:
METHOD 1:
Add the mode property with the value 'client' to your plugin
plugins: [
{ src: '~/plugins/myplugin.js', mode: 'client' }
]
METHOD 2: (Simpler in my opinion)
Rename your plugin with the extension .client.js and then add it to your plugins in the nuxt.config.js plugins. Nuxt 2.4.x will recognize the plugin extension as to be rendered on the server side .server.js or the client side .client.js depending on the extension used.
NOTE: Adding the file without either the .client.js or .server.js extensions will render the plugin on both the client side and the server side. Read more here.
plugins: ['~/plugins/myplugin.client.js']
There is no window object on the server side rendering side. But the quick fix is to check process.browser.
created(){
if (process.browser){
console.log(window.innerWidth, window.innerHeight);
}
}
This is a little bit sloppy but it works. Here's a good writeup about how to use plugins to do it better.
Its all covered in nuxt docs and in faq. First you need to make it a plugin. Second you need to make your plugin client side only
plugins: [
{ src: '~/plugins/vue-notifications', mode: 'client' }
]
Also vendor is not used in nuxt 2.x and your process.client not needed if its in plugin with ssr false
In Nuxt 3 you use process.client like so:
if (process.client) {
alert(window);
}
If you've tried most of the answers here and it isn't working for you, check this out, I also had the same problem when using Paystack, a payment package. I will use the OP's instances
Create a plugin with .client.js as extension so that it can be rendered on client side only. So in plugins folder,
create a file 'vue-session.client.js' which is the plugin and put in the code below
import Vue from 'vue'
import VueSession from 'vue-session'
//depending on what you need it for
Vue.use(VueSession)
// I needed mine as a component so I did something like this
Vue.component('vue-session', VueSession)
so in nuxt.config.js, Register the plugin depending on your plugin path
plugins:[
...
{ src: '~/plugins/vue-session.client.js'},
...
]
In index.vue or whatever page you want to use the package... import the package on mounted so it is available when the client page mounts...
export default {
...
mounted() {
if (process.client) {
const VueSession = () => import('vue-session')
}
}
...
}
You can check if you're running with client side or with the browser. window is not defined from the SSR
const isClientSide: boolean = typeof window !== 'undefined'
Lazy loading worked for me. Lazy loading a component in Vue is as easy as importing the component using dynamic import wrapped in a function. We can lazy load the StepProgress component as follows:
export default {
components: {
StepProgress: () => import('vue-step-progress')
}
};
On top of all the answers here, you can also face some other packages that are not compatible with SSR out of the box and that will require some hacks to work properly. Here is my answer in details.
The TLDR is that you'll sometimes need to:
use process.client
use the <client-only> tag
use a dynamic import if needed later on, like const Ace = await import('ace-builds/src-noconflict/ace')
load a component conditionally components: { [process.client && 'VueEditor']: () => import('vue2-editor') }
For me it was the case of using apex-charts in Nuxt, so I had to add ssr: false to nuxt.config.js.

Vue js how to set a default router path to navigate when the web app loads

I have a project it's routing strucher is like this.
1. /
2. admin-panel/:type
2.1 create-user
2.2 update-user
2.3 etc...
3. forgot password
My problem is if someone come to the admin-panel/:type,he has to route to the create-user
by default.
I used a tricky way for this which is create root route under the admin-panel/:type
like this
2.admin-panel
2.1 /
2.2 create-user
But this doesn't work this time since the admin-panel has a parameter called type.
I want if someone comes to the admin-panel he should directed to the create-user by default. How do I achieve this?
I've to add redirect property to the admin-panel route.
Personally for these cases I use an afterEach hook in the routes file:
router.afterEach((to, from) => {
let exist = false
for (let i in this.a.options.routes) {
if (this.a.options.routes[i].name === to.name) {
exist = true
}
}
if (!exist) {
this.a.push({
name: from.name
})
}
})
You can use another hook as beforeEach (I do not remember exactly why we use after). Basically the goal is to go through the list of routes, if it does not exist, it returns you to the route you are currently on, which really means that you do not notice any changes, it just does not change the route.