Is it possible to add .html at the end of the URL as you like while using NuxtJS or NextJS? - vue.js

I am currently planning to develop a web application using NuxtJS/NextJS framework with SSR Universal architecture, I am facing difficulties related to SEO, because previously my project was written in .NET , so all URLs have .html at the end..
Ex:
domain.com/hotels (this is Cate)
domain.com/hotels-5-star.html (this is a Sub cate of Hotels Cate)
The example above is specific to my problem, which means that according to Google, the shorter the URL, the better, so my pages have all been shortened to a single "/", however because of the structure page has 2 hierarchies, Cate and Sub cate, so for Google to distinguish this, the URL (sub cate) must insert .html at the end.
Can I use NuxtJS/NextJS build pages with the same URL as above?, I have researched but found a specific solution, maybe my experience is not much so I need the help of experts on NuxtJS/NextJS

yeah, in nuxtjs, you can use #nuxtjs/router to custom your router path like this
nuxt.config.js
buildModules: [
'#nuxtjs/router'
]
router.js
import Vue from 'vue';
import VueRouter from 'vue-router';
Vue.use(VueRouter);
const page = path => () => import(`./pages/${path}.vue`).then(m => m.default || m);
const routes = [
{
path: '/hotels-:star-star.html',
name: 'hotels',
props: true,
component: page('hotels')
}
];
export function createRouter(){
return new VueRouter({
routes
});
}
pages/hotels.vue
<template>
<main>
{{ star }}
</main>
</template>
<script>
export default{
props: {
star: {
type: Number
}
}
}
</script>

I'm not an expert in SEO, but I would just use 5-star as a query in domain.com/hotels
This would mean you could use the single hierarchy like so:
domain.com/hotels?rating=5-stars
As far as using html files as URL routing for the site, the nuxt generate property builds the project into html files which is under your control. The structure comes from what you set in the pages directory

Nowadays, all modern apps are having some pretty URLs looking like domain.com/hotels/
With a trailing slash at the end. Don't mix and match them. Either put them everywhere or nowhere.
SEO-wise, trailing slashes do not matter, you can let them.
Modern JS frameworks are doing a great job at this, and they know that if you reach domain.com/hotels/, you're actually wanting to serve the .html (aka the .vue) file in it, no need to add it.
In Nuxt.js, the directory structures would look like this:
-- pages
---- hotels.vue
or
-- pages
---- hotels
------ index.vue
As for this
the shorter the URL, the better
Don't stress it too much, you don't need to make super shorts paths neither.
Just keep it semantic and logic. Having domain.com/h5s will be super bad.
Also, having domain.com/hotels/5-stars or domain.com/hotels/?rating=5 is totally fine.
Basically, make them somehow readable for the end user.
PS: be aware that Next.js >> React and Nuxt.js >> VueJS.

Related

Vuepress dynamic routes and render as page?

I use vuepress as a mixed solution of static generated pages and dynamic pages in the SPA approach. The dynamic data source is a large database server, hence it can not use the additionalPages feature introduced in vurepress 1.x. That means dynamic routes was added using enhanceApp.js as below. Dynamic pages are rendered with extended layouts to share same page structure (header, footer).
// Foo is a layout component extends from Layout.vue
import Foo from './layouts/Foo.vue'
export default ({
Vue, // the version of Vue being used in the VuePress app
options, // the options for the root Vue instance
router, // the router instance for the app
siteData // site metadata
}) => {
router.addRoutes([
{ path: '/foo/:id', component: Foo },
]);
}
It works, however the layouts/Foo.vue is a component. It missing frontmatter, markdown syntax like normal markdown page does. The question is how to load a markdown page and pass to routes as component?
I'm still new to Vuepress, but I come across this comment in the source code that might be able to help you.
When Vue SFCs are source files, make them as layout components directly
I haven't finished reading all source code, so I'm not very sure I understand it correctly. But I guess when Vuepress find a vue component, it will not render it like a markdown file. It assumes you have your own style inside the .vue file. So I think that may be why you are missing what normal markdown page has.
However, if you need to mix a dynamic data source with static pages, you can try to use Vue in markdown to get what you want to achieve.

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.

Why router link have diferent behavior in mobil view?

When I use router links in desktop view works fine but in mobile view the links lack the "/" to work, any idea to make it work for mobile (I mean when max-width change) (bootstraps makes all the jobs). I think a parameter or something to keep it working if bootstraps changes to a copy the menu adapted to mobile screen. In that swap of styles bootstrap stripes the "/" to the links with router vue link to. Any advice, suggerence or sample link will appretiated.
You should read this.
Vue Router Child, Trailing slash
Also to prevent this you can use strict in vue-router as follows
const route1 = { template: '<div>Route 1 (strict)</div>' }
const route2 = { template: '<div>Route 2 (no strict)</div>' }
Check the fiddle link
http://jsfiddle.net/L7hscd8h/9377/

path-to-regexp Find regular expression matching the route

I am adding dynamic child components while the page loads. Adding child routes in OnCreated of parent does not make the page work when we refresh the page.
Hence, I am parsing the page templates (as I know them when the page loads).
I am now looking for a way to know the route which matches the href. Vue-js uses path-to-regexp and my question is very simple.
I want to know the matching component
const router = new VueRouter({
routes: [
// dynamic segments start with a colon
{ path: '/user/:id', component: User },
{ path: '/foo/bar', component: FooBar },
]
})
// Reverse of this
var matchingComponent = howDoIDothis(/foo/bar) // this should give me the matching
I need this so that I can remove from the path and then add the child component to the parent dynamically.
You need to use Vue router's router.getMatchedComponents method. However, this method needs that your router is fully initialized with all the routes. Otherwise, there is no way. Vue-router doesn't expose underlying parsed Regular expressions against which you can compare your href.
Also, what you are trying to do is not the most idiomatic way of doing things in Single Page Applications. A good practice is to declare all your routes upfront in some JS file using which you should initialize your router. Of course, you will want to protect certain routes for which you should use Route guards.
Finally, when you have all your routes declared upfront means you have all the components bundled upfront in one big JS file. To avoid this, wrap your component in async wrappers and bundler like Webpack would be smart enough to split the bundle into multiple smaller files.

Can Vue-Router handle clicks from normal anchors rather than router-link?

I have a scenario where there are two major components on a page; a frame-like component that contains common functionality for many applications (including a bookmark/tab bar) and my actual application code.
Since the frame doesn't actually own the page that it's included on, it seems like it would be incorrect for it to define any routes, however the current page may define their own routes that may match one of those links. In that case, I'd like vue-router to handle those anchor clicks and navigate appropriately rather than doing a full page reload.
Here's a simplified template of what this looks like:
Frame (an external dependency for my app):
<Frame>
<TabStrip>
</TabStrip>
<slot></slot>
<Frame>
App1:
<Frame>
<App>You're looking at: {{ pageId }}!</App>
</Frame>
So when any of the app1 domain links are clicked from that tab strip, I want my route definitions in app1 to pick that up rather than it causing a page load. Since that component is owned by the frame, I don't have access to write <router-link> since links to many different apps may co-exist there.
Any thoughts?
Whoo, this is an old one! However, since this question was high in my search results when I was researching this problem, I figured I should answer it.
My use-case was similar to the one in the comments: I needed to capture normal <a> links within rendered v-html and parse them through the router (the app is rendering Markdown with a light modification that generates internal links in some cases).
Things to note about my solution:
I'm using Vue3, not Vue2; the biggest difference is that this is the new Vue3 composition-style single page component syntax, but it should be easy to backport to Vue2, if necessary, because the actual things it's doing are standard Vue.
I stripped out the markdown logic, because it doesn't have anything to do with this question.
Note the code comment! You will very likely need to design your own conditional logic for how to identify links that need to be routed vs. other links (e.g. if the application in the original question has same-origin links that aren't handled by the Vue app, then copy/pasting my solution as-is won't work).
<script setup>
import { useRouter } from "vue-router"
const router = useRouter()
const props = defineProps({
source: {
type: String,
required: true,
},
})
function handleRouteLink(event) {
const target = event.target
// IMPORTANT! This is where you need to make a decision that's appropriate
// for your application. In my case, all links using the same origin are
// guaranteed to be internal, so I simply use duck-typing for the
// properties I need and compare the origins. Logic is inverted because I
// prefer to exit early rather than nest all logic in a conditional (pure
// style choice; works fine either way, and a non-inverted conditional is
// arguably easier to read).
if (!target.pathname || !target.origin || target.origin != window.location.origin) {
return
}
// We've determined this is a link that should be routed, so cancel
// the event and push it onto the router!
event.preventDefault()
event.stopPropagation()
router.push(target.pathname)
}
</script>
<template>
<div v-html="source" #click="handleRouteLink"></div>
</template>