Inertia.js bridges the gap between traditional server-side rendering and modern single-page applications, allowing Laravel developers to build reactive UIs with the simplicity of server-side routing. This modern monolith approach offers the best of both worlds: the developer experience of SPAs with the practical benefits of server-rendered applications.
Table Of Contents
- What is a Modern Monolith?
- Enter Inertia.js
- The Inertia.js Ecosystem
- Core Concepts of Inertia.js
- Setting Up Inertia.js in Laravel
- Handling Forms and Mutations
- Shared Data and Persistent Layouts
- Advanced Inertia.js Patterns
- Testing Inertia.js Applications
- Performance Considerations
- Inertia.js vs Other Approaches
- Real-World Use Cases
- Common Pitfalls and How to Avoid Them
- When to Choose Inertia.js
- Community and Resources
- Conclusion
What is a Modern Monolith?
Before diving into Inertia.js, let's understand what we mean by a "modern monolith." Traditional monolithic applications bundle the frontend and backend together, with server-rendered views managing the UI. On the other end of the spectrum, we have decoupled architectures with separate frontend and backend codebases.
A modern monolith strikes a balance—maintaining a single codebase while leveraging modern frontend technologies. This approach provides several advantages:
- Simplified deployment (one application to deploy)
- Shared authentication context
- No need for complex API design
- Reduced complexity in development workflows
The term "monolith" often carries negative connotations in today's microservices-obsessed world. However, the reality is that many successful companies are returning to monolithic architectures after experiencing the operational complexity that microservices introduce. The modern monolith represents a pragmatic middle ground that addresses the legitimate concerns of traditional monoliths while maintaining their simplicity.
Enter Inertia.js
Inertia.js isn't a framework but rather a protocol that connects your Laravel backend with modern JavaScript frontends like Vue, React, or Svelte. It allows you to build single-page applications without the complexity of setting up a separate API.
Unlike other approaches that might require you to build a full REST API or GraphQL endpoint, Inertia leverages your existing Laravel routes and controllers. This means you don't need to duplicate your authorization logic or create separate API resources just to power your frontend.
The Inertia.js Ecosystem
Inertia.js consists of three main parts:
- Server-side adapters: Official adapters exist for Laravel, Rails, and other backend frameworks
- Client-side adapters: Available for Vue.js, React, and Svelte
- Inertia.js core: The protocol that connects the two sides
This architecture makes Inertia incredibly flexible. You can start with Vue.js and later switch to React without changing your backend code, or vice versa.
Core Concepts of Inertia.js
1. Server-Side Routing with Client-Side Rendering
With Inertia, you define routes in Laravel as you normally would. When a user navigates to a route, Inertia intercepts the request, fetches the data via AJAX, and updates the page without a full reload. This gives you:
- The simplicity of Laravel routing
- The responsiveness of a SPA
- Progressive enhancement by default
This is fundamentally different from how you'd approach building a traditional API-driven application where you'd need to maintain separate routing systems on both the frontend and backend.
2. Controllers Return Inertia Responses
Instead of returning views, your controllers return Inertia responses:
public function index()
{
return Inertia::render('Users/Index', [
'users' => User::all()
]);
}
This tells Inertia to render the Users/Index
component and pass the users
data as props.
Note that the controller is doing the same work it would normally do—fetching data and returning a response. The only difference is that instead of returning a Blade view, it returns an Inertia response.
3. Data Persists Between Requests
Inertia maintains page data between requests, preserving your application state. This means form inputs, scroll positions, and other UI states remain intact during navigation.
4. Partial Reloads
With traditional SPAs, you typically need to build comprehensive API endpoints to support partial data updates. Inertia simplifies this by allowing you to make requests to your existing controller endpoints and only refresh the data that has changed.
// Visit a page with only partial reload
Inertia.visit('/users', {
only: ['users'],
preserveState: true,
})
This is particularly useful for features like pagination, sorting, and filtering where you want to update just a portion of the page.
Setting Up Inertia.js in Laravel
Setting up Inertia with Laravel is straightforward:
- Install the server-side adapter via Composer:
composer require inertiajs/inertia-laravel
- Install the client-side adapter (for Vue, React, or Svelte):
npm install @inertiajs/inertia @inertiajs/inertia-vue3
- Create a root template that includes your assets and the Inertia app div:
<!DOCTYPE html>
<html>
<head>
<!-- Your meta tags, stylesheets, etc. -->
@vite('resources/js/app.js')
@inertiaHead
</head>
<body>
@inertia
</body>
</html>
- Set up your JavaScript entry point to boot the Inertia app:
import { createApp, h } from 'vue'
import { createInertiaApp } from '@inertiajs/inertia-vue3'
createInertiaApp({
resolve: name => import(`./Pages/${name}.vue`),
setup({ el, App, props, plugin }) {
createApp({ render: () => h(App, props) })
.use(plugin)
.mount(el)
},
})
This setup is simpler than configuring a full SPA with a separate API, yet it provides most of the same benefits. For a more comprehensive setup, you may want to look at starter kits like Laravel Breeze which includes Inertia scaffolding.
Handling Forms and Mutations
Inertia provides a simple API for form handling:
// In your Vue component
import { useForm } from '@inertiajs/inertia-vue3'
const form = useForm({
name: '',
email: '',
})
function submit() {
form.post('/users')
}
This handles submission, validation errors, loading states, and more out of the box.
What makes this approach powerful is that you're posting directly to your Laravel controllers—the same endpoints that handle traditional form submissions. Your validation rules, authorization logic, and business logic all live in one place, following Laravel's conventions.
Shared Data and Persistent Layouts
Two powerful features of Inertia are shared data and persistent layouts:
Shared Data
Share certain data (like auth status) with every page:
// In a middleware or service provider
Inertia::share('auth', function () {
return [
'user' => Auth::user() ? [
'id' => Auth::user()->id,
'name' => Auth::user()->name,
] : null,
];
});
This is particularly useful for data that needs to be available globally, such as the current user, notifications, or application settings.
Persistent Layouts
Maintain UI elements like navigation across page transitions:
<script setup>
import Layout from '@/Layouts/Authenticated.vue'
defineOptions({
layout: Layout,
})
</script>
Persistent layouts solve one of the common challenges of SPAs—maintaining consistent UI elements across page transitions without reloading them unnecessarily.
Advanced Inertia.js Patterns
1. Server-Side Rendering for SEO
One limitation of Inertia.js is that it doesn't provide server-side rendering out of the box, which can impact SEO. However, you can implement server-side rendering with Inertia using Laravel SSR:
// In your AppServiceProvider
public function boot()
{
Inertia::version(function () {
return md5_file(public_path('mix-manifest.json'));
});
SSR::enable();
}
This allows search engines to properly index your content, addressing one of the main disadvantages of client-side rendering.
2. Modal Dialogs
Inertia.js can handle modal dialogs elegantly through nested routes:
// routes/web.php
Route::get('/users', [UserController::class, 'index'])->name('users.index');
Route::get('/users/create', [UserController::class, 'create'])->name('users.create');
<!-- Users/Index.vue -->
<template>
<div>
<h1>Users</h1>
<Link href="/users/create">Add User</Link>
<!-- Display the users list -->
<!-- Modal for creating a user -->
<Modal :show="$page.component === 'Users/Create'">
<div v-if="$page.component === 'Users/Create'">
<UserForm />
</div>
</Modal>
</div>
</template>
This pattern allows you to have dedicated URLs for each state of your application, making it more navigable and SEO-friendly.
3. Lazy Loading Components
To improve initial load performance, you can lazy-load Inertia components:
createInertiaApp({
resolve: name => {
const pages = import.meta.glob('./Pages/**/*.vue', { eager: false })
return pages[`./Pages/${name}.vue`]()
},
// ...
})
This ensures that only the components needed for the current page are loaded, reducing the initial JavaScript bundle size.
Testing Inertia.js Applications
Testing is a crucial aspect of any application, and Inertia.js applications can be tested using Laravel's testing tools:
Feature Tests
public function test_can_view_users()
{
$this->get('/users')
->assertInertia(fn (Assert $assert) => $assert
->component('Users/Index')
->has('users', 3)
);
}
Laravel provides the assertInertia
method to test Inertia responses, making it easy to verify that the correct component is rendered with the expected props.
Component Tests
For testing the frontend components, you can use testing libraries specific to your chosen frontend framework (Jest for React, Vue Test Utils for Vue, etc.).
// Vue component test example
import { mount } from '@vue/test-utils'
import { createInertiaApp } from '@inertiajs/inertia-vue3'
import UsersList from '@/Pages/Users/Index.vue'
test('displays users', async () => {
const wrapper = mount(UsersList, {
props: {
users: [
{ id: 1, name: 'John' },
{ id: 2, name: 'Jane' }
]
}
})
expect(wrapper.findAll('li')).toHaveLength(2)
})
Performance Considerations
While Inertia.js provides an excellent developer experience, there are some performance considerations to keep in mind:
1. Data Transfer
Be mindful of the data you're sending from your controllers. Unlike a traditional API where you might have fine-grained endpoints, Inertia responses often include all the data needed for a page, which could lead to larger payload sizes.
Use Laravel's resource classes to transform and limit the data you send:
return Inertia::render('Users/Index', [
'users' => UserResource::collection(User::paginate(10))
]);
2. Server Performance
For high-traffic applications, consider using Laravel Octane to improve server response times. Octane keeps your application in memory between requests, significantly reducing response times.
composer require laravel/octane
php artisan octane:install
php artisan octane:start
This can make a substantial difference in the performance of Inertia applications, as it reduces the time needed to bootstrap the Laravel application for each request.
Inertia.js vs Other Approaches
Inertia.js vs Laravel Livewire
- Livewire: Server-driven approach, where UI updates are handled by the server. Minimal JavaScript required, but more server load.
- Inertia.js: Client-driven approach, where UI updates happen on the client. More JavaScript, but less server load.
Choose Livewire if you prefer working primarily with PHP and want to minimize JavaScript. Choose Inertia if you want the flexibility of modern JavaScript frameworks with the simplicity of Laravel routing.
Inertia.js vs Traditional SPAs
- Traditional SPAs: Require building and maintaining a separate API, duplicate routing, and authentication systems.
- Inertia.js: Unified codebase, shared routing and authentication, no need for a separate API.
Choose a traditional SPA if you need to support multiple clients (web, mobile, third-party) with the same API. Choose Inertia if you're building a web application and want to avoid the complexity of maintaining separate frontend and backend codebases.
Inertia.js vs Server-Side Rendering (SSR)
- SSR: Better initial load performance and SEO, but more complex setup and potentially slower subsequent navigation.
- Inertia.js: Simpler setup, better subsequent navigation performance, but potentially worse initial load and SEO (unless you use SSR with Inertia).
Choose SSR if initial load performance and SEO are critical. Choose Inertia for a simpler development experience with good subsequent navigation performance.
Real-World Use Cases
1. Admin Dashboards
Inertia.js is perfect for admin dashboards where SEO isn't a concern, but you want a responsive, modern UI. The ability to reuse Laravel's authentication and authorization systems makes it a great fit.
2. CRUD Applications
For applications that involve a lot of form handling and data manipulation, Inertia's form handling features make development much faster compared to building a traditional SPA with a separate API.
3. Internal Tools
For internal tools where development speed is prioritized over public-facing concerns like SEO, Inertia provides the perfect balance of modern UI capabilities and development simplicity.
Common Pitfalls and How to Avoid Them
1. Over-fetching Data
Problem: Sending too much data to the frontend, impacting performance. Solution: Use resources or transformers to limit the data you send, and implement pagination for large datasets.
2. Missing SEO
Problem: Inertia.js applications are client-rendered by default, which can impact SEO. Solution: Implement server-side rendering for public-facing pages, or consider using a hybrid approach with traditional server-rendered pages for critical SEO content.
3. Backend Coupling
Problem: Tightly coupling your frontend to your backend structure. Solution: Use view models or resources to transform your data before sending it to the frontend, creating a stable interface that can evolve independently of your backend models.
When to Choose Inertia.js
Inertia.js is ideal for:
- Teams familiar with Laravel who want to add modern UI capabilities
- Projects that don't need a public API
- Applications that benefit from server-side routing
- Situations where you want to avoid duplication between API and frontend code
- When you need to build modular monoliths with clean architecture
However, it may not be the best choice if:
- You need a public API for multiple clients
- You want to completely decouple frontend and backend teams
- You need server-side rendering for SEO (though there are workarounds as discussed)
- Your application requires extensive real-time features (although Laravel Broadcasting can be used with Inertia)
Community and Resources
The Inertia.js community has grown significantly since its introduction. Here are some valuable resources:
- Official Documentation: Comprehensive and well-maintained
- Inertia.js GitHub Repository: For issues and updates
- Ping CRM: An example application built with Inertia.js
- Laravel Breeze: Official Laravel starter kit with Inertia.js support
Conclusion
Laravel Inertia.js represents a pragmatic approach to building modern web applications. It preserves the simplicity and productivity of Laravel while enabling the rich, reactive UI experiences users expect from modern applications.
By removing the complexity of API design and dual authentication systems, Inertia.js lets developers focus on building features rather than infrastructure. As the web continues to evolve, the modern monolith approach may be the sweet spot many teams are looking for—powerful enough for complex applications but simple enough for rapid development.
In an industry that often swings between extremes—from monoliths to microservices, from server-rendered to fully client-rendered—Inertia.js offers a balanced middle ground that acknowledges the strengths of both approaches. It's a testament to the idea that sometimes the best solution isn't about choosing one paradigm over another, but finding the right combination that addresses your specific needs.
If you're interested in implementing clean architecture patterns alongside Inertia.js, check out our guide on the Laravel Repository Pattern for a comprehensive approach to structuring your application logic.
Add Comment
No comments yet. Be the first to comment!