@Moose_Said

Cairo, Egypt

@mosaid.bsky.social

🦋

- Director of Education @BitterBrains

- Vue.js Educator

- Full Stack developer

M

ostafa

Vue.js Beyond

Frontend

the

Discover              and

Laravel

Inertia.js

The Beginning

Why use Laravel with Vue?

Why use Laravel with Vue?

Why use Laravel with Vue?

Why use Laravel with Vue?

Why use Laravel with Vue?

Why use Laravel with Vue?

Why use Laravel with Vue?

Why use Laravel with Vue?

Why use Laravel with Vue?

Without needing deep PHP expertise

How to use Laravel with Vue?

Laravel Blade hosting the whole Vue instance

  • Vue serves as the core application.
  • Vue consumes Laravel APIs.
  • Vue Router manages client-side navigation.

Option 1

Decoupled Vue.js and Laravel Integration

  • Vue serves as the core application.
  • Vue consumes Laravel APIs.
  • Vue Router manages client-side navigation.

Option 2

Decoupled Nuxt.js and Laravel Integration

Option 3

  • Nuxt serves as the core application.
  • Nuxt consumes Laravel APIs.
  • Nuxt handles client and server routing.

The Laravel Backends for Vue.js 3 Course

Laravel Blade hosting Vue SFCs

  • Laravel serves as the core application.
  • Data passed to Vue components via Laravel from the server.
  • Laravel manages server-side navigation

Option 4

Each method, while effective, presented distinct challenges

Each method, while effective, presented distinct challenges

  • Vue navigation overhead
  • Giving up Laravel routing and middleware
  • Laravel APIs for Vue
  • SSR Complexity
  • Global components bloat

I just want to:

  • Use Vue.js for my frontend
  • Take full advantage of Laravel's features
  • Build a solid SPA (with SSR if needed)

But I don’t want to deal with creating APIs in Laravel

I just want to:

  • Use Vue.js for my frontend
  • Take full advantage of Laravel's features
  • Build a solid SPA (with SSR if needed)

Inertia steps in

And changes everything!

Inertia who?

Inertia is

Not a Framework

Inertia is

The Modern Monolith

First-party JavaScript and Laravel Library

Inertia is

The Modern Monolith

  • It serves as a smart approach to SPA development
  • Enables developers to build modern single-page Vue, React, and Svelte apps using classic server-side routing and controllers.
  • Designed for Laravel, Ruby on Rails, and Django developers.
class UsersController
{
	public function index()
    {
        $users = User::active()->orderByName()->get(['id', 'name', 'email']);

        return Inertia::render('Users', [ 'users' => $users ]);
    }
}
<!-- Pages/Users.vue -->
<script setup>
defineProps({ users: Array })
</script>

<template>
    <div v-for="user in users" :key="user.id">
      <p> {{ user.name }} </p>
    </div>
</template>

Build single-page apps,

without

building an API.

But how is Inertia doing it?

https://vueschoo.io

Initial Visit

https://vueschoo.io

🌐

Server

Initial Visit

Request

https://vueschoo.io

🌐

Server

Initial Visit

HTML

Request

Response

https://vueschoo.io

🌐

Server

Request

Initial Visit

<html>
  <head>
      <title>VueSchool</title>
      <link href="/css/app.css" rel="stylesheet">
      <script src="/js/app.js" defer></script>
  </head>
  <body>

  <div 
      id="app" 
      data-page="{'component':'Home','props':{'greet': 'true'}">
  </div>

  </body>
</html>

The returned HTML contains a root div with data-page attribute

HTML

Request

Response

https://vueschoo.io

🌐

Server

Initial Visit

<div 
	id="app" 
	data-page='{"component":"Home","props":{"greet": "true"}}'
></div>

HTML

Client

// Home.vue
<script setup>
defineProps(['greet'])
</script>

<template>
	<h1 v-if="greet"> Welcome to VueSchool </h1>
</template>

Welcome To VueSchool

Inertia

Request

Response

https://vueschoo.io

Courses

👆

Client-Side Navigation

https://vueschoo.io

Courses

👆

Client-Side Navigation

🌐

Server

XHR Request

X-Inertia: true

Headers

https://vueschoo.io

Courses

👆

Client-Side Navigation

🌐

Server

XHR Request

Inertia

X-Inertia: true

Headers

https://vueschoo.io

Courses

👆

Client-Side Navigation

🌐

Server

XHR Request

JSON Response

JSON

X-Inertia: true

Headers

https://vueschoo.io

Courses

👆

Client-Side Navigation

🌐

Server

JSON

RESPONSE
HTTP/1.1 200 OK
Content-Type: application/json
Vary: Accept
X-Inertia: true
{
  "component": "Courses",
  "props": {
    "coursesList": [{}]
  },
  "url": "/courses",
  "version": "version_number"
}

The returned JSON contains an encoded page object.

JSON Response

X-Inertia: true

Headers

XHR Request

https://vueschoo.io

Client-Side Navigation

🌐

Server

JSON

{
  "component": "Courses",
  "props": {
    "coursesList": [{}]
  },
  "url": "/courses",
  "version": "version_number"
}
// Courses.vue
<script setup>
defineProps(['courses'])
</script>

<template>
  <h1> Courses </h1>
  <ul> <li v-for="course in courses"> course.name </li> </ul>
</template>

Inertia

Courses

XHR Request

JSON Response

X-Inertia: true

Headers

<a href="/courses">courses</a>

Courses

👆

https://vueschoo.io

<router-link to="/courses">courses</router-link>
import { Link } from '@inertiajs/vue3'

<Link href="/courses">Courses</Link>
  • Lightweight wrapper around a standard anchor tag.
  • It intercepts the click event and prevents full page reloads.

Connects the Vue front-end with the Laravel backend

You get to use Vue components with Laravel server-side routing

Smooth sailing for Laravel developers who uses Vue

No need for Laravel APIs

SPA Haven

and Vue developers who uses Laravel

Routing

No need for Vue Router to handle client-side routing

Route::get('/courses', function () {
    return Inertia::render('Courses', [
        'canView' => Route::has('subscribed'),
        'coursesData' => data
    ]);
})->name('courses');

You can create Laravel web routes and use Inertia::render

to render the corresponding page

This will render the Courses.vue component
from the ~/resources/js/Pages/ directory

Routing

Route::get('/courses', function () {
    return Inertia::render('Courses', [
        'canView' => Route::has('subscribed'),
        'coursesData' => data
    ]);
})->name('courses');

Authentication

You get to keep your server-side Laravel authentication system 😎

Server-Side Rendeirng

Inertia supports Server-Side Rendering

The returned HTML contains the actual content

<!DOCTYPE html>
<html>
  <head> .. </head>

  <body>
  	<main> Actual content .. </main>
  </body>
</html>

Sharing Data

use Inertia\Inertia;

// Synchronously...
Inertia::share('appName', config('app.name'));

The Inertia::share feature allows for global data sharing.

This means that the specified data will be included in every request's response.

use Inertia\Inertia;
use App\Models\Cart;

// Lazy...
Inertia::share('shoppingCart', fn () => auth()->user()
        ? Cart::where('user_id', auth()->user()->id)->get()
        : null
);

Accessing Shared Data

Shared data will be available in usePage().props

// useShoppingCart() composable
import { computed } from "vue";
import { usePage } from "@inertiajs/vue3";

export function useShoppingCart() {
    const page = usePage();
    return computed(() => page.props.shoppingCart);
}
<script setup>
import { useShoppingCart } from "../Composables/useShoppingCart";

const shoppingCart = useShoppingCart();
</script>

<template>
  <ul>
	<li v-for="cartItem in shoppingCart.items">{{ cartItem.name }}</li>
  </ul>
</template>

SEO - <Head>

Inertia provides helper component for the page meta

<Head>
  <title>VueSchool</title>
  <meta name="description" content="Awesome learning experience">
</Head>

<Link>

<Link href="/logout" 
      method="post" 
      as="button" 
      type="button"
      >
  Logout
</Link>

// Renders as...
<button type="button">Logout</button>

Can be displayed as any HTML element using the as attribute

<Link>

<Link href="/logout" 
      method="post" 
      as="button" 
      type="button"
      >
  Logout
</Link>

// Renders as...
<button type="button">Logout</button>

Can send POST/PUT/PATCH/DELETE requests using the method attribute 

<Link>

<Link href="/endpoint" 
      method="post" 
      :data="{ foo: bar }"
      >
  Save
</Link>

When making POST or PUT requests,

you can add additional data to the request using the data attribute.

<Link>

<Link href="/endpoint" 
      method="post" 
      :data="{ foo: bar }"
      :headers="{ foo: bar }"
      >
  Save
</Link>

The headers attribute allows you to add custom headers to an Inertia link

Forms

<script setup>
import { useForm } from '@inertiajs/vue3'

const form = useForm({
  email: null,
})
</script>

<template>
  <form @submit.prevent="form.post('/newsletter')">
    
    <input type="text" v-model="form.email">
    <p v-if="form.errors.email">{{ form.errors.email }}</p>
    
    <button type="submit" :disabled="form.processing">Subscribe</button>
    
  </form>
</template>

Inertia includes a form helper designed to help reduce the amount of boilerplate code needed for handling typical form submissions.

Forms: Request

<script setup>
import { useForm } from '@inertiajs/vue3'

const form = useForm({
  email: null,
})
</script>

<template>
  <form @submit.prevent="form.post('/newsletter')">
    
    <input type="text" v-model="form.email">
    <p v-if="form.errors.email">{{ form.errors.email }}</p>
    
    <button type="submit" :disabled="form.processing">Subscribe</button>
    
  </form>
</template>

It supports GET, POST, PUT, PATCH, and DELETE methods for

handling various request types.

If there are form validation errors, they are available via the errors property. 

Forms: Validation

<script setup>
import { useForm } from '@inertiajs/vue3'

const form = useForm({
  email: null,
})
</script>

<template>
  <form @submit.prevent="form.post('/newsletter')">
    
    <input type="text" v-model="form.email">
    <p v-if="form.errors.email">{{ form.errors.email }}</p>
    
    <button type="submit" :disabled="form.processing">Subscribe</button>
    
  </form>
</template>

It can track the form's processing state

Forms: Processing

<script setup>
import { useForm } from '@inertiajs/vue3'

const form = useForm({
  email: null,
})
</script>

<template>
  <form @submit.prevent="form.post('/newsletter')">
    
    <input type="text" v-model="form.email">
    <p v-if="form.errors.email">{{ form.errors.email }}</p>
    
    <button type="submit" :disabled="form.processing">Subscribe</button>
    
  </form>
</template>
<script setup>
import { useForm } from '@inertiajs/vue3'

const form = useForm('uniqueId', {
  email: null,
})
</script>

By providing a unique ID to the useForm helper, the form data and errors will automatically be remembered if the user navigates away

Forms: Remembering State

When using Inertia with Laravel, you get CSRF protection out of the box.

No additional configuration is required

Forms: CSRF Protection

Forms: File Upload

<script setup>
import { useForm } from '@inertiajs/vue3'

const form = useForm({
  avatar: null,
})
</script>

<template>
  <form @submit.prevent="form.post('/avatars')">
    
    <input type="file" @input="form.avatar = $event.target.files[0]" />
    
    <progress v-if="form.progress" :value="form.progress.percentage" max="100">
      {{ form.progress.percentage }}%
    </progress>
    
    <button type="submit">Submit</button>
  </form>
</template>

When making Inertia requests that include files,

Inertia will automatically convert the request data into a FormData object.

<script setup>
import { useForm } from '@inertiajs/vue3'

const form = useForm({
  avatar: null,
})
</script>

<template>
  <form @submit.prevent="form.post('/avatars')">
    
    <input type="file" @input="form.avatar = $event.target.files[0]" />
    
    <progress v-if="form.progress" :value="form.progress.percentage" max="100">
      {{ form.progress.percentage }}%
    </progress>
    
    <button type="submit">Submit</button>
  </form>
</template>

The form helper provides easy access to the current upload progress

Forms: Upload Progress

Partial Reloads

import { router } from '@inertiajs/vue3'

const filterCourses = () => {
  // fetch and filter
  router.reload({ only: ['courses'] })
}

When re-requesting the same page,

you can selectively reload specific properties without altering surrounding data.

Partial Reloads

import { Link } from '@inertiajs/vue3'

<Link href="/courses?free=true" :only="['courses']">Show Free Courses</Link>

The 'only' option is also available as a prop to the <Link> component.

Inertia, Vue, and Laravel work smoothly together to build dynamic, server-driven single-page apps.

It still got so many more features to explore

inertiajs.com

Polling: Auto-refresh data at intervals for real-time updates.

Prefetching: Fetch data in advance for instant responses.

Deferred Props: Load essential data first for faster UI.

Infinite Scrolling: Load content as users scroll.

Lazy Loading: Load data only when needed.

But, it's not the only way to do it

// UserProfile.vue
<php>
  // Define a prop in PHP
  $name = prop(Auth::user()->name);
</php>

<template>
  <!-- Use it in Vue! -->
  Hello {{ name }}!
</template>

Crazy stuff are waiting ahead!

// UserProfile.vue
<php>
  // Define a prop in PHP
  $name = prop(Auth::user()->name);
</php>

<template>
  <!-- Use it in Vue! -->
  Hello {{ name }}!
</template>

Laravel Fusion

By: Aaron Francis

github.com/fusion-php/fusion

But before I go!

Mastering Nuxt: 2025 Edition

Full Stack Unleashed

masteringnuxt.com/2025

Invest in

Invest in BitterBrains

bitterbrains.com

republic.com/bitterbrains

Grab Some Swag 🤗

I'll post out the slides after this

@Moose_Said

@mosaid.bsky.social

🦋

Thank you 🙏

Go code awesome things!

(with Vue and Laravel 😉)