Trends:our thoughts

Using Inertia with Laravel in 2024

Checking read time...

I was listening to a recent episode of the Mostly Technical podcast, where Ian and Aaron make their case for using Livewire over Inertia. We use Inertia on almost every client project we work on, so I thought I would help make a case for why you might choose to use Inertia in your Laravel project in 2024.

Livewire is great too!

While this article promotes the use of Inertia, I want you to know that we love Livewire too. It's a truly fantastic and exciting technology, with a very bright future, and we mean it no disrespect - I promise 🤝.

A refresher on Inertia

First, let's get up to speed on Inertia.

Inertia is a protocol created by Jonathan Reinink, with supporting libraries for React, Vue, Svelte, PHP, and Rails, with the goal of helping you write single-page apps (SPAs), without all the complexity that comes with SPAs. Think about managing sessions, tokens, APIs, routing, and asset cache busting.

Inertia helps us with all of these by acting as the glue layer between your frontend and backend technologies.

With Inertia applications, you are simultaneously writing both a server-side and a client-side rendered application, and it all just works seamlessly.

  • You write your Laravel routes, controllers, and form requests like you would if you were writing a Blade application.
  • You write your React, Vue, or Svelte application like you would if you were writing an SPA, however you can ignore the routing component completely as this is handled by Laravel.

For the backend, the main difference is you don't have to write any API code, and in your controllers you return Inertia::render('Blog/Index') instead of view('blog.index').

For the frontend, the main difference is that you write your frontend in Vue, React, or Svelte; you get sessions out of the box, so you never have to deal with tokens, and you don't have to manage your routes, which is a huge benefit.

How the protocol works

The Inertia docs do a great job of explaining the protocol, but the TLDR is that by calling Inertia::render(), after the initial full-page load is made and the Inertia frontend application has booted, the backend will only ever respond with XHR requests (think JSON payloads, just like an API).

This prevents the server from sending the boilerplate HTML on every pageload, saving you and your users a lot of bandwidth, and making the application feel very snappy.

Any parameters you pass into Inertia::render() will be JSON encoded and sent to the component as props, making them immediately available to your React, Vue, or Svelte components. Your frontend application will interpret the response from the backend, and render the appropriate component, using the props you passed to it.

Once you try it, it really feels quite magical to be writing a Vue (or others) SPA, with all the benefits Vue gives you, but with access to your controllers that feels like a Blade application.

Here's an example:

1<?php
2 
3namespace App\Http\Controllers;
4 
5use App\Models\Article;
6use Illuminate\Http\Request;
7use Inertia\Inertia;
8 
9class BlogController
10{
11 public function index(Request $request)
12 {
13 return Inertia::render('Blog/Index', [
14 'articles' => Article::query()
15 ->select([
16 'id',
17 'title',
18 ])
19 ->latest()
20 ->limit(9)
21 ->get(),
22 ]);
23 }
24}
1 
2<template>
3 <div class="grid grid-cols-3 gap-6">
4 <div v-for="article in $props.articles">
5 <span class="font-semibold text-lg">{{ article.title }}</span>
6 </div>
7 </div>
8</template>
9 
10<script>
11export default {
12 props: {
13 articles: Array,
14 },
15};
16</script>

Why you should use Inertia with Laravel

You don't have to create an API

Did you ever write an Angular frontend back in the day, and had to build out a separate API for your frontend to call? We did.

Did you have to figure out where to store your authentication token in the browser? We did.

Did you have to manage sessions expiring? We did.

Inertia removes all of these problems and more, because you aren't writing an API - you're writing a typical Laravel application that uses JavaScript instead of Blade.

  • You get sessions without needing authentication tokens.
  • You get form requests and redirects without needing API endpoints.

Sure, you can write actual API routes if you need them, and call them from your Inertia app, but it's not a requirement. And if you do, Inertia will use your session to make those calls, so you're covered there as well!

Your app will be really fast

You get a big performance boost by using Inertia, mostly because the backend only has to send through the actual data (as JSON props) needed to render your component, and not the HTML for those components.

Because the components themselves are just JavaScript files, they will be cached by the browser and by your CDN, so until you release an update to your application, you literally just send the user the HTML for your component once.

This differs from a Blade application, where you are sending the HTML with every single request and are unable to cache it in the browser, assuming there is some dynamic content on the page.

All JavaScript, all of the time

With Inertia, there is almost no Blade (technically two lines of Blade are required), so you spend all of your frontend time inside JavaScript, never having to make a conscious decision to implement a bit of functionality in Blade or to bring in some JS sprinkles.

In addition, putting your JS inside your Blade files has just never felt right to me. Now you can have your IDE support for Vue, React, or Svelte, as you are actually writing a Vue, React, or Svelte component, with the right file extension.

You can still do server-side rendering

Inertia has great support for server-side rendering, which will help keep your initial load really quick, and makes it easier for search engines to index your page.

It's really easy to setup using the packages that Inertia makes available.

Looking for help with your Laravel and Inertia project?

Why you might not use Inertia with Laravel

You can't leverage Blade

I'm not a huge fan of Blade. There, I said it.

It's an amazing layer on top of vanilla PHP syntax, and is great as a templating language, but it's always felt cumbersome to use.

  • I don't like that you don't know which PHP variables you have access to inside your Blade file, without checking the controller (or controllers) that rendered your Blade view.
  • I don't like that you can make database calls from your presentation layer. Given that it compiles down to just PHP, Blade doesn't force you to think about the data you're making available to your views.
  • I don't like the lack of good IDE support.

If you are a huge fan of Blade, you lose access to everything you love about it by using Inertia. It really is a case of choosing one or the other.

You might not need an SPA

You'll have to decide for yourself if you really need all the power that comes from a full SPA, at the cost (for those that see it that way) of writing your whole frontend in JavaScript.

We have started many projects where we didn't think we would need complex JS, only to find out later that it would be really handy to be able to use some feature of Headless UI, and to have to shoehorn it in, or build it ourselves with Alpine.js.

Maintenance, new features, and "complete software"

During the podcast, Aaron brought up the infamous modals presentation, which was a new and exciting feature that Jonathan previewed during Laracon Online 2021. It appeared to show a way to manage modals over the top of regular pages directly from your controller, giving you a direct route to create a new post (or whatever your domain model is), while still displaying the list of posts in the background underneath it.

This feature was never released, which led me at least to some feelings of abandonment. Jonathan wrote about why it got cut from the scope for v1.0 on Reddit. Given Jonathan has been working for the folks at Tailwind Labs for the last few years, naturally and completely understandably Inertia has appeared to become less of a focus for him personally.

Fortunately for all of us, Taylor Otwell, the founder and CEO of Laravel, has dedicated some of the Laravel core team to maintaining the Inertia repositories, so we are in very good hands there.

I completely agree with Ian and Aaron that a big drawback of Inertia is that it is "complete software", meaning it's not expected to get any further features. Despite it getting regular upgrades for dependencies and such, this does make it feel abandoned. Taylor tweeted about this in July 2023.

How to get the most from Inertia

If you're keen to get started with Inertia, here are our top tips for learning to love it.

Use Ziggy for routing

Love using the route() helper in Blade? If you're not using it already, you definitely should.

You can effectively achieve the same result using Tigten's Ziggy package. Ziggy will take your Laravel routes, serialize them as JSON, and embed them in your HTML when the page first loads. Route parameters, domains, prefixes, etc. will all be included too.

Ziggy will give you a route() method on the window object, which you can use to dynamically create the route string. Ziggy will also give you a route().current() method, which you can use as a truth test to see if the route you pass in is the route that is currently being visited. This is super helpful for creating dynamic navbars.

Here's an example comparing Blade and Vue.

1<div class="grid grid-cols-3 gap-6">
2 @foreach($posts as $post)
3 <div>
4 <a href="{{ route('posts.show', ['post' => $post]) }}"
5 class="font-semibold text-lg"
6 >{{ $post->title }}</a>
7 </div>
8 @endforeach
9</div>
1 
2<template>
3 <div class="grid grid-cols-3 gap-6">
4 <div v-for="post in $props.posts">
5 <Link :href="route('posts.show', {post: post.id})"
6 class="font-semibold text-lg"
7 >{{ post.title }}
8 </Link>
9 </div>
10 </div>
11</template>
12 
13<script setup>
14import {Link} from '@inertiajs/vue3';
15</script>
16 
17<script>
18export default {
19 props: {
20 posts: Array,
21 },
22};
23</script>

Use Inertia forms and Laravel form requests

Inertia comes with a form helper, and when paired with Laravel's form requests, you get an amazing experience that makes it a piece of cake to submit, validate, and display errors for your form inputs.

When you use the Inertia forms helper, it automatically handles the submission of form data to your Laravel backend. Laravel's form request validation then takes over to validate the incoming data according to the rules defined in your form request classes.

If the submitted data fails validation, Laravel automatically generates a response with the appropriate validation errors. Inertia automatically catches these errors and makes them available within your JavaScript components, without requiring any additional boilerplate code for error handling.

This is huge as it allows for a more intuitive and less error-prone development process. Developers can focus on defining validation rules within Laravel and building the user interface without worrying about the intricacies of error handling.

Because the validation errors are immediately displayed to users on submission, users also benefit, with a really quick and responsive UI.

1 
2<template>
3 <form @submit.prevent="submit"
4 class="grid gap-6"
5 > ...
6 <div>
7 <label for="first_name">
8 First name
9 </label>
10 
11 <input id="first_name"
12 v-model="form.first_name"
13 class="mt-1 block w-full"
14 type="text"
15 autocomplete="given-name"
16 />
17 
18 <p v-if="form.errors.first_name"
19 class="mt-2 text-red-500"
20 >
21 {{ form.errors.first_name }}
22 </p>
23 </div>
24 
25 <div>
26 <label for="middle_name">
27 Middle name
28 </label>
29 
30 <input id="middle_name"
31 v-model="form.middle_name"
32 class="mt-1 block w-full"
33 type="text"
34 autocomplete="additional-name"
35 />
36 
37 <p v-if="form.errors.middle_name"
38 class="mt-2 text-red-500"
39 >
40 {{ form.errors.middle_name }}
41 </p>
42 </div>
43 
44 <div>
45 <label for="last_name">
46 Last name
47 </label>
48 
49 <input id="last_name"
50 v-model="form.last_name"
51 class="mt-1 block w-full"
52 type="text"
53 autocomplete="family-name"
54 />
55 
56 <p v-if="form.errors.last_name"
57 class="mt-2 text-red-500"
58 >
59 {{ form.errors.last_name }}
60 </p>
61 </div>
62 
63 <div>
64 <p :on="form.recentlySuccessful"
65 class="mr-3"
66 >
67 Saved.
68 </p>
69 
70 <button :class="{ 'opacity-25': form.processing }"
71 :disabled="form.processing"
72 type="submit"
73 class="btn-primary"
74 >
75 Save
76 </button>
77 </div>
78 </form>
79</template>
80 
81<script>
82export default {
83 props: {
84 candidate: Object,
85 },
86 
87 data() {
88 return {
89 form: this.$inertia.form({
90 first_name: this.$props.candidate.first_name,
91 middle_name: this.$props.candidate.middle_name,
92 last_name: this.$props.candidate.last_name,
93 }),
94 };
95 },
96 
97 methods: {
98 submit: function () {
99 this.form.put(route('candidates.profile.update'));
100 },
101 },
102};
103</script>

Keep in mind that you can further cut down on the template by introducing reusable components to handle the logic for displaying errors.

Use Inertia middleware to share common data across pages

You can make use of Inertia's shared data middleware to automatically inject props into every Inertia page response.

This is tremendously helpful for giving your components context about the user, or anything else that you need to make your pages truly powerful.

A common example is details about the authenticated user (or lack thereof), permissions checks, customer branding settings, or unread notification counts. We also make use of the middleware to give us a canonical route that we can pass to Fathom Analytics.

When you share data via the middleware, it will be available in your JavaScript under $page.props. Here's an example from our SaaS RecruitKit:

1<?php
2 
3namespace App\Http\Middleware;
4 
5use Domains\Candidates\Models\Candidate;
6use Domains\Tenants\Settings\BrandingSettings;
7use Illuminate\Http\Request;
8use Inertia\Middleware;
9 
10class HandleInertiaRequests extends Middleware
11{
12 public function share(Request $request): array
13 {
14 return array_merge(
15 parent::share($request),
16 [
17 'meta' => [
18 'user' => function () use ($request) {
19 return $request->user();
20 },
21 
22 'notifications_count' => function () use ($request) {
23 /** @var Candidate $candidate */
24 $candidate = $request->user();
25 
26 if (! $candidate) {
27 return 0;
28 }
29
30 return $candidate
31 ->unreadNotifications()
32 ->count();
33 },
34 
35 'settings' => [
36 'branding' => function () {
37 /** @var BrandingSettings $settings */
38 $settings = app(BrandingSettings::class);
39 
40 return array_merge(
41 [
42 'name' => $settings->name,
43 'palette' => $settings->palette,
44 ],
45 $settings->getLogoUrls()
46 );
47 },
48 ],
49 
50 'fathom' => [
51 'canonical_url' => $request->getSchemeAndHttpHost().$request->route()->toSymfonyRoute()->getPath(),
52 ],
53 ],
54 ]
55 );
56 }
57}

Use Inertia events for triggering analytics

You can hook into all of the Inertia events using the router, which makes it trivial to track pageviews as users navigate your application, using Fathom Analytics.

1import './bootstrap'; ...
2import '../css/app.css';
3import {
4 createApp,
5 h,
6} from 'vue';
7import {
8 createInertiaApp,
9 Head as InertiaHead,
10 Link as InertiaLink,
11 router,
12} from '@inertiajs/vue3';
13 
14createInertiaApp({
15 resolve: (name) => resolvePageComponent( ...
16 `./Pages/${name}.vue`, import.meta.glob([
17 './**/*.vue',
18 '../img/**',
19 ])),
20 
21 setup({
22 el,
23 App,
24 props,
25 plugin
26 }) {
27 const vueApp = createApp({
28 methods: {
29 trackFathomPageview: function () {
30 if (!this.$inertia.page.props.meta) {
31 return;
32 }
33 
34 let url = this.$inertia.page.props.meta.fathom.canonical_url || '';
35 
36 if (window.fathom && url) {
37 fathom.trackPageview({
38 url: url,
39 });
40 }
41 },
42 },
43 
44 mounted() {
45 setTimeout(() => {
46 this.trackFathomPageview();
47 }, 100);
48 },
49 
50 render: () => h(App, props),
51 }) ...
52 .mixin({methods: {route}}) // Defined in the Ziggy package
53 .component('InertiaHead', InertiaHead)
54 .component('InertiaLink', InertiaLink)
55 .use(plugin);
56 
57 const mountedVueApp = vueApp.mount(el);
58 
59 router.on('navigate', (event) => {
60 mountedVueApp.trackFathomPageview();
61 });
62 },
63});

Final thoughts

Hopefully it's clear by now that we love Inertia, and have no plans to move away from it any time soon. We are satisfied with the fact that Inertia is very unlikely to get new features into the future, and grateful for the ongoing maintenance being completed by the Laravel core team.

If you're looking for further reading, we encourage you to check out the official Inertia documentation, and to also take a look at the Advanced Inertia course by Boris Lepikhin.

Want to receive updates from us?

Sign up for our newsletter to stay up to date with Laravel, Inertia, and Expo development.