Vue Router works fine in development, but doesn't match routes in production - vue.js

I have a Vue SPA that's being served by an ASP Core API. When I run it in development mode, everything works perfectly. But as soon as I deploy it to production (on an Azure App Service), I always get a blank page.
It seems to be specifically the router that can't match the routes, as I can put some arbitrary HTML into my App.vue, and that will render.
If I go into the developer tools, I can see that the index.html and all .js files download successfully and there are no errors in the console. This is true no matter what URL I visit e.g. myapp.com and myapp.com/login, both download everything but nothing displays on screen.
I have seen several posts saying to change the routing mode to hash, but I still get the same result with that.
Please see below my files:
main.ts
import Vue from 'vue';
import App from './App.vue';
import router from './router';
import store from './store';
import vuetify from './plugins/vuetify';
import { LOGIN_INITIALISE } from './use-cases/user-auth/AuthModule';
Vue.config.productionTip = false;
store.dispatch(LOGIN_INITIALISE)
.then(() => {
new Vue({
router,
store,
vuetify,
render: (h) => h(App),
}).$mount('#app');
});
App.vue
<template>
<div>
<div>test</div>
<router-view></router-view>
</div>
</template>
<script lang="ts">
/* eslint-disable no-underscore-dangle */
import Vue from 'vue';
import Axios from 'axios';
import { LOGOUT } from './use-cases/user-auth/AuthModule';
import { LOGIN } from './router/route-names';
export default Vue.extend({
name: 'App',
created() {
// configure axios
Axios.defaults.baseURL = '/api';
Axios.interceptors.response.use(undefined, (err) => {
// log user out if token has expired
if (err.response.status === 401 && err.config && !err.config.__isRetryRequest) {
this.$store.dispatch(LOGOUT);
this.$router.push({ name: LOGIN });
}
throw err;
});
},
});
</script>
router/index.ts
import Vue from 'vue';
import {} from 'vuex';
import VueRouter, { RouteConfig } from 'vue-router';
import store from '#/store';
import {
HOME,
LOGIN,
SIGNUP,
USERS,
} from './route-names';
Vue.use(VueRouter);
const routes: Array<RouteConfig> = [
{
path: '/',
name: HOME,
component: () => import('#/views/Home.vue'),
},
{
path: '/login',
name: LOGIN,
component: () => import('#/views/Login.vue'),
},
{
path: '/signup',
name: SIGNUP,
component: () => import('#/views/SignUp.vue'),
},
{
path: '/users',
name: USERS,
component: () => import('#/views/Users.vue'),
beforeEnter: (to, from, next) => {
if (store.getters.userRole === 'Admin') {
next();
} else {
next({ name: HOME });
}
},
},
{
path: '*',
name: '404',
component: {
template: '<span>404 Not Found</span>',
},
},
];
const router = new VueRouter({
mode: 'history',
base: process.env.BASE_URL,
routes,
});
router.beforeEach((to, from, next) => {
if (store.getters.isAuthenticated) {
next();
} else if (to.name === LOGIN || to.name === SIGNUP) {
next();
} else {
next({ name: LOGIN });
}
});
export default router;

Finally after completely rebuilding my router piece by piece, I found the issue. I found that the problem was in this global route guard:
router.beforeEach((to, from, next) => {
if (store.getters.isAuthenticated) {
next();
} else if (to.name === LOGIN || to.name === SIGNUP) {
next();
} else {
next({ name: LOGIN });
}
});
Specifically, the isAuthenticated getter was throwing an error (silently), so all of the routes were failing before they could render. I wrapped my isAuthenticated logic in a try-catch that returns false if an error is thrown, and now everything works fine.
I still don't understand why this only affects the production build, but hopefully this experience will be useful to others stuck in the same situation.

Related

vue-router Navigation Guard does not cancle navigation

Before accessing any page, except login and register; I want to authenticate the user with Navigation Guards.
Following you can see my code for the vue-router. The "here" gets logged, but the navigation is not cancelled in the line afterwards. It is still possible that if the user is not authenticated that he can access the /me-route
my router-file:
import { createRouter, createWebHistory } from "vue-router";
import axios from "axios";
import HomeView from "../views/HomeView.vue";
import RegisterView from "../views/RegisterView.vue";
import LoginView from "../views/LoginView.vue";
import MeHomeView from "../views/MeHomeView.vue";
const router = createRouter({
history: createWebHistory(import.meta.env.BASE_URL),
routes: [
{
path: "/",
name: "home",
component: HomeView,
},
{
path: "/register",
name: "register",
component: RegisterView,
},
{
path: "/login",
name: "login",
component: LoginView,
},
{
path: "/me",
name: "me",
component: MeHomeView,
},
],
});
router.beforeEach((to, from) => {
if(to.name !== 'login' && to.name !== 'register') {
console.log(to.name);
axios.post("http://localhost:4000/authenticate/", {accessToken: localStorage.getItem("accessToken")})
.then(message => {
console.log(message.data.code);
if(message.data.code === 200) {
} else {
console.log("here");
return false;
}
})
.catch(error => {
console.log(error);
return false;
})
}
})
export default router;
Navigation guards support promises in Vue Router 4. The problem is that promise chain is broken, and return false doesn't affect anything. As a rule of thumb, each promise needs to be chained.
It should be:
return axios.post(...)
The same function can be written in more readable and less error-prone way with async..await.

How to add router with query param to router list?

I want to add a route with query params.
If the url is blog, then navigate to index page.
If the url includes the author query param, replace a component on the page with the BlogAuthorPage component.
router: {
extendsRoutes(routes, resolve) {
routes.push({
name: 'author-page-detail',
path: '/blog?author=*',
component: resolve(__dirname, 'pages/blog/author-page.vue')
})
}
}
This should not be done in nuxt.config.js's router key but rather in your blog.vue page directly with a component router guard.
The code below should be enough to check if the route does have a author query params and redirect to the blog/author-page page.
<script>
export default {
beforeRouteEnter(to, from, next) {
next((vm) => {
if (vm.$route.query?.author) next({ name: 'blog-author-page' })
else next()
})
},
}
</script>
I use "#nuxtjs/router": "^1.6.1",
nuxt.config.js
/*
** #nuxtjs/router module config
*/
routerModule: {
keepDefaultRouter: true,
parsePages: true
}
router.js
import Vue from 'vue'
import Router from 'vue-router'
import BlogIndexPage from '~/pages/blog/index'
import BlogAuthorPage from '~/pages/blog/author-page';
Vue.use(Router);
export function createRouter(ssrContext, createDefaultRouter, routerOptions, config) {
const options = routerOptions ? routerOptions : createDefaultRouter(ssrContext, config).options
return new Router({
...options,
routes: [
...options.routes,
{
path: '/blog',
component: ssrContext.req.url.includes('/blog?author') ? BlogAuthorPage : BlogIndexPage
}
]
})
}

Refreshing page redirects to home using vue router

I am facing a problem with my vuejs project. I am using vue router in a single page application. I can go to any page using vue router. But when I reload the page at any route, it redirects me to / of the project. Here is the code I have written for vue router in router/index.js file.
import Vue from 'vue'
import VueRouter from 'vue-router'
// import store from '../store'
import Home from '../views/Home.vue'
import Login from '#/components/auth/Login.vue'
import Register from '#/components/auth/Register.vue'
import Admin from '#/components/admin/Admin.vue'
import CreateCourse from '#/components/admin/course/CreateCourse.vue'
import Categories from '#/components/admin/Categories.vue'
Vue.use(VueRouter);
function isAuthenticated(to, from, next) {
// if (store.getters['auth/authenticated']) {
// next();
// } else {
// next('/login');
// }
next();
}
function isAdmin(to, from, next) {
// if (store.getters['auth/user'].role === 'super' || store.getters['auth/user'].role === 'admin') {
// next();
// } else {
// next('/');
// }
next();
}
function isNotAuthenticated(to, from, next) {
// if (!store.getters['auth/authenticated']) {
// next();
// } else {
// next('/');
// }
next();
}
const routes = [
{
path: '/',
name: 'Home',
component: Home,
},
{
path: '/about',
name: 'About',
beforeEnter: isAuthenticated,
component: () => import(/* webpackChunkName: "about" */ '../views/About.vue')
},
{
path: '/login',
name: 'Login',
component: Login,
beforeEnter: isNotAuthenticated,
},
{
path: '/register',
name: 'Register',
component: Register,
beforeEnter: isNotAuthenticated,
},
{
path: '/admin',
name: 'Admin',
component: Admin,
beforeEnter: isAdmin,
},
];
const router = new VueRouter({
mode: 'history',
routes
});
export default router
What is the problem?
Note: The commented code is to control user access to a specific route. I am calling beforeEnter for each route to check if the user has permission or not. Is there any better solution?
I found what is wrong with my code. Every time I reload the page, I authenticate the user using vuex and if the authentication is successful, I redirect the user to home page. So, every time I refresh the page, I am authenticated and redirected to the home page. Now I have removed the redirect login after login and the problem is solved.

Laravel 5.6 + vue.js app router + laravel passport

I am testing Laravel5.6+Vue app, I have activated Laravel Auth and Passport.
My project:
API uses Laravel Passport.
Web Laravel Auth is active.
Vue App
Since Laravel Auth is active, it seems like Auth triggers and validate user session, if he is login or not and redirect to Auth /login route.
When I disable auth routes in web.php, error occurs.
// Auth::routes();
Error
InvalidArgumentException
Route [login] not defined.
Since I am using Vue app and Laravel API with Passport, how can avoid Laravel web Auth to validate Vue app?
web.php
Auth::routes();
Route::get('{any}', 'HomeController#index')->name('home')->where('any','.*');
api.php
Route::group([
'prefix' => 'auth'
], function () {
Route::group(['middleware' => 'auth:api'], function() {
Route::get('logout', 'API\AuthController#logout');
Route::get('user', 'API\AuthController#user');
});
});
Route::group(['middleware' => 'auth:api'], function() {
Route::get('/application/all', 'ApplicationController#index');
});
Route::post('/auth/login', 'API\AuthController#login');
Route::post('/auth/register', 'API\AuthController#register');
Vue
routes.js
import Home from './components/Home.vue';
import Login from './components/auth/Login.vue';
import Logout from './components/auth/Logout.vue';
import Profile from './components/auth/Profile.vue';
import Register from './components/auth/Register.vue';
import ApplicationList from './components/content-app/ApplicationList.vue';
export const routes = [
{
path: '/',
component: Home,
meta: {
requiresVisitor: true,
}
},
{
name: 'user-login',
path: '/user/login',
component: Login,
meta: {
requiresVisitor: true,
}
},
{
name: 'user-register',
path: '/user/register',
component: Register,
meta: {
requiresVisitor: true,
}
},
{
name: 'user-profile',
path: '/user/profile',
component: Profile,
meta: {
requiresAuth: true,
}
},
{
path: '/logout',
component: Logout,
meta: {
requiresAuth: true,
}
},
{
path: '/content/application-list',
component: ApplicationList,
meta: {
requiresAuth: true,
}
}
];
app.js
require('./bootstrap');
import Vue from 'vue';
import VueRouter from 'vue-router';
import Vuex from 'vuex';
import axios from 'axios'
import {routes} from './routes';
import StoreDate from './store';
import MainApp from './components/MailApp.vue';
Vue.use(VueRouter);
Vue.use(Vuex);
const store = new Vuex.Store(StoreDate);
const router = new VueRouter({
routes,
mode: 'history'
});
router.beforeEach((to, from, next) => {
if (to.matched.some(record => record.meta.requiresAuth)) {
// this route requires auth, check if logged in
// if not, redirect to login page.
if (!store.getters.loggedIn) {
next({
path: '/login',
query: { auth: 'unauthenticated' } //query: { redirect: to.fullPath }
})
} else {
next()
}
} else if (to.matched.some(record => record.meta.requiresVisitor)) {
if (store.getters.loggedIn && (to.path == '/login' || to.path == '/register')) {
next({
path: '/profile',
})
} else {
next()
}
} else {
next() // make sure to always call next()!
}
})
const app = new Vue({
el: '#app',
router,
store,
components: {
MainApp
}
});
///////// OVER WRITING JS
$('.navbar-nav>li>a').on('click', function(){
$('.navbar-collapse').collapse('hide');
});
$('.navbar-nav>navbar-brand').on('click', function(){
$('.navbar-collapse').collapse('hide');
});

How to setup vuex and vue-router to redirect when a store value is not set?

I'm working with the latest versions of vue-router, vuex and feathers-vuex and I have a problem with my router.
What I'm doing is to check if a route has the property "requiresAuth": true in the meta.json file. If it does then check the value of store.state.auth.user provided by feathers-vuex, if this value is not set then redirect to login.
This works fine except when I'm logged in and if I reload my protected page called /private then it gets redirected to login so it seems that the value of store.state.auth.user is not ready inside router.beforeEach.
So how can I set up my router in order to get the value of the store at the right moment?
My files are as follow:
index.js
import Vue from 'vue'
import Router from 'vue-router'
import store from '../store'
const meta = require('./meta.json')
// Route helper function for lazy loading
function route (path, view) {
return {
path: path,
meta: meta[path],
component: () => import(`../components/${view}`)
}
}
Vue.use(Router)
export function createRouter () {
const router = new Router({
mode: 'history',
scrollBehavior: () => ({ y: 0 }),
routes: [
route('/login', 'Login')
route('/private', 'Private'),
{ path: '*', redirect: '/' }
]
})
router.beforeEach((to, from, next) => {
if (to.meta.requiresAuth) {
if (!store.state.auth.user) {
next('/login')
} else {
next()
}
} else {
next()
}
})
return router
}
meta.json
{
"/private": {
"requiresAuth": true
}
}
I fixed the issue by returning a promise from vuex action and then run the validations
router.beforeEach((to, from, next) => {
store.dispatch('auth/authenticate').then(response => {
next()
}).catch(error => {
if (!error.message.includes('Could not find stored JWT')) {
console.log('Authentication error', error)
}
(to.meta.requiresAuth) ? next('/inicio-sesion') : next()
})
})