Dynamically choose compontent in Nuxt Router - vue.js

I would like to render the same component for all languages. The translation will be done inside the component. The url and component relationship should look like so:
pseudocode:
browserurl: "/blogpost_1"
nuxtcomponent: "blogpost_1"
browserurl: "/en/blogpost_1"
nuxtcomponent: "blogpost_1"
browserurl: "/randompage"
nuxtcomponent: "randompage"
browserurl: "/en/randompage"
nuxtcomponent: "randompage"
My approach was to do the following, unfortunately i cant find a way to access pathMatch.
router: {
extendRoutes(routes, resolve){
routes.push({
path: 'en/*',
component: resolve(__dirname, '/' + somehow.access.pathMatch )
})
}
}

I don't think you can resolve component dynamically. component: expects component instance or Promise. Once the promise resolves, result is cached. I can imagine solution using navigation guards and switching components "by hand" but that would require to import views by hand etc.
So your best bet is to rewrite paths generated by nuxt to include optional path segment (from /about to /:lang(en|jp)?/about so it accepts paths like /en/about) - your component then receives lang parameter which will be empty for /about otherwise it will contain language....
Define available translations in meta
Rewrite the paths of pages with translations
In your page:
<script>
export default {
meta: {
translations: ['en', 'jp']
}
}
</script>
In Nuxt config (pseudo - not tested)
router: {
extendRoutes(routes, resolve) {
const routesWithTranslation = routes.filter(i => i.meta.translations && i.meta.translations.length > 0);
routesWithTranslation.forEach(route => {
const segment = `/:lang(${route.meta.translations.join("|")})?`
route.path = segment + route.path
})
}

Related

Is there any way to have dynamic routes/components in vuejs router?

Hi beautiful Vuejs developers out there!
I have a little problem with routing many Vue components/pages dynamically. In this scenario I am using nested routes to have a couple of routes for my layout components and hundreds of child routes for my pages and as you can imagine I'll have to type many child routes statically or manually, and then add more when I need more child routes in the future code changes but I need a solution to simplify/solve this problem with more efficient/better way like adding those routes from what user types after the layout in the url... here is my example code code:
const routes: RouteRecordRaw[] = [
{
{
path: '/student',
component: () => import('layouts/StudentLayout.vue'),
children: [
{
path: 'dashboard',
component: () => import('pages/student/Dashboard.vue'),
},
{
path: 'profile',
component: () => import('pages/student/Profile.vue'),
},
],
},
}
As you see in this code I have a layout named Student and it has two children but I'll have to type manually hundreds of child routes for this layout and other layouts is there any way to dynamically set up those routes with what users enter after the layout name like /student/dashboard or /layout/page and match it with a component name? I mean like params in Angular, can I use the param value itself inside the router to say?
{
path: ':pagename',
component: (pagename) => import('pages/student/' + pagename + '.vue'),
},
let me know if there is an efficient way to solve this problem.
Thanks in advance!
I would, personally, not use this, or advise such an approach, nor have I done it, but this idea came to me when I read your question:
My best guess would be to have a handler component which renders a component dynamically based on a route parameter (say you have /:subpage as a child to /student and the handler component is set to that route), and an exception handler around that to show a 404 page when the user types in an inexistent/unsupported route.
For example, I would dynamically import the component by the route parameter into a predefined let (e.g. let SubpageComponent; outside the try catch block), have a try catch block around the dynamic import assignment for the respective error where catch would set the variable to a 404 page. Then I would add the SubpageComponent into the data() of the component doing the rendering of the route.
Edit
I've written out come code that, maybe, makes sense.
It's based on https://v2.vuejs.org/v2/guide/components.html#Dynamic-Components
your routes definition, changed
const routes: RouteRecordRaw[] = [
{
path: '/student',
component: () => import('layouts/StudentLayout.vue'),
children: [
{
path: '/:subpage',
component: () => import('pages/student/SubpageRenderer.vue'),
props: true,
},
],
},
]
SubpageRenderer.vue
<script>
export default {
props: ['subpage'],
data() {
return {
currentSubpage: () => import(`./${subpage}.vue`)
}
}
}
</script>
<template>
<component :is="currentSubpage"></component>
</template>
Instead of using the currentSubpage import, you can also use the subpage route prop to bind :is if subpage is the name of a registered component.
Since this would get only "dashboard" from the route, you'd need some namespacing, like "student-dashboard" with the help of template literals. You could make currentSubpage into a template literal that creates the student-${subpage} name.
I'd probably recommend importing the options object of the component designated by the subpage route parameter instead of registering all the components - if you're registering them, you might as well use vue-router the usual way :)
Also, I only think this could work! It should be tested out, and perhaps casing should be kept in mind, and maybe the Layout suffix as well (subpage will probably be all lowercase, and you'll probably have the components named in PascalCase). After uppercasing the first letter, this could also obviously lead to both /student/Dashboard and /student/dashboard being valid routes

How do I properly import multiple components dynamically and use them in Nuxt?

I need to implement dynamic pages in a Nuxt + CMS bundle.
I send the URL to the server and if such a page exists I receive the data.
The data contains a list of components that I need to use, the number of components can be different.
I need to dynamically import these components and use them on the page.
I don't fully understand how I can properly import these components and use them.
I know that I can use the global registration of components, but in this case I am interested in dynamic imports.
Here is a demo that describes the approximate logic of my application.
https://codesandbox.io/s/dank-water-zvwmu?file=%2Fpages%2F_.vue
Here is a github issue that may be useful for you: https://github.com/nuxt/components/issues/227#issuecomment-902013353
I've used something like this before
<nuxt-dynamic :name="icon"></nuxt-dynamic>
to load dynamic SVG depending of the icon prop thanks to dynamic.
Since now, it is baked-in you should be able to do
<component :is="componentId" />
but it looks like it is costly in terms of performance.
This is of course based on Nuxt components and auto-importing them.
Also, if you want to import those from anywhere you wish, you can follow my answer here.
I used this solution. I get all the necessary data in the asyncData hook and then import the components in the created () hook
https://codesandbox.io/s/codesandbox-nuxt-uidc7?file=/pages/index.vue
asyncData({ route, redirect }) {
const dataFromServer = [
{
path: "/about",
componentName: 'myComponent'
},
];
const componentData = dataFromServer.find(
(data) => data.path === route.path
);
return { componentData };
},
data() {
return {
selectedRouteData: null,
componentData: {},
importedComponents: []
};
},
created() {
this.importComponent();
},
methods: {
async importComponent() {
const comp = await import(`~/folder/${this.componentData.componentName}.vue`);
this.importedComponents.push(comp.default);
}

Component dynamic import doesn't work from router.js

With Nuxt.js, in my router.js I'm trying to import my route component thus:
{
path: '/',
component: () => import(/* webpackChunkName: "index" */ '~/pages/index.vue')
}
I get this error:
render function or template not defined in component: anonymous
I came across someone else's Nuxt.js project where they add the following at the end, and with this it works:
{
path: '/',
component: () => import(/* webpackChunkName: "index" */ '~/pages/index.vue').then(m => m.default || m)
}
The returned object of my import() looks like this:
In a another Vue.js, non-Nuxt project, the same kind of import() looks like that:
In both cases, the component is nested in some "default" object. But with Nuxt.js it seems you must import that object explicitly, whereas with regular Vue.js you don't have to specify that.
Why?
Regarding why the module is nested inside a { default } object: it's because Nuxt uses ES6 style modules. ES6 style modules are written the following:
export default {
// some object
};
Default is not the only property you can have. More syntax.
Regarding why vue-router are working without the .default, it's because the code is more versatile than the code used by Nuxt. Nuxt already ships it's own builder and always uses ES6 modules.
On the other hand, vue-router does not know about how it will be built. So to be easier to use, vue-router automatically checks if the module is ES6 or not.
const resolve = once(resolvedDef => {
if (isESModule(resolvedDef)) {
resolvedDef = resolvedDef.default
}
// ...
}
Source: code of vue-router.
As you may already know, it's very uncommon to use a router.js file in a Nuxt project, because Nuxt already has its own routing system which splits your code into chunks for each page. Documentation here.

Vue.js routing not working by using parameter as a route

I'm trying to use parameter only as a route as I need URL like localhost:8080/road. But, it's not working properly.
My code:
{
path: "/:id",
name: "id",
component: Blog
},
Whenever I enter a url like localhost:8080/dashboard or any other URL, it uses the Blog component. How can I solve it?
If your goal is to cut down on lines of code in your router definition, you could optimise this somewhat with an array of component names mapped to route definitions. For example
const components = ["Blog", "Road", "Dashboard"]
// creates kebab-cased slugs from Pascal cased component names
const toSlug = component =>
component.replace(/(?<=\w)[A-Z]/g, c => `-${c}`).toLowerCase()
const routes = components.map(name => ({
name,
path: toSlug(name),
component: () => import(`#/components/${name}.vue`)
})
Using static parts in the import path like #/components/ and .vue help Webpack optimise bundling.

How to Properly Use Vue Router beforeRouteEnter or Watch to trigger method in Single File Component?

I'm working on an app in Vue.js using Single File Components and Vue Router. I have a Search component where I need to execute a method to re-populate search results each time a user visits the route. The method executes correctly the first time the route is visited because of the "create" hook:
created: function() {
this.initializeSearch();
},
However, when the user leaves the route (to register or log into the app for instance), and returns to the Search page, I can't seem to find a way to automatically trigger this.initializeSearch() on subsequent visits.
Routes are set up in index.js like so:
import Search from './components/Search.vue';
import Login from './components/Login.vue';
import Register from './components/Register.vue';
// Vue Router Setup
Vue.use(VueRouter)
const routes = [
{ path: '/', component: Search },
{ path: '/register', component: Register },
{ path: '/login', component: Login },
{ path: '*', redirect: '/' }
]
export const router = new VueRouter({
routes
})
I gather that I should be using "watch" or "beforeRouteEnter" but I can't seem to get either to work.
I tried using "watch" like so within my Search component:
watch: {
// Call the method again if the route changes
'$route': 'initializeSearch'
}
And I can't seem to find any documentation explaining how to properly use the beforeRouteEnter callback with a single file component (the vue-router documentation isn't very clear).
Any help on this would be much appreciated.
Since you want to re-populate search results each time a user visits the route.
You can use beforeRouteEnter() in your component as below:
beforeRouteEnter (to, from, next) {
next(vm => {
// access to component's instance using `vm` .
// this is done because this navigation guard is called before the component is created.
// clear your previously populated search results.
// re-populate search results
vm.initializeSearch();
})
}
You can read more about navigation guards here
Here is the working fiddle