I've read a number of solutions to this same problem, but none have worked for me. So here it goes.
I have a Vue 2 app using Express that runs on AWS Amplify. When I run my app locally in 'dev' mode (npm run serve) and 'start' mode (npm run build && node server.js), everything works fine.
The problem shows up when I deploy to Amplify. I can click nav buttons, go back, and go forward, all of which send me to the correct URL. However, the moment I refresh the page or manually enter a valid URL in the browser, the screen goes blank and the browser URL shows https://dontblowup.co/index.html.
Below is my server.js file:
const express = require('express')
const path = require('path')
const history = require('connect-history-api-fallback')
const app = express()
const port = process.env.PORT || 8080
const buildLocation = 'dist'
const staticFileMiddleware = express.static(path.resolve(__dirname, buildLocation))
app.use(staticFileMiddleware)
app.use(history())
app.use(staticFileMiddleware)
app.get('*', function (req, res) {
res.sendFile(path.resolve(__dirname, buildLocation, 'index.html'))
})
app.listen(port, () => {
console.log(`App listening to port ${port}...`)
console.log('Press Ctrl+C to quit.')
})
The solutions I found included using the history package in the server.js file and using the staticFileMiddleware before and after using history(). I also have 'history' mode set in the Vue app's router.js file (see below).
import Vue from "vue";
import VueRouter from "vue-router";
import Index from "./views/Index.vue";
import MainFooter from "./layout/MainFooter.vue";
import DashboardLayout from "#/layout/DashboardLayout.vue";
import Analytics from "#/views/Analytics.vue";
import TradeSheet from "#/views/TradeSheet.vue";
import KellyCriterionCalculator from "#/views/KellyCriterionCalculator.vue";
import PositionBuilder from "#/views/PositionBuilder.vue";
Vue.use(VueRouter);
export default new VueRouter({
mode: 'history',
routes: [
{
path: "/",
name: "Index",
components: { default: Index, footer: MainFooter },
props: {
footer: { backgroundColor: "black" }
},
meta: { requiresAuth: false }
},
{
path: "/dashboard",
redirect: "/dashboard/analytics",
name: "Dashboard",
component: DashboardLayout,
meta: { requiresAuth: true },
children: [
{
path: "/dashboard/analytics",
name: "Analytics",
component: Analytics
},
{
path: "/dashboard/trade-sheet",
name: "Trade Sheet",
component: TradeSheet
},
{
path: "/dashboard/risk-budget-calculator",
name: "Risk Budget Calculator",
component: KellyCriterionCalculator
},
{
path: "/dashboard/trade-analyzer",
name: "Trade Analyzer",
component: PositionBuilder
}
]
}
],
scrollBehavior: to => {
if (to.hash) {
return { selector: to.hash };
} else {
return { x: 0, y: 0 };
}
}
});
At this point I'm convinced there's something wrong with Amplify or Namecheap (where my DNS is configured). Unfortunately I haven't found anyone with the same issues using the same tech, so hopefully someone here can help.
Cheers!
You need to set up it on the Amplify Console
Navigate to the Amplify Console
On the left menu click on "Rewrites and redirects"
Click on Edit
Add the rule:
Source: </^[^.]+$|\.(?!(css|gif|ico|jpg|js|png|txt|svg|woff|ttf|map|json)$)([^.]+$)/>
Target: /
Type: 200
You can read more about it here
Go to section: Redirects for Single Page Web Apps (SPA)
Most SPA frameworks support HTML5 history.pushState() to change browser location without triggering a server request. This works for users who begin their journey from the root (or /index.html), but fails for users who navigate directly to any other page. Using regular expressions, the following example sets up a 200 rewrite for all files to index.html except for the specific file extensions specified in the regular expression.
the better way to handle SPA call to index.html will be
app.get(/.*/, (req, res) => res.sendFile(__dirname + '/dist/index.html'))
in frontend vue router you need to add a redirect route like this
{
path: "*",
redirect: "/"
}
Related
I loading saved in cookies user authorization data when my Vue 3 application mount.
Data in json format like Jwt and Refresh tokens & something user data.
I have setup like:
main.js:
import VueCookies from 'vue3-cookies'
...
app.use(VueCookies, {
expireTimes: "365d",
path: "/"
})
In (vuex) store: auth.module.js:
I have things like this:
import { useCookies } from "vue3-cookies"
...
// in getters
const auth = cookies.get('auth')
// and in mutations
cookies.set('auth', auth)
And in router I have:
const routes = [
...
{
path: '/user/:id',
name: 'user',
props: true,
component: () => import('#/views/UserView.vue'),
meta: {
requiresAuth: true,
availableRoles: [ "Admin" ]
}
},
...
]
...
const router = createRouter({
history: createWebHistory(process.env.BASE_URL),
routes
})
So, the problem is, that when I load the location like:
https://[url]/user/new
Or
https://[url]/user/1
I got duplicated cookies.
First auth cookie path is /user and its empty.
Second auth cookie path is / and it's ok.
Problem appears only on this route, and IDN why...
Seems like strange bug, do you have same issues?
If I going to route like:
<router-link to=`/user/{user.id}`>
Then issue occurs.
But, If I going to route like:
<router-link :to="{ name: 'user', params: { id: String(user?.id) } }">
That's fine.
Strange behavior.
is there a way to build all .vue files in my views/ folder without importing them into my vue application directly?
What I want to achieve is a Router with dynamic route definitions which will be filled from a configuration which is a json filled with name, router path and the component name which should be lazy loaded.
My issue right now: The application does only build the files which are imported somewhere in my application. What I need: Build all files in views/ as chunk in order to be able to have a dynamic Router definition.
Thank you for your help :)
It's hard to give proper solution without any code provided, but I'll try
With code like this:
// route definition loaded from json
const routeDef = [
{ name: 'about', path: '/about', componentName: 'about' },
{ name: 'profile', path: '/profile', componentName: 'profile' }
]
// we can create routes like this
const routes = routeDef.map(function(def) {
return {
name: def.name,
path: def.path,
component: () => import('#/views/' + def.componentName + '.vue')
}
})
const router = new VueRouter({
routes: routes
})
By using import('#/views/' + def.componentName + '.vue') you are telling Webpack I want to import one of the .vue files in the views directory but which one will be known at runtime. So Webpack will bundle all file in that directory and make it available for import at runtime....
Note that what you pass into import() is really important - part of the argument must always be a static string so Webpack have at least some clue about the directory (import(def.componentName) wont work) - see the docs
Additional example - loading route definitions from server at runtime
router.js
export default createRouter() {
return axios.get('/getRouteDefinitions').then(function(data) {
const routes = data.routeDef.map(function(def) {
return {
name: def.name,
path: def.path,
component: () => import('#/views/' + def.componentName + '.vue')
}
})
return new VueRouter({
routes: routes
})
})
}
main.js
import VueRouter from `VueRouter`
import createRouter from `router.js`
Vue.use(VueRouter)
createRouter().then(function(router) {
const app = new Vue({
router
})
app.$mount("#app")
})
Good day.
When running my app i get the following error related to my vue-router:
TypeError: e is not a function
however i have nothing ... i simply named "e" in my code.
I have a few before each options but nothing too big besides a few cookies deletions.
I have a few imports of apps and i am trying to use them in my router and they all work. So does the cookies. I did the beforeEach() method a few times to see if the error was there but so far no luck.
i got no idea of what is going on.
EDIT: When trying to figure this i was comenting on my code to see if i could find the error and when i removed most of the beforeEach() section i left on the next() function and a new error showed sayin "t is not a function", so i guess for some reason java script is only recognizing the last letters of my funcions, like t in next() and e in some().
EDIT2: After removing unecessary code that i copied from another project apperently the error happens in my next() function.
here's my router code:
import Vue from "vue";
import Router from "vue-router";
import Dashboard from "#/views/Dashboard.vue";
import Opcoes from "#/views/settings.vue";
import LoginForm from "#/components/component/loginForm.vue";
import store from "./store.js";
import Cookies from "js-cookie";
Vue.use(Router);
let router = new Router({
mode: "history",
base: process.env.BASE_URL,
routes: [
{
path: "/",
name: "loginForm",
component: LoginForm,
meta: {
guest: true
}
},
{
path: "/dashboard",
name: "Dashboard",
component: Dashboard,
meta: {
requiresAuth: true
}
},
{
path: "/opcoes",
name: "Opcoes",
component: Opcoes,
meta: {
requiresAuth: true
}
},
{
path: "/sair",
name: "Sair",
component: LoginForm,
meta: {
requiresAuth: true
}
}
]
});
router.beforeEach((to, from, next) => {
const expirationDate = Cookies.get("expireIn");
const now = new Date().getTime();
if (now >= expirationDate) {
Cookies.remove("expirationDate");
Cookies.remove("token");
Cookies.remove("userId");
store.dispatch('LOGOUT');
} else {
next();
}
});
export default router;
Heres a print of my stack trace:
My Big Fat Guess
The error message points to line 2203 (line 1921 is part of a generic error handler). This line is in the Router push() method
onComplete && onComplete(route);
My guess is, somewhere in your code not shown in your question (perhaps in your LoginForm component), you are calling this.$router.push() with a second argument that is not a function, eg
this.$router.push({ name: 'Dashboard' }, somethingNotAFunction)
Other problems (from previous versions of the question)
Your / route (loginForm) has no params so this...
next({
path: "/",
params: { nextUrl: to.fullPath }
});
is invalid. Did you perhaps mean to send nextUrl as a query parameter. If so, use
next({ name: 'loginForm', query: { nextUrl: to.fullPath } })
You also have
next({ path: "login" });
and
next({ name: "login" });
neither of which exist in your routes. You should only forward the route request to existing routes.
Finally, if you're using Vuex, you should not be directly assigning state values. In strict-mode, this
store.state.isLoggedIn = false
will trigger an error. You should be committing state mutations instead.
I've a node.js (express) application listening at localhost:4000/api
I'm working on a vuejs based client with router as follows,
const routes = [
{
path: '/customers',
name: 'customers',
component: Customers,
},
{
path: '/customers/:customer_id',
name: 'customer_details',
component: CustomerDetails,
},
]
I'm managing server path via vue.config.js file as,
const path = require('path');
module.exports = {
devServer: {
proxy: {
'/api': {
target: 'http://localhost:4000'
}
}
}
}
I'm also using state mgmt via store object/functionality as follows,
const BASE_URL = 'api/'
fetchCustomers(context) {
const server_url = BASE_URL + '/customers'
axios.get(server_url)
...
}
This works fine, but
fetchCustomer(context, id) {
const server_url = BASE_URL + '/customers/' + id
axios.get(server_url)
...
}
This one fails with 404 error. When I checked on the server, I see that client is making call to,
/customers/api//customers/5c891995e5212d439459ff28
instead of
/api//customers/5c891995e5212d439459ff28
And at the client side I've a router-link as follows,
<router-link :to="{'name':'customer_details',
params: {'customer_id': cid }}></router-link>
When I click on this link, I'm seeing following in the console,
GET http://localhost:8080/customers/api//customers/5c891995e5212d439459ff28 404 (Not Found)
If I remove vue.config.js and put BASE_URL with complete path as 'http://localhost:4000/api', then I don't see this error. Must be very small mistake but I'm not seeing it. Any help is appreciated.
I'm having a really hard time to figure out how to load a particular route when the activate event is fired. I'm creating an Electron application using the Electron-Vue framework and I've these following routes:
export default [
{
path: '/',
name: 'login-page',
component: require('components/LoginPageView')
},
{
path: '/tracker',
name: 'tracker-page',
component: require('components/TrackerPageView')
},
{
path: '*',
redirect: '/'
}
]
Now, I'd like to load the /tracker route once the app.on('activate') is fired on the following:
app.on('activate', () => {
if (mainWindow === null) {
createWindow()
}
})
The main reason behind this is I'm creating a two window Electron application. The first window would be the login and the second window would be user profiles. When the user already logged in and closed the app using the system close button, the app stays in the Dock bar and when the app is clicked, the Electron activate event is fired and the login screen shows again. Since the user is already logged in, I don't want the user to show the login window again. Any suggestion would be must appreciated.
I was finally able to achieve this by using the Vue-Router Per-Route Guard beforeEnter method. Here's my draft:
let auth = true
export default [
{
path: '/',
name: 'login-page',
component: require('components/LoginPageView'),
meta: {
auth: false
},
beforeEnter: (to, from, next) => {
if (auth) {
next('/tracker')
} else {
next()
}
}
},
{
path: '/tracker',
name: 'tracker-page',
component: require('components/TrackerPageView'),
meta: {
auth: true
}
},
{
path: '*',
redirect: '/'
}
]
Any feedbacks are most welcome to improve this even better :)