How to use onBeforeRouteLeave in Nuxt? - vue.js

I am new to Nuxt and I am not able to find how to use onBeforeRouteLeave in Nuxt with composition API
If possible to give me an example it will be great Thanks

Nuxt 3 uses Vue Router for routing. The documentation goes over onBeforeRouteLeave Here.
OnBeforeRouteLeave acts as a guard to prevent a page from leaving before a task is completed. Heres an example of how it could be used that you may be familiar with!
onBeforeRouteLeave (to, from) {
const answer = window.confirm('Do you really want to leave? you have unsaved changes!')
if (!answer) return false
}

Related

asyncData hook when hard refreshing in Nuxt

I just realized that the asyncData hook is not called when hard refreshing the page. But I have important data to load to show on that page. And I want to make sure that they are always available, even when the user hard refreshes the page.
asyncData from the documentation
the promise returned by the asyncData hook is resolved during route transition
In that case, the best way is to use the fetch() hook and display a loader while you do finish your calls thanks to the $fetchState.pending helper.
Actually, I do think that fetch() is better in many ways.
Quick article on the subject: https://nuxtjs.org/blog/understanding-how-fetch-works-in-nuxt-2-12/
The one on the bottom of the page (Sergey's) is cool with some practical example of a nice implementation too.
You could also use this somewhat hacky solution in a layout to see if the initial location (the one you are when you hard refresh) is the one you want to be. Basically, if you land on a specific page (hard refresh or following a new window page for example) but want to have some custom behavior.
beforeCreate() {
if (!['some-other-page'].includes(this.$router.history._startLocation)) {
this.$router.replace({ name: 'index' }).catch(() => {})
}
},
Still, this one infinite loops if used in a middleware (or I made a mistake) and it's less clean than a fetch() hook.

docusaurus 2: run custom script for every page

Is it possible to run some custom script for every page?
E.g., I want to run alert(1); on every page. How can I do that without the sizzling of any components?
I know it can be done by creating jsx component and using it in every .mdx file (but every doc then should be a .mdx file). So it's not the thing I'm looking for.
Docusaurus 2 user here! 👋
Docusaurus is Server-Side Rendered and then hydrated to function as a Single Page Application. Without knowing more about what you want to achieve, I can only try to give you a general advice.
One way of achieving this is to create your own plugin, it gives you access to the execution context, such as router events.
I currently use this for analytics reporting when the user changes page. It's not yet documented, but there's a good example in the Docusaurus 2 repository in the docusaurus-plugin-google-analytics package.
Here's a fragment of what I use, this only executes when a new page is loaded, which fits my use case perfectly. There may be another lifecycle hook called when the page is hydrated that I haven't found yet.
analytics-module.js
import ExecutionEnvironment from "#docusaurus/ExecutionEnvironment";
export default (function () {
if (!ExecutionEnvironment.canUseDOM) {
return null;
}
return {
onRouteUpdate({ location }) {
_paq.push(["setCustomUrl", location.pathname]);
_paq.push(["setDocumentTitle", document.title]);
_paq.push(["trackPageView"]);
},
};
})();

load routes via component from external api and add them to the router

I would like to load my routes from an external API. Some users might not have the permissions to access a module.
So my navbar makes an API call and gets all the modules returned. These module objects contain the path to the view file.
I tried to create a small sandbox to reproduce the problem
https://codesandbox.io/s/vue-routing-example-i5z1h
If you open this url in your browser
https://i5z1h.codesandbox.io/#/First
you will first get the following error
Url /First not found
but after clicking on the First module link in the navbar, the First view should get rendered.
I think the problem is related to the fact that the page has not yet started the navigation created event after loading and the module page is therefore not found. After changing a router URL the navigation component had enough time to add all the required routes to the router.
How can I load these URLs before the router leads to the first route and responds a 404 error?
The key idea here is to load the routes asynchronously which means you must defer loading of your SPA till that time. In your index.js or main.js, your code would be something like this:
// Some functions are assumed and not defined in the below code.
import Vue from 'vue';
import VueRouter from 'vue-router';
// Application root component
import App from './App.vue';
import { getRoutes } from './api';
// Register Vue plugins
Vue.use(VueRouter);
// Make API call here
// Some animation before the app is fully rendered.
showLoader();
getRoutes(/* Optional User data */)
.then((routesData) => {
// Stop the animation
stopLoader();
return routesData;
})
.then((routesData) => {
// processRoutes returns an array of `RouteConfig`
const routes = processRoutes(routesData);
const router = new Router({
routes: [
...routes,
{
path: '*',
component: NotFound
}
]
});
})
.then((router) => {
const app = new Vue({
el: '#app',
router,
template: '<App/>',
components: { App }
});
});
Additionally, there are a few things you need to do:
Routing is generally the higher-level concern. So if you consider DIP - Dependency Inversion and the stateful + singleton nature of the router, then it makes sense to bootstrap it at the very beginning. Thus, anything that router needs should be available. This means that the navbar component should not be responsible for making the API call. You must take it out.
Another possible solution is to use $router.addRoutes() method. But it is inadequate for your needs. It will not work considering authorization in mind. It will not prevent navigation.
On a philosophical level, when you are using SPA with client-side routing, then client-side routing is its own source of truth. It is reasonable to know all the routes upfront and hence most routers are designed with this idea in mind. Thus, a requirement like this is a poor fit for this paradigm. If you need something like this, then a server should possess the knowledge of client-side routes and during page refresh, the server should decide what to do - Load the SPA or reject with 404/403 page. And if the access is allowed, the server should inject routing data in the HTML page which will then be picked by Vue.js on the browser side. Many sophisticated SSR - Server-Side Rendering techniques exist to achieve this.
Alternative strategy: Use guards
Define all the routes upfront in your router for all the possible views of all the users.
Define guards for each authorized routes. All these guards would be resolved asynchronously.
Instead of loading routing data from API, use the API to return an Authorization Matrix. Use this API response in your route guards to determine the access.
To prevent calls to the same API multiple times, you can use some sort of caching like Proxy, Memoization, store etc. Generally, for a user, the Auth Matrix will not vary between the calls.
As an advantage of this, you can still load the application partially if required leading to meaningful user experience by reducing the user's time to interact with the application.

How to make next(false) in BeforeRouteEnter prevent Vue-Router from navigating to the route?

I am desperately to implement a navigation guard that will prevent users to access an item page if the item doesn't exist, based on the ID provided in route params.
It says in the Vue-Router guide that:
next(false): abort the current navigation. If the browser URL was changed(either manually by the user or via back button), it will be reset to that of the from route.
Yet in my component, using next(false) won't prevent the route change, or component rendering. It won't even navigate back as promised in the doc.
beforeRouteEnter(to, from, next) {
next(false)
ajaxcall$.subscribe(data => next(vm => vm.setData(data)))
I would have expected that obvious next(false) to work and prevent the component and route from rendering but nope. The ajax call is made and the data is set.
"Make sure that the next function is called exactly once in any given pass through the navigation guard." Vue Router - Global Before Guards
You have multiple next() calls.
Also, your ajaxcall$.subscribe() is most likely an async method, so by the time it calls next(vm => vm.setData(data)) the navigation has happened most probably.
You should use await to make sure the information from ajax is available before calling next().
It might not really be a good idea to slow down each page transition with an API call, but that's a different issue...

Custom Login page in Docusaurus

I have a task at work, It is to implement the login page inside the Docusaurus.
I am trying to customizing the index.js by adding some HelloWorld. Including login.js which has actual Docusaurus Index page
const Index = require('./login.js');
const React = require('react');
class Button extends React.Component {
render() {
return ("helloworld");}
}
module.exports = Button;
But Error thrown is:
Error: Cannot find module './login.js'
Is it possible to call the class from another js page in Docusaurus?
Docusaurus maintainer here! Yes, it should be possible as the current module system is CommonJS. You have to put that component in the same directory as index.js. Link me to a repository if possible and I can help you take a look.
On a side note, it doesn't make much sense to build a login form for Docusaurus as Docusaurus generates a static site that loses state across page navigations unless you persist them in cookies or localStorage. It'd be quite troublesome to do so. Maybe you could explain what you're trying to build here and I can recommend you better alternatives.
I found a way to work this out, even though there is no official documentation on this matter. I used the Docusaurus Swizzle to intervene in the Root component so that I can use Firebase Authentication (I think that you can use Auth0 or any custom logic in replacement).
I wrote a detailed blog on this so that I hope it can help others, you can see it here: https://medium.com/#thomasdevshare/docusaurus-authentication-with-firebase-c824da24bc51
There is also full example source code in the article, you can clone it.
First of all. What does "login.js" look like? It is an exported module with a well defined (default) namespace?
Secondly, you shouldn't add the file extension to an import. It's "require('./login')" not "require('./login.js')".