Introduction
Building a complex VueJS SPA implies a lot more challenges than an ordinary application. At my job we are creating a VueJS powered SAAS platform, which shows us how powerful this library is, but also how easy it is to misunderstand things.
The Problem
This application is built upon a set of APIs provided by Laravel. We also have a few routes with static regular pages. Our RouteServiceProvider.php
looks like the following:
<?php
public function map(Router $router)
{
$this->mapApiRoutes($router);
$this->mapClientRoutes($router);
}
/**
* Define the "client" routes for the application.
* These also includes static pages
*
* @param \Illuminate\Routing\Router $router
* @return void
*/
protected function mapClientRoutes(Router $router)
{
$router->group([
'namespace' => $this->namespace,
'middleware' => ['session'],
], function ($router) {
require base_path('routes/client.php');
});
}
/**
* Define the "api" routes for the application.
*
* @param \Illuminate\Routing\Router $router
* @return void
*/
protected function mapApiRoutes(Router $router)
{
$router->group([
'namespace' => $this->namespace,
'middleware' => 'api',
'prefix' => 'api',
], function ($router) {
require base_path('routes/api.php');
});
}
As you can see APIs routes are mapped before client routes. This is a specific requirement of Vue-Router, because we need to add a catch-all fallback route in order to serve the Vue application using “History mode”. Otherwise users will get 404 errors if they access a specific URL (that is defined in the routes
file of Vue-Router, but not in Server’s configuration).
In Laravel this catch-all route can be defined in the following way, at the end of the last route file:
<?php
Route::get('/{catchall?}', 'AppController@show');
Everything that is not matched in previous routes will be served by the AppController
, which returns the naked view that contains the files for bootstrapping the VueJS application.
However there is a little caveat, our server will “no longer report 404 errors as all not-found paths now serve up your index.html file”. To get around the issue, the documentation suggests to implement a catch-all route within the Vue app to show a 404 page:
const router = new VueRouter({
mode: 'history',
routes: [
{ path: '*', component: NotFoundComponent }
]
});
This works great for “regular” Vue pages, but what about the APIs? If we try to fetch a route that doesn’t exist, for example because we misspelled it, we notice that the status code of the response is 200 and the content is the HTML for the VueJS application. Not really what we want.
The Solution
At first we were really confused by this behaviour, however the solution is really easy and elegant. Simply we need to configure the catch-all route in order to ignore routes that start with the api
segment. We can define it using a regex constraint:
<?php
Route::get('/{catchall?}', 'AppController@show')->where('catchall', '^(?!api).*$')->name('administration');
Bonus: match only numeric routes
Do you need a regex constraint to match only numeric routes? Here we go:
<?php
Route::get('/{route?}', 'RouteController@show')->where('route', '([0-9]+(\/){0,1})*');