How to use a 404 component in VUE3 single page app - javascript

I have the following structure of the code:
router.js:
import { createRouter, createWebHistory } from 'vue-router';
const router = createRouter({
history: createWebHistory(),
routes: [
{
path: '/',
name: 'App',
component: () => import('../../src/App.vue'),
},
{
path: '/:pathMatch(.*)',
name: 'ErrorView',
component: () => import('../components/Error.vue'),
}
],
});
export default router;
App.vue
<template>
<Land />
<LeftNavbar />
<Navbar />
<Experience />
<TechnologiesCarousel />
<Projects />
<Copyright />
<BackToTop />
</template>
When I'm pressing in the URL bar: http://localhost:3000. The app is rendering properly which is fine, but when I'm trying to write a wrong URL, for eg: http://localhost:3000/abcf/ || http://localhost:3000/dsafbdmhgfjweghjfw to be redirected to the 404 page, I'm not redirected, the page still rendering the App.vue component.
Does anyone have any idea why the 404 page isn't rendered?

Try like following /:pathMatch(.*)*:
{
path: "/:pathMatch(.*)*",
name: 'ErrorView',
component: () => import('../components/Error.vue'),
},
and in App.vue place <router-view /> and move components to other view.

I assume you are using Vue 3.
In your router file you need to change the path to this:
{
path: '/:pathMatch(.*)*',
name: 'ErrorView',
component: () => import('../components/Error.vue'),
}
For Vue 2 the path can be: path:'*'
From Vue 2 -> 3 migration some more info for those they want to know what has changed:
Essentially, you should be able to replace the '' path with '/:pathMatch(.)*' and be good to go!
Reason: Vue Router doesn't use path-to-regexp anymore, instead it implements its own parsing system that allows route ranking and enables dynamic routing. Since we usually add one single catch-all route per project, there is no big benefit in supporting a special syntax for *.

Related

How to work on lazy load(async loading) Vue without vue-cli or node?

I am very new to vue. I know that this one is easy with vue-cli but I need to learn this first without using the node.
I was making many template files and pages in different js files. Suddenly it hit my mind that what if a lot of file is requested and downloaded?
What I am trying to do is I am trying to fuse route and async component loading together for loading different pages when they are called.
Is there a way to do this? This is the code that I tried for my initial project.
<html>
<head>
<script src="vue.js"></script>
<script src="vue-route.js"></script>
<script src="axios.min.js"></script>
</head>
<body>
<div id="app">
<h1>Hello App!</h1>
<p>
<router-link to="/">Go to Home</router-link>
<router-link to="/about">Go to About</router-link>
</p>
<router-view></router-view>
</div>
<script>
//axios.get("https://shohanlab.com")
const Home = { template: '<div>Home</div>' }
const AsyncComp =
() =>
new Promise((resolve, reject) => {
resolve({
template: About
})
})
const routes = [
{ path: '/', component: Home },
{ path: '/about', component: AsyncComp },
]
const router = VueRouter.createRouter({
history: VueRouter.createWebHashHistory(),
routes, // short for `routes: routes`
})
const app =Vue.createApp({})
app.use(router)
app.mount('#app')
</script>
</body>
</html>
As we can see in the code that The About.js is called even before I call it. In this way the page size will be very big.
Vue is primarily intended to be used with Vue CLI or other Node setup with a build step. The documentation and virtually all examples cover this scenario and assume that .vue SFC are used. It's still possible to use Vue 3 with vanilla JavaScript but this way lacks the documentation, features and a lot of third-party Vue libraries that cannot be used without being built.
It's possible to achieve this with lazy loading (as another answer noted). Native ES modules can be used for the same purpose to avoid a build step. Entry point needs to be a module either.
A demo:
index.html
<div id="app">
<h1>Hello App!</h1>
<p>
<router-link to="/">Go to Home</router-link>
<router-link to="/about">Go to About</router-link>
</p>
<router-view></router-view>
</div>
<script type="module" src="script.js"></script>
about.js
export default { template: '<div>About</div>' }
script.js
const routes = [
{ path: '/', component: { template: '<div>Home</div>' } },
{ path: '/about', component: () => import('./about.js') },
]
const router = VueRouter.createRouter({
history: VueRouter.createWebHashHistory(),
routes,
})
const app =Vue.createApp({})
app.use(router)
app.mount('#app')
You can lazily load views as follows:
const routes = [
{
path: '/',
name: 'Dashboard',
component: () => import('../views/Dashboard.vue')
}
]
See the official docs for more info

Vue-router keeps appending # to existing routes. P.S this isn't the typical hash, history mode issue

Using Vue 3, i have my router file set up this way
import { createRouter, createWebHistory } from "vue-router";
import Home from "../views/Home.vue";
const routes = [
{
path: "/",
name: "Home",
component: Home,
},
{
path: "/Portfolio",
name: "Portfolio",
component: () =>
import(/*webpackChunkName: "DestinationDetails" */ "../views/Portfolio"),
},
{
path: "/Services",
name: "Services",
component: () =>
import(/*webpackChunkName: "DestinationDetails" */ "../views/Services"),
},
{
path: "/details/:id",
name: "PortfolioDetails",
component: () =>
import(
/*webpackChunkName: "DestinationDetails" */ "../views/PortfolioDetails"
),
},
{
path: "/:pathMatch(.*)*",
redirect: "/Home",
},
];
const router = createRouter({
history: createWebHistory(process.env.BASE_URL),
routes,
});
export default router;
I am also running a v-for loop to get paths from an API "https://api.fake.rest/ca2a6662-22d0-4010-ba08-0440ffe813ab/menu". 3 of the 5 url paths have a value of "#". the remaining two have normal paths.
<div
v-for="(men, index) in webMenu.menu_items"
:key="index" class=" mt-32"
>
<!-- <SidebarLink class="w-full" :to="{path:men.url}" icon="fas fa-home">{{
men.name
}}</SidebarLink> -->
<router-link class="w-full" :to="men.url"> {{men.name}} </router-link>
</div>
<p class="font-bold">{{webMenu.menu_text}}</p>
</div>
Problem is now when the webpage loads initially, it works fine but after clicking on the portfolio or services link, the paths to the others are changed.
e.g if i was on the portfolio page and tried switching back to the home page, it would change the route path to "portfolio#" and refuse to switch pages.
Can anyone help explain why this is and a possible way to resolve it?
I was also facing the similar issue.
For me the issue was that I was redirecting to new page using router.push() on click of a div.
And I was using #click.stop = "myFunction" . This was adding a # in the url and not redirecting the user.
I tried different variation of this as well. I tried using event object as well like event.stopPropagation(). With this as well got same result.
When I used #click.prevent, this everything worked as expected.
PS:
You can use catch block to trace the error as well.
this.$router..push({
name: "PAthName",
params: { id },
query: { id }
})
.catch(e => {
console.log("Errors", e);
});
Should have updated this earlier.
The fix was relatively simple, all i did was change the binding from
<router-link class="w-full" :to="men.url"> {{men.name}} </router-link>
to
<router-link class="w-full" :to="`/${men.url}`"> {{men.name}} </router-link>
and voila!

Vue - Pass components to router-view on dynamically loaded component

I need to render a different layout for the same route for a specific URI with different components depending on the user being on mobile or in desktop.
I would like to avoid having route path checks in the PageCommon(layout component) to keep it clean.
The app has a main component taking care of the layout, it has different router-views where we load the different components for each page URI. This would be a normal route for that.
{
path: '',
component: PageCommon,
children: [
{
path: '',
name: 'Home',
components: {
default: Home,
header: Header,
'main-menu': MainMenu,
'page-content': PageContent,
footer: Footer,
'content-footer': ContentFooter
}
},
I can't change the route components property once the component is loaded so I tried to make a wrapper and pass the components dynamically.
{
path: 'my-view',
name: 'My_View',
component: () => import('#/components/MyView/ViewWrapper')
},
In /components/MyView/ViewWrapper'
<page-common v-if="isMobile">
<my-mobile-view is="default"></my-mobile-view>
<main-menu is="main-menu"></main-menu>
</page-common>
<page-common v-else>
<my-desktop-view is="default"></my-desktop-view>
<header is="header"></header>
<main-menu is="main-menu"></main-menu>
<footer is="footer"></footer>
</page-common>
</template>
I would expect that the components passed inside page-common block would be substituted on the appropriate , but is not how it works, and Vue just loads page-common component with empty router-views.
Is there any approach for this?
Note that I already tried using :is property for loading different components, but the problem then is on how to tell the parent to use this or that component for this page. This is the code for that:
<template>
<component :is="myView"></component>
</template>
<script>
import DesktopView from "#/components/MyView/DesktopView";
import MobileView from "#/components/MyView/MobileView";
export default {
name: 'MyView',
components: {
DesktopView,
MobileView,
},
data(){
return {
myView: null,
isMobile: this.detectMobile()
}
},
methods : {
getViewComponent() {
return this.isMobile ? 'mobile-view' : 'desktop-view';
}
},
created() {
this.myView = this.getViewComponent();
}
}
</script>
I could use this approach for each of the PageCommon router views, creating a component for each that does the above, but it looks like a very bad solution.
A computed method is all you need.
You should have this top level Logic in App.vue and the <router-view> should be placed in both DesktopView and MobileView.
// App.vue
<template>
<component :is="myView"></component>
</template>
<script>
import DesktopView from "#/components/MyView/DesktopView";
import MobileView from "#/components/MyView/MobileView";
export default {
name: 'MyView',
components: {
DesktopView,
MobileView,
},
computed: {
myView() {
return this.detectMobile() ? 'mobile-view' : 'desktop-view';
}
}
}
</script>
You may also want to consider code splitting by setting up Dynamic Components for those layouts since Mobile will load Desktop View because it is compiled into final build, register them globally as dynamic imports instead if importing them in MyView and then delete components also after doing the following instead, this way only the one that is needed will be downloaded saving mobile users their bandwidth:
// main.js
import LoadingDesktopComponent from '#/components/LoadingDesktopComponent '
Vue.componenet('desktop-view', () => ({
component: import('#/components/MyView/DesktopView'),
loading: LoadingDesktopComponent // Displayed while loading the Desktop View
})
// LoadingDesktopComponent .vue
<template>
<div>
Optimizing for Desktop, Please wait.
</div>
</template>
<script>
export default {
name: 'loading-component'
}
</script>
Routing logic will only be processed when <router-view> is available,this means you can delay the presentation of Vue Router, for example you can have :is show a splash screen like a loading screen on any URI before displaying a component in :is that contains <router-view>, only than at that point will the URI be processed to display the relevant content.

Components not loading into page upon path switch (Vue.js quasar framework)

When switching paths in Vue (using the quasar framework) the path switches but the components don't load into the page. Then, when I click refresh, the components load. Is there a way to write code that will refresh the page so the components load in?
<template>
<div>
<q-btn
size="40px"
round
color="teal"
label="F"
to="/"
/>
</div>
</q-page>
</template>
When I click the button, I would like it to go to the / page (below) and load the maps and coordinates components in, but it doesn't do that.
<template>
<coordinates />
<maps />
</template>
<script>
export default {
components: {
'coordinates' : require('components/coordinates.vue').default,
'maps' : require('components/maps.vue').default
},
ROUTER CONFIGURATION:
const routes = [
{
path: '/',
component: () => import('layouts/MyLayout.vue'),
children: [
{ path: '/', component: () => import('pages/PageUsers.vue') },
{ path: '/auth', component: () => import('pages/PageAuth.vue') },
{ path: '/buttons', component: () => import('pages/PageButtons.vue') }
]
}
]
Thanks!
I ended up fixing the problem in a way that is a bit hacky, but I use local storage to refresh the page.
load() {
if( window.localStorage )
{
if( !localStorage.getItem('firstLoad') )
{
localStorage['firstLoad'] = true;
window.location.reload();
}
else
localStorage.removeItem('firstLoad');
}
}

Vue router not loading component

So ive been looking into vue and been experiencing an issue which i cant seem to find the solution for. Im using Vue and Vue-router. I started with the basic vue + webpack template which gave the initial boilerplate.
I've successfully added additional routes to the predefined routes which is working as expected (games, tournaments, stats and users routes works just fine). However now im unable to get additional routes to work. the "gaming" route doesnt work, ive also tried adding additional routes which does not seem to work either.
So this is my current router file (index.js):
import Vue from 'vue'
import Router from 'vue-router'
const Gaming = () => import('#/components/gaming.vue');
const Home = () => import('#/components/home.vue');
const Game = () => import('#/components/game.vue');
const Tournament = () => import('#/components/tournament.vue');
const Users = () => import('#/components/users.vue');
const Stats = () => import('#/components/stats.vue');
const router = new Router({
routes: [
{
path: '/',
name: 'Home',
component: Home
},
{
path: '/games',
name: 'Game',
component: Game,
},
{
path: '/wtf',
name: 'Gaming',
components: Gaming,
},
{
path: '/tournaments',
name: 'Tournament',
component: Tournament
},
{
path: '/users',
name: 'Users',
component: Users
},
{
path: '/stats',
name: 'Stats',
component: Stats
}
]
});
export default router;
Vue.use(Router);
All my routes works as expected except the "Gaming" route. The "Gaming" component looks like this:
<template>
<div>
<h1>WTF?!?!?!?!?=!?!</h1>
</div>
</template>
<script>
export default {
name: 'Gaming',
components: {},
data() {
return {}
},
}
</script>
<!-- Add "scoped" attribute to limit CSS to this component only -->
<style>
</style>
Ive tried to pretty much copy/paste a working component, And only change the name, as well as the template. But it seems to have issues. Initially i had done the route component imports the normal "boilerplate" way
import Stats from '#/components/Stats'
Which pretty much had the same result, Except this would cause an exception when attempting to navigate to the "gaming" route.
Cannot read property '$createElement' of undefined
at render (eval at ./node_modules/vue-loader/lib/template-compiler/index.js?{"id":"data-v-c9036282","hasScoped":false,"transformToRequire":{"video":["src","poster"],"source":"src","img":"src","image":"xlink:href"},"buble":{"transforms":{}}}!./node_modules/vue-loader/lib/selector.js?type=template&index=0!./src/components/gaming.vue (app.js:4240), <anonymous>:3:16)
All other routes worked. Ive also tried to re-create all the files and re-do the specific route which doesnt seem to work either. So im at a loss of what i can do to fix this issue?
Here i attempt to inspect the route, And as you can see the component is missing "everything"
Inspecting the route
Also tried looking with the vue addon for chrome, Where the component does not get loaded into the view
Vue Chrome Extension
Uploaded the project to gdrive if someone want to tweak around with it
Google Drive Link
Found the issue:
const router = new Router({
routes: [
{
path: '/',
name: 'Home',
component: Home
},
{
path: '/games',
name: 'Game',
component: Game,
},
{
path: '/wtf',
name: 'Gaming',
components <----------: Gaming,
// Should be component not componentS
},
{
path: '/tournaments',
name: 'Tournament',
component: Tournament
},
...
Also, you should use the standard method of importing. Without that error, I would've never found the issue.

Categories