Frappe UI Vue error 404 on history mode

I use Frappe UI and Vue by customizing the frontend HRMS, but when I try to access host/mobile it will redirect to host/mobile/login and it works and doesn’t error, but when I refresh the page (host/mobile/login) or access directly with url , it’s error 404 frappe. Maybe because of Vue route history mode. But how do I set it so it doesn’t 404? maybe the nginx conf? or something else?

My website is: https://bimbelku.web.id/mobile/
and when access that link, will be redirect to https://bimbelku.web.id/mobile/login, but when i refresh that page, that return 404 frappe page.

First access site

After refresh page

1 Like

Of course. This is a classic issue when deploying a Single Page Application (SPA) like Vue.js within a framework like Frappe.

The problem happens because of a mismatch between client-side routing (handled by Vue Router in the browser) and server-side routing (handled by Frappe).

  • When you first go to .../mobile/, Frappe serves the main HTML file. Vue then loads and takes over navigation. Clicking a link to /login works because Vue Router just changes the URL in the browser and displays the login component without talking to the server.
  • When you refresh the page at .../mobile/login, the browser sends a direct request for that specific URL to the Frappe server. Frappe has no idea what /mobile/login is, because that route only exists inside your Vue app. It looks for a corresponding file or server route, doesn’t find one, and correctly returns a 404 “Page Not Found” error.

The solution is to tell the Frappe server that for any URL under /mobile/, it should always serve your main Vue index.html file. This lets the Vue app load, and then Vue Router can read the URL and show the correct page.


The Frappe Solution: Using website_route_rules

The cleanest way to solve this within the Frappe framework is to use the website_route_rules hook in your app’s hooks.py file. This hook intercepts URL requests before Frappe tries to find a page for them.

Here’s how to set it up.

1. Edit Your App’s hooks.py

In the hooks.py file of your custom Frappe app, add the following rule. If you already have website_route_rules defined, just add the new dictionary to the list.

# in your_app/hooks.py

website_route_rules = [
	{"from_route": "/mobile/<path:path>", "to_route": "mobile"},
]

What this rule does:

  • from_route: "/mobile/<path:path>": This is a pattern matcher. It tells Frappe to capture any request that starts with /mobile/ followed by anything else (e.g., /mobile/login, /mobile/dashboard/user1, etc.).
  • to_route: "mobile": This tells Frappe to serve the web page located at /mobile for any matching request. Frappe will look for www/mobile.html or www/mobile/index.html in your app’s directory.

2. Ensure Your Vue App’s Base File Exists

Make sure your built Vue application’s entry point is located at your_app/www/mobile.html or your_app/www/mobile/index.html. This file should contain the root element for your Vue app (like <div id="app"></div>) and the script tags that load your compiled JavaScript and CSS.

3. Check Your Vue Router base Config

For this to work seamlessly, you must also tell Vue Router what its base path is. In your Vue app’s router configuration file (e.g., src/router/index.js), make sure you’re using createWebHistory with the correct base path.

import { createRouter, createWebHistory } from 'vue-router';

const router = createRouter({
  // This is the crucial part!
  history: createWebHistory('/mobile/'), 
  routes: [
    {
      path: '/login', // becomes /mobile/login
      name: 'Login',
      component: () => import('../views/Login.vue'),
    },
    // ... other routes
  ],
});

export default router;

After making these changes, run bench migrate and bench restart to make sure the hooks are reloaded and the server is updated. Now, when you refresh https://bimbelku.web.id/mobile/login, Frappe’s website_route_rules will catch the request and serve the main mobile.html page, allowing your Vue Router to handle the rest.


2 Likes