How to handle route param updates nuxt.js - vue-router

If the destination is the same as the current route and only params are changing (eg: going from one profile to another /users/1 -> /users/2) How can I recognize this and update the component. With vue-router I can just use the beforeRouteUpdate method to update the component.
Here is a recording of my issue

I created an routeGaurds.js file inside the middleware directory and then added that middleware inside my nuxt.config.js file
// routeGaurds.js
export default ({ app }) => {
if(process.browser) {
app.router.beforeEach((to, from, next) => {
console.log(to)
next()
})
}
// nuxt.config.js file
module.exports = {
router: {
linkActiveClass: 'active-link',
middleware: ['routeGaurds']
}}

Related

Dynamically add a route in a Nuxt3 middleware

I have a Nuxt3 project where I'd like to add new routes based on an API call to a database. For example, let's say a user navigates to /my-product-1. A route middleware will look into the database and if it finds an entry, it will return that a product page should be rendered (instead of a category page, for example).
This is what I came up with:
export default defineNuxtPlugin(() => {
const router = useRouter()
addRouteMiddleware('routing', async (to) => {
if (to.path == '/my-awesome-product') {
router.addRoute({
component: () => import('/pages/product.vue'),
name: to.path,
path: to.path
})
console.log(router.hasRoute(to.path)) // returns TRUE
}
}, { global: true })
})
To keep it simple, I excluded the API call from this example. The solution above works, but not on initial load of the route. The route is indeed added to the Vue Router (even on the first visit), however, when I go directly to that route, it shows a 404 and only if I don't reload the page on the client does it show the correct page when navigated to it for the second time.
I guess it has something to do with the router not being updated... I found the following example in a GitHub issue, however, I can't get it to work in Nuxt3 as (as far as I'm aware) it doesn't provide the next() method.
When I tried adding router.replace(to.path) below the router.addRoute line, I ended up in an infinite redirect loop.
// from https://github.com/vuejs/vue-router/issues/3660
// You need to trigger a redirect to resolve again so it includes the newly added
route:
let hasAdded = false;
router.beforeEach((to, from, next) => {
if (!hasAdded && to.path === "/route3") {
router.addRoute(
{
path: "/route3",
name: "route3",
component: () => import("#/views/Route3.vue")
}
);
hasAdded = true;
next('/route3');
return;
}
next();
});
How could I fix this issue, please?
Edit:
Based on a suggestion, I tried using navigateTo() as a replacement for the next() method from Vue Router. This, however, also doesn't work on the first navigation to the route.
let dynamicPages: { path: string, type: string }[] = []
export default defineNuxtRouteMiddleware((to, _from) => {
const router = useRouter()
router.addRoute({
path: to.path,
name: to.path,
component: () => import ('/pages/[[dynamic]]/product.vue')
})
if (!dynamicPages.some(route => route.path === to.path)) {
dynamicPages.push({
path: to.path,
type: 'product'
})
return navigateTo(to.fullPath)
}
})
I also came up with this code (which works like I wanted), however, I don't know whether it is the best solution.
export default defineNuxtPlugin(() => {
const router = useRouter()
let routes = []
router.beforeEach(async (to, _from, next) => {
const pageType = await getPageType(to.path) // api call
if (isDynamicPage(pageType)) {
router.addRoute({
path: to.path,
name: to.path,
component: () => import(`/pages/[[dynamic]]/product.vue`),
})
if (!routes.some(route => route.path === to.path)) {
routes.push({
path: to.path,
type: pageType,
})
next(to.fullPath)
return
}
}
next()
})
})
I suggest you use dynamic routing within /page directory structure - https://nuxt.com/docs/guide/directory-structure/pages#dynamic-routes
The [slug] concept is designed exactly for your usecase. You don't need to know all possible routes in advance. You just provide a placeholder and Nuxt will take care of resolving during runtime.
If you insist on resolving method called before each route change, the Nuxt's replacement for next() method you're looking for is navigateTo
https://nuxt.com/docs/api/utils/navigate-to
And I advise you to use route middleware and put your logic into /middleware/routeGuard.global.ts. It will be auto-executed upon every route resolving event. The file will contain:
export default defineNuxtRouteMiddleware((to, from) => {
// your route-resolving logic you wanna perform
if ( /* navigation should happen */ {
return navigateTo( /* your dynamic route */ )
}
// otherwise do nothing - code will flow and given to.path route will be resolved
})
EDIT: However, this would still need content inside /pages directory or some routes created via Vue Router. Because otherwise navigateTo will fail, as there would be no route to go.
Here is an example of one possible approach:
https://stackblitz.com/edit/github-8wz4sj
Based on pageType returned from API Nuxt route guard can dynamically re-route the original URL to a specific slug page.

How can I use router in store for Quasar

I'm trying to redirect to dashboard after login.
I found this in app.js file that
// make router instance available in store store.$router = router
So I codede that in my login method of auth.js file like below.
store.$router.push({ name: 'dashboard' })
But there is nothing to happen.
How can I use router in vuex store file?
Router is not available in the store directly. You might have to use plugin which makes the router available to the store.
Here is the Vuex-Router plugin which helps to access router to the store.
Are you trying to call router.push in an vuex action?
If so i am doing the same in my quasar v2 project
store/auth/actions.js
import { api } from 'boot/axios'
export function login({ dispatch, commit }, data) {
return api.post('/user/login', data).then(res => {
commit('setToken', res.data.token)
this.$router.push({ name: 'dashboard' }) // <-- What you are looking for?
}).catch(err => {
let msg = err.response.data || 'Error occurred'
return Promise.reject(msg)
})
}

Loading different components based on route param in Vue Router

I'm building an editor app which supports multiple design templates. Each design template has wildly different set of fields so they each has their own .vue file.
I'm trying to dynamically load the corresponding view component file based on params. So visiting /editor/yellow-on-black would load views/designs/yellow-on-black.vue etc.
I've been trying to do it like this
{
path: '/editor/:design',
component: () => {
return import(`../views/designs/${route.params.design}`)
}
}
But of course route is not defined. Any idea on how to work around this?
The route's component option is only evaluated once, so that won't work. Here's a solution using a Dynamic.vue view which uses a dynamic component based on the route param.
Use a simple route definition with route param. I changed the param name to dynamic:
import Dynamic from '#/views/Dynamic.vue';
{
path: "/editor/:dynamic",
component: Dynamic
}
Create a generic Dynamic.vue component that dynamically loads a component from the route param. It expects the param to be called dynamic:
<template>
<component v-if="c" :is="c" :key="c.__file"></component>
</template>
<script>
export default {
data: () => ({
c: null
}),
methods: {
updateComponent(param) {
// The dynamic import
import(`#/components/${param}.vue`).then(module => {
this.c = module.default;
})
}
},
beforeRouteEnter(to, from, next) {
// When first entering the route
next(vm => vm.updateComponent(to.params.dynamic));
},
beforeRouteUpdate(to, from, next) {
// When changing from one dynamic route to another
this.updateComponent(to.params.dynamic);
next();
}
}
</script>

Vue router allways redirecting to error page

i am trying to setup a redirect when the user is not logged in. But when i do it like in my example the URL changes but i get This page could not be found from nuxt. The code is inside an login.js inside the plugins folder. Then i included this in the nuxt config like this.
plugins: [
'~/plugins/login.js'
],
And here is the actual code for handling redirecting
export default ({ app, store }) => {
app.router.beforeEach((to, from, next) => {
const loggedIn = store.state.account.loggedInAccount
if (!loggedIn) {
if (to.path !== '/redirect') {
next({ path: '/redirect' })
} else {
next()
}
} else {
next()
}
})
}
It looks like the routes are not mounted yet.
You should try to use middleware. It is the conventional way to implement the beforeEach function as mentioned by the official docs. You can read about it from here. If have access to the route object, store object and redirect function inside the middleware, so use redirect to direct to the other routes after validation.

Nuxt serverMiddleware get json from API

Instead of getting redirects from 301.json I want to make a request to my api which returns my json.
I am using the #nuxtjs/axios module.
const redirects = require('../301.json');
export default function (req, res, next) {
const redirect = redirects.find(r => r.from === req.url);
if (redirect) {
console.log('redirect: ${redirect.from} => ${redirect.to}');
res.writeHead(301, { Location: redirect.to });
res.end();
} else {
next();
}
}
Original answer
To build on #Dominooch's answer, if you want to return just JSON, you can use the .json() helper. It automatically sets the content-type to application/json and stringify's an object you pass it.
edit:
To clarify what we're doing here, we're replacing your 301.json entirely and using nuxt's way of creating middleware to:
define a generic handler that you can reuse for any route
defining explicitly which paths will use your handler (what I'm assuming you're 301.json is doing)
If 301.json is really just an array of paths that you want to redirect, then you can just use .map() but i'd personally not, because it's not immediately clear which paths are getting redirected (see my last sample)
That said, the very last thing I would avoid is making a global middleware (fires for every request) that checks to see if the path is included in your array. <- Will make route handling longer for each item in the array. Using .map() will make nuxt do the route matching for you (which it already does anyways) instead of sending every request through your handler.
// some-api-endpoint.js
import axios from 'axios'
export default {
path: '/endpoint'
handler: async (req, res) => {
const { data } = await axios.get('some-request')
res.json(data)
}
}
Then in your nuxt.config.js:
// nuxt.config.js
module.exports = {
// some other exported properties ...
serverMiddleware: [
{ path: '/endpoint', handler: '~/path/to/some-api-endpoint.js' },
]
}
If 301.json is really just an array of paths:
// nuxt.config.js
const routes = require('../301.json');
module.exports = {
// some other exported properties ...
serverMiddleware: routes.map(path =>
({ path, handler: '~/path/to/some-api-endpoint.js' }))
}
Or if you have other middleware:
// nuxt.config.js
const routes = require('../301.json');
module.exports = {
// some other exported properties ...
serverMiddleware: [
...routes.map(path =>
({ path, handler: '~/path/to/some-api-endpoint.js' })),
... // Other middlewares
}
Here's what I did and it seems to work:
//uri-path.js
import axios from 'axios'
export default {
path: '/uri/path',
async handler (req, res) {
const { data } = await axios.get('http://127.0.0.1:8000/uri/path')
res.setHeader('Content-Type', 'text/html')
res.end(data)
}
}