So we have built our first page: list of the posts. Now let's build the second page to add a new post. For this lesson, I will introduce the vue-router
.
First, we need to install the vue-router
package.
npm install vue-router@latest
Then, we need to import vue-router
.
resources/js/app.js:
import './bootstrap'; import { createApp } from 'vue'import { createRouter, createWebHistory } from 'vue-router' import PostsIndex from './components/Posts/Index.vue' createApp({}) .component('PostsIndex', PostsIndex) .mount('#app')
Then, we need to build a list of routes, initialize the router, and enable that router inside createApp()
.
Also, we don't need to define components in the createApp()
anymore, as they are already defined in the routes.
resources/js/app.js:
import './bootstrap'; import { createApp } from 'vue'import { createRouter, createWebHistory } from 'vue-router'import PostsIndex from './components/Posts/Index.vue' const routes = [ { path: '/', component: PostsIndex },] const router = createRouter({ history: createWebHistory(), routes}) createApp({}) .component('PostsIndex', PostsIndex) .use(router) .mount('#app')
Now, let's create a Vue component for the create post page and add some dummy text.
resources/js/components/Posts/Create.vue:
<template> To-do.</template>
Next, we can create a new route and point it to this Vue component.
resources/js/app.js:
import './bootstrap'; import { createApp } from 'vue'import { createRouter, createWebHistory } from 'vue-router'import PostsIndex from './components/Posts/Index.vue'import PostsCreate from './components/Posts/Create.vue' const routes = [ { path: '/', component: PostsIndex }, { path: '/posts/create', component: PostsCreate }, ] const router = createRouter({ history: createWebHistory(), routes}) createApp({}) .use(router) .mount('#app')
But now, we cannot add new links to the navigation, they are in the Laravel Blade files of Breeze and not in the Vue files. So, we need to change all the structure to be .vue
files, including the main layout, so that we would be able to use <router-link>
.
First, we will use the resources/views/dashboard.blade.php
file as a main HTML file where we set id="app"
in the <body>
tag, instead of the previous <div id="app">
.
resources/views/dashboard.blade.php:
<!DOCTYPE html><html lang="{{ str_replace('_', '-', app()->getLocale()) }}"><head> <meta charset="utf-8"> <meta name="viewport" content="width=device-width, initial-scale=1"> <meta name="csrf-token" content="{{ csrf_token() }}"> <title>{{ config('app.name', 'Laravel') }}</title> <!-- Fonts --> <link rel="preconnect" href="https://fonts.bunny.net"> <link href="https://fonts.bunny.net/css?family=figtree:400,500,600&display=swap" rel="stylesheet" /> @vite(['resources/css/app.css', 'resources/js/app.js'])</head><body class="font-sans antialiased" id="app"></body></html>
Next, we need to have a root component: the main layout.
resources/js/layouts/App.vue:
<template> <div class="min-h-screen bg-gray-100"> <nav class="bg-white border-b border-gray-100"> <!-- Primary Navigation Menu --> <div class="max-w-7xl mx-auto px-4 sm:px-6 lg:px-8"> <div class="flex justify-between h-16"> <div class="flex"> <!-- Logo --> <div class="shrink-0 flex items-center"> <a href="/"> <svg viewBox="0 0 316 316" xmlns="http://www.w3.org/2000/svg" class="block h-9 w-auto fill-current text-gray-800"> <path d="M305.8 81.125C305.77 80.995 305.69 80.885 305.65 80.755C305.56 80.525 305.49 80.285 305.37 80.075C305.29 79.935 305.17 79.815 305.07 79.685C304.94 79.515 304.83 79.325 304.68 79.175C304.55 79.045 304.39 78.955 304.25 78.845C304.09 78.715 303.95 78.575 303.77 78.475L251.32 48.275C249.97 47.495 248.31 47.495 246.96 48.275L194.51 78.475C194.33 78.575 194.19 78.725 194.03 78.845C193.89 78.955 193.73 79.045 193.6 79.175C193.45 79.325 193.34 79.515 193.21 79.685C193.11 79.815 192.99 79.935 192.91 80.075C192.79 80.285 192.71 80.525 192.63 80.755C192.58 80.875 192.51 80.995 192.48 81.125C192.38 81.495 192.33 81.875 192.33 82.265V139.625L148.62 164.795V52.575C148.62 52.185 148.57 51.805 148.47 51.435C148.44 51.305 148.36 51.195 148.32 51.065C148.23 50.835 148.16 50.595 148.04 50.385C147.96 50.245 147.84 50.125 147.74 49.995C147.61 49.825 147.5 49.635 147.35 49.485C147.22 49.355 147.06 49.265 146.92 49.155C146.76 49.025 146.62 48.885 146.44 48.785L93.99 18.585C92.64 17.805 90.98 17.805 89.63 18.585L37.18 48.785C37 48.885 36.86 49.035 36.7 49.155C36.56 49.265 36.4 49.355 36.27 49.485C36.12 49.635 36.01 49.825 35.88 49.995C35.78 50.125 35.66 50.245 35.58 50.385C35.46 50.595 35.38 50.835 35.3 51.065C35.25 51.185 35.18 51.305 35.15 51.435C35.05 51.805 35 52.185 35 52.575V232.235C35 233.795 35.84 235.245 37.19 236.025L142.1 296.425C142.33 296.555 142.58 296.635 142.82 296.725C142.93 296.765 143.04 296.835 143.16 296.865C143.53 296.965 143.9 297.015 144.28 297.015C144.66 297.015 145.03 296.965 145.4 296.865C145.5 296.835 145.59 296.775 145.69 296.745C145.95 296.655 146.21 296.565 146.45 296.435L251.36 236.035C252.72 235.255 253.55 233.815 253.55 232.245V174.885L303.81 145.945C305.17 145.165 306 143.725 306 142.155V82.265C305.95 81.875 305.89 81.495 305.8 81.125ZM144.2 227.205L100.57 202.515L146.39 176.135L196.66 147.195L240.33 172.335L208.29 190.625L144.2 227.205ZM244.75 114.995V164.795L226.39 154.225L201.03 139.625V89.825L219.39 100.395L244.75 114.995ZM249.12 57.105L292.81 82.265L249.12 107.425L205.43 82.265L249.12 57.105ZM114.49 184.425L96.13 194.995V85.305L121.49 70.705L139.85 60.135V169.815L114.49 184.425ZM91.76 27.425L135.45 52.585L91.76 77.745L48.07 52.585L91.76 27.425ZM43.67 60.135L62.03 70.705L87.39 85.305V202.545V202.555V202.565C87.39 202.735 87.44 202.895 87.46 203.055C87.49 203.265 87.49 203.485 87.55 203.695V203.705C87.6 203.875 87.69 204.035 87.76 204.195C87.84 204.375 87.89 204.575 87.99 204.745C87.99 204.745 87.99 204.755 88 204.755C88.09 204.905 88.22 205.035 88.33 205.175C88.45 205.335 88.55 205.495 88.69 205.635L88.7 205.645C88.82 205.765 88.98 205.855 89.12 205.965C89.28 206.085 89.42 206.225 89.59 206.325C89.6 206.325 89.6 206.325 89.61 206.335C89.62 206.335 89.62 206.345 89.63 206.345L139.87 234.775V285.065L43.67 229.705V60.135ZM244.75 229.705L148.58 285.075V234.775L219.8 194.115L244.75 179.875V229.705ZM297.2 139.625L253.49 164.795V114.995L278.85 100.395L297.21 89.825V139.625H297.2Z"/> </svg> </a> </div> <!-- Navigation Links --> <div class="hidden space-x-8 sm:-my-px sm:ml-10 sm:flex"> <router-link to="/" active-class="border-b-2 border-indigo-400" class="inline-flex items-center px-1 pt-1 text-sm font-medium leading-5 text-gray-900 focus:outline-none focus:border-indigo-700 transition duration-150 ease-in-out"> Posts </router-link> <router-link to="/posts/create" active-class="border-b-2 border-indigo-400" class="inline-flex items-center px-1 pt-1 text-sm font-medium leading-5 text-gray-900 focus:outline-none focus:border-indigo-700 transition duration-150 ease-in-out"> Create Post </router-link> </div> </div> </div> </div> </nav> <!-- Page Heading --> <header class="bg-white shadow"> <div class="max-w-7xl mx-auto py-6 px-4 sm:px-6 lg:px-8"> <h2 class="font-semibold text-xl text-gray-800 leading-tight"> Dashboard </h2> </div> </header> <!-- Page Content --> <main> <div class="py-12"> <div class="max-w-7xl mx-auto sm:px-6 lg:px-8"> <div class="bg-white overflow-hidden shadow-sm sm:rounded-lg"> <div class="p-6 bg-white border-b border-gray-200"> <router-view></router-view> </div> </div> </div> </div> </main> </div></template>
See those <router-link>
and <router-view>
? Those come from vue-router, replacing typical HTML <a href
links. When someone clicks those links, it will not refresh the full page, but only the part that is inside of the router-view
. This is the SPA behavior.
The <router-view>
element is where all the main dynamic content will be loaded.
Now, we need to load this layout when creating a Vue app.
resources/js/app.js:
import './bootstrap'; import { createApp } from 'vue'import { createRouter, createWebHistory } from 'vue-router'import App from './layouts/App.vue' import PostsIndex from './components/Posts/Index.vue'import PostsCreate from './components/Posts/Create.vue' // ... createApp(App) createApp({}) .use(router) .mount('#app')
Now, after visiting the project, we have two links in the navigation.
And if you will click Create Post
you will that the PostsCreate
Vue component and the URL in the address bar changed.
The underline below active link is made by specifying classes to the active-class
inside the <router-link>
.
<router-link to="/" active-class="border-b-2 border-indigo-400" class="inline-flex items-center px-1 pt-1 text-sm font-medium leading-5 text-gray-900 focus:outline-none focus:border-indigo-700 transition duration-150 ease-in-out"> Posts</router-link>
Now we need just to fix one problem.
If you try to access /posts/create
directly via URL, you would get a 404 error.
To fix it, we need to add one route to the Laravel app: to point any route to the dashboard
view file.
routes/web.php:
Route::view('/', 'dashboard')->name('dashboard'); Route::view('/{any?}', 'dashboard') ->where('any', '.*');
Now, after visiting any page directly via browser URL, it will be loaded inside the Vue.