Lloyd Miller Lloyd is a freelance full-stack web developer specializing in the RILT stack (React, Inertia, Laravel, Tailwind). He also loves Jamstack and builds websites with Nuxt.js, Next.js, and Gatsby.

Laravel Breeze’s Inertia-React stack vs. Next.js and Gatsby

Choosing your next React framework

12 min read 3467

Laravel Breeze's Inertia-React stack vs. Next.js and Gatsby

Recently, the Laravel team made it easy to set up a React stack with just one command, making Laravel a viable full-stack React framework.

Madness, you say? Well, what makes Next.js and other similar React frameworks so great?

They’re great because they allow for better server-side rendering, routing, authentication, state management, and session management, to say the least.

In this article, we’ll show you why Laravel Breeze’s Inertia-React stack is so much better and easier to use than Next or Gatsby. Even though this is about Laravel’s suitability as a React framework, many of these points can be understood in a Vue or Svelte context, too!

What is Laravel Breeze?

Laravel Breeze is one of the starter kits that was introduced with Laravel 8 in fall 2020 — the other one is Jetstream. Both Laravel Breeze and Jetstream come with baked-in authentication, as well as the routes, controllers, and views that you’ll need to quickly set up a large application. Breeze also comes with frontend scaffolding. Both are styled with Tailwind CSS.

Though you can use normal Blade templates with Laravel Breeze, you can also use the Inertia.js stack.

Think of Inertia.js as an adapter that connects two devices that weren’t made to work directly with each other: instead of having to create a REST API, Inertia.js allows developers to connect a React, Vue, or Svelte frontend with their Laravel backend.

Say what you want about PHP, but it comes with a lot of tooling right out of the box. Yes, there are trade-offs when using a stack like this instead of an all-JavaScript stack, but it’s a great way to build a powerful monolith — this way, we can have both the benefits of PHP on the backend and a JavaScript framework on the frontend.

Why should you choose Laravel Breeze as your next React framework?

Setup is, well, a breeze

The developer only needs to look over a few lines of code in App.js to get React and Laravel to talk to each other.

Before the Laravel team made it easy to spin up the Inertia-React stack, developers had to do a lot of manual work to get Inertia working with React, as Vue was the default.

We made a custom demo for .
No really. Click here to check it out.

Now, the developer will not have to write anything at all, as scaffolding is done automatically by running php artisan breeze:install react.

The App.js file will look something like this:

js
require('./bootstrap')

// Import modules...
import React from "react"
import { render } from "react-dom"
import { InertiaApp } from "@inertiajs/inertia-react"
import { InertiaProgress } from '@inertiajs/progress'

const el = document.getElementById('app')

render(
    <InertiaApp
        initialPage={el ? JSON.parse(el.dataset.page) : "{}"}
        resolveComponent={(name) => require(`./Pages/${name}`).default}
    />,
    el
);

InertiaProgress.init({ color: '#4B5563' })

Inertia.js is pretty easy to use

For a developer that is already steeped in the world of JavaScript, there is virtually no barrier to entry if they have some knowledge of PHP and Laravel. The Inertia docs are pretty easy to read and cover just about every use case you need to build your app.

In the past, when developers wanted to have a Laravel backend and a JavaScript SPA, they had to build an API with Laravel and use a JavaScript framework that was hosted separately. Now, developers can just use Inertia.

The way it works is pretty genius: the first visit to the page loads pure HTML, and then data is loaded to the page — without a full reload by way of XHR and JSON. Inertia also removes the need for a REST API and gives developers the ability to build a big and beautiful SPA with a React frontend and PHP backend.

It also makes it extremely simple to pass data straight from your database to the client, removing the need for extra frontend libraries like Axios that other frameworks need to do the same thing.

To put this into perspective, let’s compare the way data is sent from the client to the backend with a Laravel API and an SPA built with Next, and the new way we can do this with Inertia.

Data sharing in a Next.js app vs. Laravel Breeze and Inertia.js

First, the developer installs and sets up something like Laravel Sanctum to ensure that requests are authorized with tokens, cookies, or some combination. Then, they would need to install and set up the CORS package to prevent CORS issues.

When that is set up, including the middleware to prevent the need for CSRF protection on the frontend, routes are set up in routes/api.php.

So, let’s say we need a route to create a bank account. Our route would look something like this, where createAccount is the controller method that will handle the request from the frontend:

php
Route::post('create-account', [AccountController::class, 'createAccount']);

Then in the Next SPA, extra work needs to be done to ensure CORS and authentication issues don’t happen.

Frontend developers should be very familiar with CORS issues, and they will most likely come up when the frontend and backend are hosted separately. To solve these issues and handle cookies and other factors, developers end up installing an authentication library such as NextAuth.js or next-iron-session.

When all of that is set up, the function to create the account will use fetch or axios to submit the data and wait for a response from the API. That function would look something like this:

js
import axios from 'axios'
…
    const [account, setAccount] = useState({
        phone: "", street: "", unit: "", city: "", state: "", zip: ""
    })

    async function handleSubmit(){
        try {
            const accountData = JSON.stringify(account)
            const response = await axios(`${apiUrl}/create-account`, accountData, {
                header: {
                    'Authorization': `Bearer ${user.token}`,
                }
            })

            console.log(response.message)
        }
        catch(e){
            console.log(e.errors)
        }
    }

That’s a lot of work!

With Inertia, there is no need to install extra libraries or write so many lines of code to handle CORS issues and authentication.

The only thing the developer has to do is share data between Laravel and Inertia, so that data is sent with subsequent renders after the first visit, set the route, and use Inertia visits to submit and get data.

Inertia visits are basically at the core of how Inertia works: when we click an Inertia <Link /> or do this programmatically with manual visits (more on them below), the library performs an XHR instead of a page reload. JSON is returned from the server and the client-side of Inertia swaps out the old data with the new.

There are different ways to share data between Laravel and Inertia, but personally, I like to use flashed messages. To do this, I simply place a few lines in app/Providers/AppServiceProviders.php:

php
<?php

namespace App\Providers;

use Illuminate\Support\Facades\Session;
use Illuminate\Support\ServiceProvider;
use Inertia\Inertia;

class AppServiceProvider extends ServiceProvider
{
    ...

    public function boot()
    {
        Inertia::share('flash', function(){
            return [
                'message' => Session::get('message')
            ];
        });
    }
}

The route can look the same as above, and I can use manual visits in my frontend where methods such as onStart, onSuccess, and onError help to perform events before, during, and after the exchange of data.

Manual visits mimic Promises, but make creating and handling events better than chaining then statements. Let me demonstrate:

js
    const [account, setAccount] = useState({
        phone: "", street: "", unit: "", city: "", state: "", zip: ""
    })

    function handleSubmit(){
        const accountData = JSON.stringify(account)
        Inertia.post(`create-account`, {data: accountData}, {
            onStart: () => {
                // Do something the moment request is made
            },
            onSuccess: response => {
                console.log(response.message)
            },
            onError: e => {
                console.log(e.errors)
            }
        })
    }

Though Inertia is supposed to be framework agnostic, there is first-party support for Laravel and Rails on the backend and React, Vue, and Svelte on the frontend.

Authentication with Laravel Breeze

If you’re coming from the Gatsby or Next world, you already know how complicated authentication can be. Even when building a simple library, you’ll still have to spend a lot of time setting up login and registration pages, tokens, cookies, email verification, password resets, and routes, among other things.

The most beautiful thing about Laravel is their Starter Kits, which is a large part of how they make authentication so easy. With Laravel Breeze, you can build a system for login, registration, password reset, email verification, and password confirmation with just one command!

If you choose the Inertia-React stack, login, registration, and dashboard pages with their corresponding routes are already done up for you! You can also choose to implement OAuth by extending Breeze using Laravel Passport.

This is a huge advantage over other methods because you don’t have to use libraries to handle complex session and state management for authentication to work properly. With Laravel, everything you need for authentication comes right out of the box.

Improved session and state management

Session and state management for large apps in React is excruciating without using any libraries or packages. Still, handling state in React is important for session management in Next and Gatsby.

Laravel makes session and state management so much easier. For sessions, Laravel provides you with several ways in which you can store sessions, including:

  • Files
  • Cookies
  • Databases
  • Memcached/Redis
  • AWS DynamoDB
  • Arrays

You can even use your own custom session drivers. From there, interacting with and saving to the session can be achieved with just two lines of code.

The Inertia-React stack of Laravel Breeze further negates the need for any state management on the client side, making for a complete and pleasurable experience when building auth systems or other features that need complex state management.

You also have more complex abilities to control how your Laravel app handles sessions, such as limiting the number of HTTP requests that can be made at the same time.

The code to do this in Laravel is really reminiscent of async-await in JavaScript. When a request is made, a “session lock” is acquired so that subsequent requests with the same session ID will have to wait for the first session to finish executing before they can execute.

If you look at the code sample below, you’ll see the block method accepts two arguments:

  1. The first defines the maximum number of seconds the session lock should be held before it’s released
  2. The second argument defines the number of seconds a request should wait for a session lock

If a session lock takes too long to be acquired, an exception is thrown. It’s a genius way to circumvent PHP’s asynchronous limitations.

php
Route::post('/profile', function () {
    //
})->block($lockSeconds = 10, $waitSeconds = 10)

Route::post('/order', function () {
    //
})->block($lockSeconds = 10, $waitSeconds = 10

Easier client-side asset compiling

Just like Gatsby and Next, Laravel uses webpack for compiling client-side assets. Configuring webpack is not an easy task — but Laravel has a fix for that in the form of Laravel Mix.

Laravel Mix makes it easy to implement all kinds of tools and technologies for your frontend. It does this by providing an API to dictate the build steps to compile these assets.

Don’t believe me? Below is what a Laravel Mix file looks like in a project using Tailwind (and PostCSS modules), TypeScript, and React:

js
const mix = require('laravel-mix');

mix.ts('resources/js/app.tsx', 'public/js')
    .react()
    .postCss('resources/css/app.css', 'public/css', [
        require('postcss-import'),
        require('tailwindcss'),
        require('autoprefixer'),
    ])
    .webpackConfig(require('./webpack.config'))

if (mix.inProduction()) {
    mix.version()
}

The code above tells Laravel Mix to look in resources/js for the App.js or App.tsx file and compiles its contents to public/js/app.js, the JavaScript file that is read by the web browser. Chaining the react() helper lets Laravel Mix know to expect React and JSX — there’s also a vue() helper, if you’re using Vue.

This code also tells Laravel Mix to use PostCSS to compile the CSS in resources/css/app.css, which are Tailwind directives, to actual CSS and place it in public/css. If the developer wants to set up an alias for paths, they can do so in the webpack config file.

Just like Gatsby and Next, you don’t have to stick with the Laravel Mix/webpack default. If you want to use Vite, Esbuild, or any other similar build tools, Laravel has instructions for that, too.

Simpler routing with Laravel Breeze

Both Next and Gatsby have a pages folder, inside of which you can place files that correspond to the pages of the app. Both use a routing API that lets you use brackets (curly with Gatsby, or square with Next) as the file name to denote dynamic pages.

Both frameworks try hard to make routing easier and more intuitive, but sometimes they need a lot more fiddling to work properly. And since complex business logic is often handled in these files, readability and good code organization often suffer.

Laravel is built with a model-view-controller (MVC) architecture, so it has routes that direct requests from your view on the frontend to your controllers. The MVC architecture forces good code organization practices, as you know your logic will be based in your controllers and your client is sending requests through routes that channel responses back to the client.

The Inertia-React stack handles routing on the server-side, which is different from SPAs built with other frameworks. Routes are found in the routes folder, and in that folder you can find web.php, where most of your routes will be housed. All JavaScript files — including the project’s React pages, components, etc. — can be found in the resources folder.

See how a sample Laravel Breeze and React project with the resources and routes folder is set up below:

A sample Laravel Breeze and React project structure

More flexible data management

Gatsby is very opinionated about how data should be handled and dictates that GraphQL be used in most cases. That’s great for devs that love GraphQL, but can be a bit cumbersome for those who don’t. Next is not as opinionated, but devs will still need to install and set up a lot of moving parts to get data from the database to the client.

Laravel is also opinionated with its ORM, Eloquent, but the beauty of the framework is that you can very easily not use it. Instead, you can directly query the database with regular MySQL statements if you need, or you can use another PHP ORM of your choosing.

One popular ORM is Doctrine, which is used often with other frameworks such as Symfony, Zend, and CakePHP. If you want speed and performance, Doctrine is certainly something to consider.

However, if you’re concerned about how well it will blend with the rest of the framework, Eloquent is the best of them all. To understand this, let’s look at the way a database table is created in both Eloquent and Doctrine.

Eloquent

php
Schema::create('User', function($table)
{
    $table->id();
    $table->string('name');
});

Doctrine

php
<?php
use Doctrine\ORM\Mapping AS ORM;

class User
{
    private $id;
    private $name;
}
?>

In terms of what databases PHP is compatible with, you will hardly have to worry. PHP has drivers and extensions for a wide variety of database systems such as SQLite, MongoDB, and PostgreSQL, so you’re not stuck with just MySQL.

Laravel helps you to easily set up REST APIs, but you can create GraphQL APIs with Laravel, too! If you do choose a GraphQL API for your app, you have a choice between Rebing’s GraphQL library or Lighthouse.

You can even consume GraphQL APIs — or just about any API you can think of — with just a couple lines of code:

php
use Illuminate\Support\Facades\Http;

$response = Http::get('http://someapi.com');
dd($response); // dump data

Enjoy a lively community and plugin ecosystem

Even though they are built in an opinionated way, Gatsby plugins are excellent and plentiful. Next is extensible, too. Don’t be fooled, though — Laravel’s plugin ecosystem is far from paltry. There is a Laravel plugin for just about everything under the sun, and they are housed in one easy-to-use directory called Packalyst.

Though PHP only uses a few lines of code to implement features that would require hundreds or thousands of lines in JavaScript, for everything else, there are plugins and libraries that can easily be installed in your project.

And, of course, if you have an issue with your project, or if you just want to network with other Laravel devs — especially those that use the Inertia stack — there are thousands of developers that you can follow on Twitter, Stack Overflow, GitHub, and Discord. It’s a kind, welcoming, and non-toxic community that obviously loves the technology it uses and wants others to love this technology, too.

The friendly Laravel community!

Roadblocks to wider adoption

SEO is difficult

Currently, Inertia.js renders webpages on the client-side. The creators of Inertia.js argue that Inertia wasn’t made for webpages that need SEO, so creators should use Blade for those kinds of pages instead. There are other workarounds such as using meta tags, and some people have developed workarounds.

Don’t let this be a dealbreaker: an SSR mode for Inertia.js is coming very soon. It’s worth mentioning that, at the time of this article’s publication, GitHub sponsors have early access to it. There are also some SaaS apps in production that have been successfully using Inertia.js.

PHP is still tricky

Though I listed it as a pro earlier, to be fair, PHP does fall short to Node.js in some ways, including concurrency, asynchronous requests, and speed. Node’s strength with async processing enables faster build times and increases flexibility around how an app is built. PHP does have some plugins that allow for asynchronous processing, but it’s not the same.

This shouldn’t let you put PHP down, though. Thanks to a newly energized community, the language is adding new features and is already a lot faster and much more flexible than it was in the recent past. And it still handles many things better than Node, such as:

  • Native support for databases such as MySQL, MongoDB, and PostgreSQL
  • Compatibility with most hosting service providers
  • Object-oriented programming

Limited static generation capabilities

React frameworks like Gatsby and Next can pre-render pages to static HTML, CSS, and JS files. Static site generation has been a growing paradigm recently as Jamstack and serverless architecture adoption have skyrocketed.

As a result of this, developers have been eager to switch to those and other similar frameworks so that they can build full-stack apps by just focusing on the frontend and the way data is fed into the app.

Yet, apps can be broken up into bits and pieces where many functions and capabilities are handled by third-party APIs and microservices. Sure, Laravel Breeze can utilize third-party services too, but the concept that powers Inertia is that building powerful monoliths is the only thing that you need to do.

Why would you want to build a monolith? Here are a few reasons:

  • Monoliths have less complexity and are easier to maintain — microservices only increase the complexity of an app, as there are many more moving parts and areas to monitor
  • It’s the traditional way of building an app and is the way most developers, especially more senior developers, learned to make apps. This makes them easier to build for many developers
  • Deploying monoliths is a much simpler process because all parts are in one place, and they are guaranteed to work because they are built using the same language or framework
  • It’s much simpler to debug and test a monolithic application — with one indivisible unit, developers can run simple end-to-end tests

If you want to use Laravel but also want to use Jamstack, you could check out other Laravel-based technologies such as Statamic and Jigsaw. With these tools, you get CMS capabilities and the joy of building with PHP, the Blade template, and other Laravel features.

Conclusion

Some developers complain that Laravel does too much “magic” and they don’t have the control that other PHP frameworks have. They’re wrong, though, because Laravel provides all the control a developer needs and the code abstraction helps for a much better developer experience.

This is the reason why Laravel is the most popular PHP framework by far and the most popular backend framework. Also, isn’t a magical feeling the sign of a great framework?

As developers, we use frameworks to make building complex apps easier, and Laravel, especially Laravel Breeze’s Inertia-React stack, makes building complex React apps incredibly easy.

While many app developers go serverless and split their app up into many parts, Inertia has proven that you can build big and powerful React SPA monoliths. For state and session management alone, Laravel Breeze’s Inertia-React stack is worth a try compared to the competition.

: Full visibility into your web apps

LogRocket is a frontend application monitoring solution that lets you replay problems as if they happened in your own browser. Instead of guessing why errors happen, or asking users for screenshots and log dumps, LogRocket lets you replay the session to quickly understand what went wrong. It works perfectly with any app, regardless of framework, and has plugins to log additional context from Redux, Vuex, and @ngrx/store.

In addition to logging Redux actions and state, LogRocket records console logs, JavaScript errors, stacktraces, network requests/responses with headers + bodies, browser metadata, and custom logs. It also instruments the DOM to record the HTML and CSS on the page, recreating pixel-perfect videos of even the most complex single-page apps.

.
Lloyd Miller Lloyd is a freelance full-stack web developer specializing in the RILT stack (React, Inertia, Laravel, Tailwind). He also loves Jamstack and builds websites with Nuxt.js, Next.js, and Gatsby.

Testing accessibility with Storybook

One big challenge when building a component library is prioritizing accessibility. Accessibility is usually seen as one of those “nice-to-have” features, and unfortunately, we’re...
Laura Carballo
4 min read

Leave a Reply