I've been struggling with this for a few days and was hoping for a graceful way of handling dynamic URLs with no data.
I have the following routes:
const router = new VueRouter({
routes: [
{path: '/product/:slug', component: Product},
{path: '/404', component: PageNotFound, alias: '*'}
]
});
In the Product component, I have an object of products and, depending on the slug variable, load the product to show.
The issue i'm having is when the URL is a slug that does not exist in the products dataset. I would like to load the PageNotFound component, without updating the URL.
Is this possible? It would be nice to have a consistent 404 page throughout the app and would also be good for me not to have to repeat myself with a v-if in the product table.
The closest I've got to it is this:
if(!product) {
this.$router.replace({path: '/404', query: {product: this.$route.params.slug}});
}
However, this updates the actual URL which is not very good UX.
Any clues?
You could conditionally render your PageNotFound component in Product.vue if the query returns no results, and then not have to fiddle with your router at all.
Thanks to Kyle pointing me in the right direction, this is what I came up with.
Becuase I am being slightly unorthodox and using server-side components and JavaScript, I already had my page not found component loaded - which looks like this:
const PageNotFound = {
name: 'PageNotFound',
template: `<div>
<h1>404 Page Not Found</h1>
<p>Head back to the <router-link to="/">home page</router-link> and start again.</p>
</div>`
};
I made sure the PageNotFound.js file was loaded in the HTML before my product component, so I was able to do the following:
const ProductPage = {
name: 'ProductPage',
template: `<div>
<div v-if="product"><h1>{{ product.title }}</h1></div>
<page-not-found v-if="notFound"></page-not-found>
</div>`,
components: {
PageNotFound
},
data() {
return {
notFound: false
}
},
computed: {
product() {
let product;
if(Object.keys(this.$store.state.products).length) {
product = this.$store.state.products[this.$route.params.slug];
if(!product) {
this.notFound = true;
}
}
return product;
}
}
};
Things to note in the above:
Data is being loaded asynchronously, hence the check to see if products exist
The PageNotFound component is loaded in - this is ES6 for PageNotFound: PageNotFound - Vue then automatically makes a <page-not-found></page-not-found> element
That element then has a v-if which gets triggered. As the first container would not be in existence if there is no product, only the 404 component is displayed
I don't do it based on product, as you would get a flash of the 404 if the product data was still loading via an API.
It's better practice to have the URL params as props (see docs), which I will be doing at some point!
To conclude, this allows you to show a consistent 404 page throughout your SPA (single page application) while maintaining URLs with dynamic routes. It allows you to load another component or show another component without updating the URL and also lets you have a wildcard 404 for dynamic routes.
Hope that all makes sense and helps someone in the future and saves them from wasting ~4 hours of trial, error and googling. (and yes I have "keyword" and phrase stuffed this answer to help someone find it...)
Related
I am creating dynamic pages using Nuxt. In the pages folder I have one file _url.vue. It contains the following code:
<template lang="pug">
div
component(
v-for="component in components"
:key="`${component.type}-${component.id}`"
:is="`the-${component.type}`"
)
</template>
<script>
// import vuex
export default {
computed: {
...mapGetters('app', {
components: 'getComponents'
})
}
}
</script>
setComponents happens at the middleware level:
export default async function ({ store }) {
await store.dispatch('app/setPage')
}
In the first milliseconds of page load, the content "jumps" as the components are rendered on the fly. How can this situation be corrected?
I'd first try to import the components manually, to see where this all comes from: the components taking some time to get injected or the layout being displayed, just to be sure.
Then, I had a discussion about it here, you may give it a look: Vue: wait to render until all components are mounted
There are several ways of handling this kind of micro-jumping explained there. You can choose your own solution. Also depends if you're using your app as universal or SPA only.
Looks like require is a way to go but some alternative are also available.
The Problem:
Every time I navigate between the list page (home) and detail page my site starts to slow down drastically. With this I mean, after like 3 times going back and forth I start noticing it having hiccups, after 5-6 times my whole pc starts to freeze.
My Project:
It's a Vue Project, with currently only 2 routes. The homepage is a list of items and a detail page for every item on the list. The detail page (specifically the tree component) is probably where the issue is because when I remove this, the problem is gone. I put some code at the bottom of this post with the basic structure of the project.
What I'm looking for:
Since I'm not getting any errors, I'm not sure where the problem is here. There is probably something I can do better in the way my project is set up, the way I load/show things. So I'm looking for ways to find out where the problem is at.
What I tried:
Stay Alive
While searching for solutions I came across the <stay-alive> tag. I tried putting this around my <router-view>. This does get rid of the slowing down, but I also lose all my dynamic content. The data on all pages is now the same when I navigate between different detail pages.
Data Fetching (https://router.vuejs.org/guide/advanced/data-fetching.html#fetching-after-navigation)
I was thinking, maybe it helps if I load all the data before someone enters a route or do some kind of loading before I show the page. This did not help.
It's possible that one of these things is the right direction, and I just didn't implement it right. Not 100% confident with my coding yet :)
views/home.vue
Just a simple page with a list of items that link to the detail page
<template>
// list with items that link to their corresponding detail page
</template>
import { mapState } from 'vuex'
export default {
name: 'Home',
computed: {
...mapState([
'builds'
])
}
}
views/details.vue
Now, this page is a little more complex. The big thing on this page is a canvas that is generated with Pixi Js, this canvas changes while the user is scrolling through the page. The Canvas element is its own component, so I pass some data with a prop.
<template>
<div class='page-wrapper'>
<div class="content-container">
<section class="level milestone" v-for="level in build.guide" :key="level.id" :id="level.level">
// Some stuff to display
</section>
<div class="sidebar">
<tree :myprop="current"></tree>
</div>
</div>
</div>
</template>
import { mapState } from 'vuex'
import Tree from '#/components/tree'
export default {
name: 'Build',
watch: {
currentActive: {
// To know where to user currently is
// Pass this data to my tree component
}
},
computed: {
...mapState([
'builds'
])
}
}
components/tree.vue
This is where my canvas is drawn with the help of data from a JSON file.
<template>
<div id="tree">
</div>
</template>
import axios from 'axios'
import * as PIXI from 'pixi.js'
import { Viewport } from 'pixi-viewport'
export default {
name: 'Tree',
props: ['myprop'],
data () {
return {
app: new PIXI.Application({ transparent: true, antialias: true }),
treeData: {},
// Some more
}
},
watch: {
myprop: function (newVal) {
// Some stuff with my prop
}
},
created () {
axios.get('/data/data.json', {
headers: {
'Content-Type': 'application/json'
}
})
.then(response => {
this.treeData = response.data
this.loadTree()
})
.catch(function (error) {
console.log(error)
})
},
methods: {
loadTree () {
// Load images and draw the canvas
PIXI.loader
.add('Image', require('#/assets/image.png'))
.load(this.drawTree)
},
drawTree () {
// Do all the drawing on the canvas
}
}
}
Oke, so i've kind of solved it for now. I'm still new to all this, but i started to look more into working with dev tools so i can find out where issues like this come from. I think i still can win a lot with this, but for now it helped to destroy my PIXI app from data when i'm done with it.
beforeDestroy () {
this.app.destroy()
}
Maybe some usefull links if someone ever finds this thread that has similar issues:
Using dev tools to find memory leaks:
https://www.youtube.com/watch?v=Hr2vrhrNaRo
https://www.youtube.com/watch?v=RJRbZdtKmxU
Avoiding memory leaks in vue:
https://v2.vuejs.org/v2/cookbook/avoiding-memory-leaks.html
I'm rendering dynamically a list of posts using next/router. This list contains the index, path, and title of every post is:
export const items = [
{
id: "0",
path: 'Post_Alpha',
title: "Title example 0",
},
{
id: "1",
path: 'Post_Beta',
title: "Title example 1",
},
I can access to the post beta by its index in the array:
import {items} from '../posts/data'
function Posts() {
const router = useRouter()
const {post} = router.query
const postIndex = items.map(function(e) { return e.path; }).indexOf(post)
const post = items[courseIndex]
return (
<h2>
{post.title}
</h2>
)
}
export default Posts
I wonder whether Next.js creates a page for every post or it only creates one that is dynamically populated. I have a list of links that use <Link /> from 'next/link' to go to every post. In addition, how can I see the pages that Next.js is generating, like the sitemap.xml of a site?
I do not think it does.
I think that only happens when you use the getStaticProps or getServerSideProps. In your case, your pages do not have the features of using Next.js as a service, such as good SEO. You just use client-side fetching, which does not take advantage of any of the benefits of Next.js. You could check also the useSWR() hook for fetching data on the client.
Edit
I believe you should follow this example from Next.js Documentation if your data are not so many and are not updated frequently. Only use, if you qualify both of the above requirements, as the data are static and will be generated at build time.
This question already has answers here:
How can I bind the html <title> content in vuejs?
(9 answers)
Closed 5 years ago.
I'm building a web application using vue.js v1.0, vue-router v0.7 and WebPack. I'm following Single File Component pattern and have different components for each page.
I don't know how I could change page title in different routings (or maybe different components) when I am navigating through the web app pages. I also want page titles to be available in browser history.
In addition to my earlier solution posted here, there is a second method that I found after a bit of research: use Navigation Guards
As detailed in my previous answer, here is the problem: vue-router may start reusing route components after getting created for the first time. There is really no need to destroy these components on route-exit, and re-create on subsequent route-entry. Therefore the created hook in my earlier solution may not fire on subsequent visits to the same route. Therefore our window title may not work as expected.
To overcome that problem, we can set the window title on a route-change event. The router instance has a afterEach hook that gets called after route change. This can be used to set window title as detailed below:
// Let's say this is your router instance, with all "Named Routes"
const ROUTER_INSTANCE = new VueRouter({
mode: "history",
routes: [
{ path: "/", name: "HomeComponentName", component: HomeComponent },
{ path: "/about", name: "AboutComponentName", component: AboutComponent },
{ path: "*", name: "RouteErrorName", component: RouteError }
]
})
// Assign page titles for each of the "named routes"
// Reason: This allows us to have short named routes, with descriptive title
const PAGE_TITLE = {
"HomeComponentName": "Your Dashboard",
"AboutComponentName": "About Us Page",
"RouteErrorName": "Error: Page not found"
}
ROUTER_INSTANCE.afterEach((toRoute, fromRoute) => {
window.document.title = PAGE_TITLE[toRoute.name]
console.log(toRoute) // this lets you check what else is available to you here
})
This may still not help you if you are navigating between similar routes, like "/user/foo" to "/user/bar". If you want user name in the titlebar or some dynamic page specific info, check out Reacting to Params Changes as detailed in http://router.vuejs.org/en/essentials/dynamic-matching.html. Based on docs, we should be able to use watch in component as follows:
watch: {
'$route' (toRoute, fromRoute) {
window.document.title = "some page title"
}
}
Hope it helps!
I also had the same problem few days ago, and I resolved as follows in my route component definition:
export default {
created: function() {
window.document.title = "Page Title for this route"
...
},
...
}
That's really not the correct way of doing it. Reason: I am making a big assumption that the route component gets created everytime on changing to a new route. It is true in vue-router for now, but may change in future.
I was using ui-router in Angular 1.4 earlier, which allows route components to live in memory (sticky states), so that the route change is instantaneous next time. If vue-router ever implements something similar to sticky states, my above method of setting title in created hook will fail.
But till that happens, you may use this solution.
I've got a solution and used it on one my projects.
First create a directive.
Vue.directive('title', {
inserted: (el, binding) => document.title = binding.value,
update: (el, binding) => document.title = binding.value
})
Suppose, we are working on 'MyComponent.vue' file.
Then use that directive on the router-view component.
<router-view v-title="title" ></router-view>
export default {
data(){
return {
title: 'This will be the title'
}
}
}
This works even if the component is updated or the page is reloaded.
Worked very well for me!!
I already use this Vue.js Routing example application.
https://github.com/chrisvfritz/vue-2.0-simple-routing-example
In the src/main.js
I have so much data value .
const app = new Vue({
el: '#app',
data: {
currentRoute: window.location.pathname,
token : "",
errorMessage : '', etc...etc..
},
Now with socket.io i set the token to "sdawdda2d2ada2ad22ad"
When application start than the currentRoute is equal with "/"
Its okey, the first page loaded. src/routes.js
'/': 'Home',
'/about': 'About'
When i want to check the /about (url: localhost:8080/about), than its works good , but the token and errorMessage is empty again, because the app created again.
If i want to change page without lose the token value i can use:
this.currentRoute = "/about" (url: localhost:8080)
Its works good , but the url not change, so i cant use back button in browser.
How can i separate my Vue app, if i dont want to lose token value when the browser go to /about?
Thanks so much!
you can do something like this.$router.go(-1) or vm.$router.go(-1) to go forward this.$router.go(1) for more click here
simple way
<template lang="html">
<div>
<button #click="goBack">
Back
</button>
</div>
</template>
<script>
export default {
methods: {
goBack() {
this.$router.go(-1)
}
}
}
</script>
When you are moving from your Home route to About route, you need to use <router-link> to navigate.
From your home page, you can navigate to about page as follows:
<router-link to="/about">Go to my About Page</router-link>
That generates a regular html anchor tag <a> with the path set correctly. When you click on that, it will take you to the about component, and not refresh the entire html webpage (with app scripts) from server.
When only the route component gets changed, all your other Vue params like token on the Vue instance will remain.
Please take a look at the vue-router here: http://router.vuejs.org/en/essentials/getting-started.html
Remember to have a placeholder for routes in your main app as follows: <router-view></router-view>. Your Home and About component will get rendered into this router-view as you change routes.