404 Not Found Errors with Vue Router History Mode

I’m working on setting up the Vue Router for my application, and I’ve encountered an issue with the history mode. When I use this mode, the URL updates correctly according to my route definitions, but refreshing the page results in a 404 error. If I don’t use history mode, the URL in the address bar doesn’t change as I navigate through my app.
I understand that to use history mode, certain server-side configurations are required. Upon reviewing the Nextcloud server’s source code, I noticed that it utilizes the history mode for Vue routing, suggesting that the server should already be configured to support this feature. However, I’m still facing the refresh issue and am unsure of how to proceed.

Still trying to figure out…


You have two options here:

  1. Use the web hash history mode and be done
  2. Use the (HTML5) hash history mode and configure the server accordingly.

Hash history mode

The hash history mode uses a hash added to the address to store the routing. So, for example, in the cookbook app we use this mode. The URLs look like https://example.com/apps/cookbook/#/recipe/1234. This is the URL for the route /recipe/1234 inside the Vue component located at the main index document.

The drawback is that the part after the # is not considered for SEO. So, as long as you are only using for internal routes, you are fine. Otherwise you should go to the web history mode.

Web history mode

When you activate web history mode, the corresponding address would be https://example.com/apps/cookbook/recipe/1234. Note that the browser does not know where the actual URL ends and where the routing inside the Vue component starts. Thus, a request is done to the actual URL as it is.

In order to cope with this, the backend needs fixing. The calendar app uses this mode. The corresponsing route defines /embed/:token/:view/:firstday.

The backend code in PHP defines corresponding routes to catch all possible values as well. So, the backend would know the token, the view name and the time range to display when reloading. It is not used, however but just provided to allow for clear parsing the URL as the actual URL hitting the server would be https://example.com/app/calendar/token/2345/daily/now (or similar) for example.

It is no bad idea to provide this for your app as it allows for more stable URLs and does not rely on the hash anchor of links to work.


Here’s my Vue routers:

const router = new VueRouter({
	mode: 'history', 
	base: generateUrl('/apps/qlcv'),
	routes: [
			path: '/',
			component: UpcomingTasks,
			name: 'work',
			path: '/work/:sharedWorkID',
			component: TaskList,

And here’s my routes.php

return [
    'routes' => [
        // Page
        ['name' => 'page#index', 'url' => '/', 'verb' => 'GET'],

        // Work
        ['name' => 'Work#createWork', 'url' => '/create_work', 'verb' => 'POST'],
        ['name' => 'Work#getUsers', 'url' => '/users', 'verb' => 'GET'],
        ['name' => 'Work#getWork', 'url' => '/works', 'verb' => 'GET'],
        ['name' => 'Work#updateWork', 'url' => '/update_work/{work_id}', 'verb' => 'PUT'],
        ['name' => 'Work#deleteWork', 'url' => '/delete_work/{work_id}', 'verb' => 'DELETE'],

When I refresh the page, the path /work/:sharedWorkID returns a 404 error. Do you mean I need to define a router for this path in routes.php which accepts param sharedWorkID?


As a route must be unique in the PHP part, you must provide a postfix for one of the routes. That would be something like

['name' => 'page#index', 'url' => '/work/:shareWorkId', 'verb' => 'GET', 'postfix' => 'selectedWork'],

It might be a good advice to collect all API endpoints (like /create_work etc) under a common prefix to reduce the risk of name clashes here (e.g. move to /api/create_work). Then you must ensure, that no Vue route starts with the /api prefix and all collisions are ruled out.