LaravelをAPIに専念させてVue.jsにフロントエンドを任せる、という構成でwebサイトを作ることを考えます。そうした時、Laravel側のルーティングはおおよそ次の様になります。
// web.php
Route::group(
static function () {
// ログイン
Route::get('login', 'Auth\LoginController@showLoginForm')->name('login');
Route::post('login', 'Auth\LoginController@login')->name('login');
Route::post('logout', 'Auth\LoginController@logout')->name('logout');
// 本体
Route::middleware(['auth:web'])->group(static function () {
// API
Route::prefix('api')->name('api.')->group(static function () {
Route::get('member/search', 'MemberController@search')->name('member.search');
Route::apiResource('member', 'MemberController', ['show', 'update']);
});
// Vue.jsアプリへのアクセス
Route::get('/{any}', static function () {
return view('pages.index');
})->where('any', '.*');
});
}
);
末尾の
Route::get('/{any}', static function () {
return view('pages.index');
})->where('any', '.*');
で上からルーティングを読んでいき、当てあはまらなかった全てのルーティングをキャッチしてindexページを返します。indexページの中にはVue.jsのアプリページが入っています。
このようにするとLaravel側はVue.js側のルーティングに対してほぼ完全に無関心になります。無関心故に404も返せなくなります。Vue.js側で404を実装する必要が出てきます。
まずはルーティングにキャッチできなかった謎URLを全て404にするルーティングの定義です。
import Vue from 'vue';
import Router, {RouterOptions} from 'vue-router';
import IndexPage from '@/pages/Index';
import MemberIndexPage from '@/pages/member/Index';
Vue.use(Router);
const options: RouterOptions = {
mode: 'history',
routes: [
{
path: '/',
name: 'root',
component: IndexPage,
},
{
path: '/member',
name: 'member',
component: MemberIndexPage,
},
{
// ここで上のルーティングに該当しなかったものを全てNotFoundPage送り
path: '*',
name: 'notFound',
component: NotFoundPage,
},
],
};
export default new Router(options);
少し手間なのがAPIの結果によってページが存在するかしないか判断する場合です。そういった際はbeforeEnterというルート単位ガード処理を定義し、その中でリダイレクトさせるとNotFoundPage送りにできます。
ナビゲーションガード | Vue Router
{
path: '/member/:id',
name: 'memberShow',
component: MemberShow,
beforeEnter: (to, from, next) => {
MemberRepository.find(to.params.id).then((member) => {
to.meta.member = member;
next();
}).catch((e) =>{
// APIの結果が例外処理ならば、とりあえず404送り。/404に該当するルーティングはないのでNotFoundPage送り
next({path: '/404', query: {msg: '存在しない会員のIDです'}});
});
},
props: (route)=> {
return {member: route.meta.member};
},
},