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}; }, },