Building the Modern Monolith: Master Inertia.js with Laravel

Overview of the Inertia Approach

represents a fundamental shift in how we approach the "modern monolith." Historically, developers faced a binary choice: the simplicity of
Laravel
Blade
templates with full page reloads, or the complexity of a fully decoupled
Single Page Application
(SPA) requiring a custom API. Inertia acts as the glue. It allows you to build a frontend using modern libraries like
Vue.js
or
React
while keeping your routing, controllers, and state management firmly in the backend. This eliminates the need for
JSON
APIs and client-side routing, providing the snappy feel of a desktop app with the productivity of a classic monolith.

Prerequisites and Tools

To follow this tutorial, you should have a solid grasp of

and basic
Laravel
routing. Familiarity with
JavaScript
and a component-based framework—specifically
Vue.js
—is necessary as we explore the frontend implementation. You will need a development environment with
Node.js
and
Composer
installed.

Key Libraries & Tools

  • Laravel: The backend framework handling logic and data.
  • Vue 3: The reactive frontend library used for UI components.
  • Jetstream: A Laravel starter kit that provides a pre-configured Inertia and Vue environment.
  • Vue DevTools: Essential for inspecting page props and state.
  • Network Tab: Used to monitor XHR requests and partial data transfers.

The Core Render Flow

In a standard Laravel app, you return view(). In an Inertia app, you return Inertia::render(). This subtle change is where the magic happens. On the initial request, the server sends a full

document. Subsequent requests are intercepted by Inertia, which instructs the server to send back a JSON payload instead.

// In your Laravel Controller
public function index()
{
    return Inertia::render('TravelStories/Index', [
        'stories' => TravelStory::latest()->get(),
    ]);
}

On the frontend, the

component receives these props automatically. You don't need to fetch data in a mounted() hook or manage
Axios
calls manually. The data is simply there.

Navigation and the Link Component

Standard <a> tags cause full page refreshes, wiping out the application state. To achieve a true SPA feel, you must use the <Link> component.

import { Link } from '@inertiajs/vue3';

// Usage in template
<Link href="/statistics" class="btn">View Stats</Link>

When you click this link, Inertia makes an XHR request. The server returns only the data needed for the new page, and Inertia swaps the component out without a browser refresh. This reduces the transfer size from dozens of requests to a single, lean JSON payload.

Optimizing Data with Partial Reloads

Partial reloads allow you to request a subset of data from the server. This is vital for heavy pages where you might only need to update a chart or a list based on a filter. You define these in your controller using closures to ensure they only run when requested.

return Inertia::render('Statistics', [
    'chartData' => fn() => $this->getChartData(),
    'listData' => Inertia::lazy(fn() => $this->getHeavyData()),
]);

By using Inertia::lazy(), the data is excluded from the initial page load. You can then trigger a load from the frontend using the router.reload method with the only attribute:

router.reload({ only: ['listData'] });

Syntax Notes and Best Practices

Always use

to transform your data. Passing raw
Eloquent
models often exposes sensitive information like email addresses or internal IDs. By using a Resource, you ensure the frontend receives only the slimmed-down data it actually needs for the UI.

For data required on every page, such as user permissions or global settings, use the HandleInertiaRequests middleware. The share() method merges these global props into every single page response, making them accessible via the usePage() hook without manual controller injection.

Practical Application: Lazy Loading Components

A common real-world use case involves heavy dashboards. You can render the page shell immediately to give the user instant feedback, then use a Vue onMounted hook to trigger a partial reload for the data-intensive parts. This technique provides the fastest possible perceived performance while keeping your backend logic clean and organized.

4 min read