I have a vue.js application with vue router to render various components. I have a path /home which loads the home component. In the dev environment, I was able to go to the component by giving localhost:8080/home in my address bar and also by setting links using <router-link>. When I deploy the production build to apache server, when I give localhost/home is giving the error
The requested URL /home was not found on this server.
But the links are working and localhost/home is shown in the address bar when we click on the link
Why this happens? How to solve this?
Directly from the Vue router web site.
The default mode for vue-router is hash mode - it uses the URL hash to
simulate a full URL so that the page won't be reloaded when the URL
changes.
To get rid of the hash, we can use the router's history mode, which
leverages the history.pushState API to achieve URL navigation without
a page reload:
const router = new VueRouter({ mode: 'history', routes: [...] })
When using history mode, the URL will look "normal," e.g.
http://oursite.com/user/id. Beautiful!
Here comes a problem, though: Since our app is a single page client
side app, without a proper server configuration, the users will get a
404 error if they access http://oursite.com/user/id directly in their
browser. Now that's ugly.
Not to worry: To fix the issue, all you need to do is add a simple
catch-all fallback route to your server. If the URL doesn't match any
static assets, it should serve the same index.html page that your app
lives in. Beautiful, again!
Apache
<IfModule mod_rewrite.c>
RewriteEngine On
RewriteBase /
RewriteRule ^index\.html$ - [L]
RewriteCond %{REQUEST_FILENAME} !-f
RewriteCond %{REQUEST_FILENAME} !-d
RewriteRule . /index.html [L]
</IfModule>
nginx
location / {
try_files $uri $uri/ /index.html;
}
Native Node.js
const http = require('http')
const fs = require('fs')
const httpPort = 80
http.createServer((req, res) => {
fs.readFile('index.htm', 'utf-8', (err, content) => {
if (err) {
console.log('We cannot open "index.htm" file.')
}
res.writeHead(200, {
'Content-Type': 'text/html; charset=utf-8'
})
res.end(content)
})
}).listen(httpPort, () => {
console.log('Server listening on: http://localhost:%s', httpPort)
})
[0] https://router.vuejs.org/en/essentials/history-mode.html
In the router instance, I used history mode which gives a URL without hash(#). I changed the mode form 'history' to the default 'hash' and it solved the problem for me.
Related
I have created a simple example of Vue3 in a subdirectory on my webserver:
https://tokant.com/vue-router-transition-v2/
Navigating to the different navigational links works as intended, and refreshing the browser on home and about also works as intended. However on the last two links (that use Dynamic Route Matching with Params), there is an issue:
the main.js file cannot be retrieved because it becomes relative to the /users/ path and not the app root.
There must be an issue with my router createWebHistory base path or .htaccess, however I have tried many possibilities without success.
The router and routes are set up like this:
const routes = [
{ path: '/', name: 'home', component: Home },
{ path: '/about', name: 'about', component: About },
{ path: '/users/:id', name: 'user', component: User },
{ path: '/:pathMatch(.*)*', name: 'notfound', component: NotFound }
]
const router = createRouter({
// 4. Provide the history implementation to use. We are using the hash history for simplicity here.
history: createWebHistory('/vue-router-transition-v2'),
routes, // short for `routes: routes`
})
The .htaccess looks like this:
<IfModule mod_rewrite.c>
RewriteEngine On
RewriteBase /
RewriteRule ^index\.html$ - [L]
RewriteCond %{REQUEST_FILENAME} !-f
RewriteCond %{REQUEST_FILENAME} !-d
RewriteRule . /vue-router-transition-v2/index.html [L]
</IfModule>
Does anyone know why the base path is not used when refreshing pages that use dynamic routes?
Important information: The app is created without a build step.
From the vue-router documentation:
"If you deploy to a subfolder, you should use the publicPath option of Vue CLI and the related base property of the router."
You are missing the publicPath part and not building your application with vue-cli.
Is there any reason why you are deploying the app without building it?
Building you app is essential for minifying your source files, bundling and tree shaking your libraries and cache busting on any new updates you deploy.
If SEO is not important to you, you can switch to Hash mode
I have a strange problem when I am using Nuxt.js with SSR mode, for fetching data in page components I am using nuxt fetch(), this works fine when I am navigating between nuxt-links but if I refresh the page or visit the specific page from direct link, in some cases this.$route.params.id is undefined, I can't figure out what is causing this problem
File structure inside pages folder
pages/blogs/_id/index.vue
<template>
<div class="container-padding">
{{singleBlog}}
</div>
</template>
<script>
import { getSingleBlog } from "#/utils/api";
export default {
data() {
return {
singleBlog: {},
};
},
async fetch() {
this.singleBlog = await getSingleBlog(this.$route.params.id);
},
};
</script>
utils/api.js
import axios from 'axios'
let baseUrl = 'https://myurl'
/* get single blog */
export const getSingleBlog = async (blogId) =>{
let req = await axios.get(`${baseUrl}/api/blog/${blogId}`)
return await req.data
}
Here is my .htaccess file
RewriteEngine On
RewriteBase /
DirectoryIndex
Options -Indexes
# Forcing all incoming requests to HTTPS.
# HTTPS is required for PWA. If you don't want PWA feature you can deisable next 2 lines
RewriteCond %{HTTP_HOST} ^www\.(.*)$ [NC]
RewriteRule ^(.*)$ https://%1/$1 [R=301,L]
RewriteCond %{HTTP_HOST} ^mydomen.com$
RewriteRule ^(.*) "http://127.0.0.1:3000/$1" [P,L]
It is because on direct visit it's called at server-side but navigationg with nuxt-link calls it at client-side.
this is not available at server-side
fetch is called on server-side when rendering the route, and on client-side when navigating. according to Nuxt Docs
How can I create 404 error page with 404 response code in Vue? Here is the route for 404.
{
path: "*",
name: "404",
component: load("404"),
alias: "/404"
}
You won't be able to set the HTTP status code in a Single-Page Application - all the routing is done on the same page in the browser so once the page is loaded no more HTTP status codes will be received.
However, if you try to load a non-existent route by directly typing/copying the URL into the address bar of the browser - then you can use nginX (or whatever server software you are using) to signal 404 HTTP status:
server {
error_page 404 /404.html;
location / {
try_files $uri $uri/ =404;
}
}
But this is not a practical/wise approach - basically, you want every non-existent path to be resolved to /index.html so that your SPA is always loaded and once loaded - it will detect that this route does not exist and will render your 404 component.
So your nginX config should look like this:
server {
...
location / {
try_files $uri /index.html$is_args$args;
}
}
The $is_args variable will add ? if $args is not empty while $args will provide the query parameters (if any).
Your route definition looks ok, except that I don't know your load function does, i.e. how it resolves to a Vue component. But in general your code follows the common approach as described in the Vue router docs. Usually you won't need a name or an alias here since this route is not used explicitly. Just put in a component that shows your "not found" content and you should be good to go:
{
path: "*",
component: PageNotFound
}
Since this is very close to the code you provided, please explain what exactly gives you a problem.
Like IVO GELOV wrote, you have to force this on the server. I came across the issue with Apache (IVO GELOV provides help on nginx).
In the vue router (Vue 3):
const routes = [
{
// Your other routes here
// ...
{
path: '/404',
name: '404',
component: Error404,
},
{
path: '/:pathMatch(.*)*',
beforeEnter() { window.location.href = "/404" },
},
]
With the last route item, all non-matched routes will be redirected to /404. I use beforeEnter to avoid creating a component and window.location.href to get out of the Vue application.
In Apache .htaccess:
<IfModule mod_alias.c>
Redirect 404 /404
</IfModule>
This will add a 404 error code to the /404 url.
Then the /404 route from the vue router config will call the Error404 component that you have to define and import like any other Vue component!
This answer is heavily inspired by:
https://stackoverflow.com/a/62651493/14217548
Through $router.push({ path:/week/${year}/${month}/${day}/}) I can successfully route/move (not sure about what term to use here) to a different route, with the URL correctly updating.
However once there, if I refresh the browser, the routed component becomes empty (and no console error).
Any idea what's going on here?
Here's my router code:
let router = new Router({
mode: 'history',
routes: [
...
{
path: '/',
redirect: '/home',
meta: {
requiresAuth: true
}
},
{
path: '/week/:year/:month/:day',
component: Home,
meta: {
requiresAuth: true
}
},
...
]
})
You need to configure whichever server that you are using to handle the router as you are using history mode.
I will post the sample configuration for Apache and Nginx since they are most commonly used.
Apache
<IfModule mod_rewrite.c>
RewriteEngine On
RewriteBase /
RewriteRule ^index\.html$ - [L]
RewriteCond %{REQUEST_FILENAME} !-f
RewriteCond %{REQUEST_FILENAME} !-d
RewriteRule . /index.html [L]
</IfModule>
Nginx
location / {
try_files $uri $uri/ /index.html;
}
Additional information can be found on the official docs.
I am using laravel mix and vuejs for my app and everything works fine.
Now I tried to change the VueRouter to history mode:
const router = new VueRouter({
mode: 'history'
})
On the ngix server, I added the catch all rule:
location / {
try_files $uri $uri/ /index.html;
}
Because of that its still working, when accessing or refreshing top level urls like "/pages" or "/contact".
The only problem is with sublevel urls like /pages/1 or /foo/bar/boo. Clicking on a router link still works, but if I try to refresh the page on /pages/1 or if I try to directly access it (enter /pages/1 in browser) its not working, since the browser tries to access the assets from /pages/js/2.js instead of /js/2.js
Ok, I just found it out - I had to set the public path to an absolute path in my webpack.mix.js file:
mix.webpackConfig({
...
output: {
...
publicPath: "/"
}
});