Navigation

Laravel

Laravel Inertia.js: Building Modern Monolith Applications

Build modern monolith apps with Laravel Inertia.js - combine server-side routing simplicity with SPA reactivity. No separate API needed. Learn setup, forms, layouts & performance tips for Vue/React frontends.

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?

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:

  1. Server-side adapters: Official adapters exist for Laravel, Rails, and other backend frameworks
  2. Client-side adapters: Available for Vue.js, React, and Svelte
  3. 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:

  1. Install the server-side adapter via Composer:
composer require inertiajs/inertia-laravel
  1. Install the client-side adapter (for Vue, React, or Svelte):
npm install @inertiajs/inertia @inertiajs/inertia-vue3
  1. 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>
  1. 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:

  1. Teams familiar with Laravel who want to add modern UI capabilities
  2. Projects that don't need a public API
  3. Applications that benefit from server-side routing
  4. Situations where you want to avoid duplication between API and frontend code
  5. When you need to build modular monoliths with clean architecture

However, it may not be the best choice if:

  1. You need a public API for multiple clients
  2. You want to completely decouple frontend and backend teams
  3. You need server-side rendering for SEO (though there are workarounds as discussed)
  4. 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:

  1. Official Documentation: Comprehensive and well-maintained
  2. Inertia.js GitHub Repository: For issues and updates
  3. Ping CRM: An example application built with Inertia.js
  4. 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.

Share this article

Add Comment

No comments yet. Be the first to comment!

More from Laravel