Please wait while the page loads.
Skip to main content
Fullstack

Fullstack Laravel & React Inertia.js Integration

Lucas Vance
Aug 03, 2026
11 min read

Introduction: The Power of the Modern Monolith

For years, web development teams faced a difficult architectural trade-off. You either built a traditional Server-Side Rendered (SSR) multi-page application with Blade or Twig (which offered effortless server routing, authentication, and database access, but lacked fluid client-side page transitions), or you built a Decoupled Client-Side SPA with React and a REST/GraphQL API.

The decoupled SPA model introduced immense complexity: writing dozens of API endpoints, managing client-side OAuth token refresh cycles, duplicating form validation schemas across frontend and backend, and wrestling with CORS policies.

Inertia.js completely bridges this divide. Acting as a lightweight adapter between Laravel and React, Inertia allows you to write standard Laravel controllers and routes while rendering client-side React components without ever writing a REST API.

In this deep-dive guide, we explore how to configure, structure, and scale a production-ready Laravel + React application using Inertia.js.


1. How Inertia.js Works Under the Hood

Inertia is neither a backend framework nor a frontend UI library. Instead, it replaces your traditional Blade template rendering with a dynamic JSON protocol:

Initial Visit: Browser ➔ Laravel Controller ➔ Inertia::render() ➔ Sends Full HTML Shell with Pre-Hydrated Props
Subsequent Clicks: React Link ➔ Inertia XHR Request ➔ Laravel Controller ➔ Sends Tiny JSON Payload ➔ React Re-Renders Page View (Zero Full Refresh)
  1. Initial Page Load: On the first request to https://yourapp.com/dashboard, Laravel returns a minimal HTML document shell containing your compiled React bundle and a JSON payload of props in a data-page attribute.
  2. Subsequent Navigation: When a user clicks an <InertiaLink href="/quiz/play">, Inertia intercepts the click, issues a lightweight AJAX request with an X-Inertia: true HTTP header, receives only the updated JSON props from the controller, and swaps the active React component in memory without a browser refresh.

2. Setting Up Laravel, React, and Vite with Inertia

To configure Inertia in a modern Laravel 11/12 project:

Step 1: Install Server & Client Dependencies

# Server-side adapter
composer require inertiajs/inertia-laravel

# Client-side React adapter & Vite plugins
npm install @inertiajs/react react react-dom @vitejs/plugin-react

Step 2: Configure the Root Blade Template (app.blade.php)

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="utf-8">
    <meta name="viewport" content="width=device-width, initial-scale=1">
    <title inertia>{{ config('app.name', 'QuizRush') }}</title>
    @viteReactRefresh
    @vite(['resources/js/app.jsx', "resources/js/Pages/{$page['component']}.jsx"])
    @inertiaHead
</head>
<body class="font-sans antialiased bg-slate-950 text-white">
    @inertia
</body>
</html>

Step 3: Initialize the React Inertia App (resources/js/app.jsx)

import { createRoot } from 'react-dom/client';
import { createInertiaApp } from '@inertiajs/react';
import { resolvePageComponent } from 'laravel-vite-plugin/inertia-helpers';

createInertiaApp({
  title: (title) => `${title} | QuizRush`,
  resolve: (name) =>
    resolvePageComponent(
      `./Pages/${name}.jsx`,
      import.meta.glob('./Pages/**/*.jsx')
    ),
  setup({ el, App, props }) {
    const root = createRoot(el);
    root.render(<App {...props} />);
  },
  progress: {
    color: '#ffc93c',
    showSpinner: true,
  },
});

3. Writing Laravel Controllers for React Pages

With Inertia, your Laravel controllers look identical to standard Blade controllers, returning Inertia::render() with direct arrays of data:

namespace App\Http\Controllers;

use App\Models\Question;
use App\Models\Leaderboard;
use Illuminate\Http\Request;
use Inertia\Inertia;
use Inertia\Response;

class QuizController extends Controller
{
    public function index(): Response
    {
        return Inertia::render('Quiz/Arena', [
            'questions' => Question::query()
                ->where('is_active', true)
                ->inRandomOrder()
                ->take(5)
                ->get(['id', 'prompt', 'category', 'difficulty', 'time_limit']),
            'userStreak' => auth()->user()?->active_streak ?? 0,
        ]);
    }

    public function submitScore(Request $request)
    {
        $validated = $request->validate([
            'score' => 'required|integer|min:0|max:225',
            'answers' => 'required|array|size:5',
        ]);

        auth()->user()->scores()->create($validated);

        return redirect()->route('leaderboard')->with('success', 'Round score saved successfully!');
    }
}

4. Building the React Page Component with useForm

In your React component (resources/js/Pages/Quiz/Arena.jsx), consuming props and handling form submissions requires zero manual fetch() or axios boilerplate. You use Inertia's built-in useForm hook:

import React, { useState } from 'react';
import { Head, useForm, Link } from '@inertiajs/react';

export default function Arena({ questions, userStreak }) {
  const [currentIdx, setCurrentIdx] = useState(0);
  const [userScore, setUserScore] = useState(0);

  const { data, setData, post, processing, errors } = useForm({
    score: 0,
    answers: [],
  });

  const handleAnswer = (optionId, isCorrect, points) => {
    const nextScore = isCorrect ? userScore + points : userScore;
    setUserScore(nextScore);
    setData('answers', [...data.answers, optionId]);

    if (currentIdx + 1 < questions.length) {
      setCurrentIdx(currentIdx + 1);
    } else {
      // Last question finished: submit payload to Laravel
      data.score = nextScore;
      post('/quiz/submit');
    }
  };

  const currentQ = questions[currentIdx];

  return (
    <div className="min-h-screen bg-slate-950 text-slate-100 p-8">
      <Head title="Live Quiz Arena" />
      
      <div className="max-w-2xl mx-auto bg-slate-900 border border-slate-800 rounded-2xl p-6 shadow-2xl">
        <div className="flex justify-between items-center mb-6">
          <span className="text-yellow-400 font-bold">Streak: {userStreak} 🔥</span>
          <span className="text-slate-400">Question {currentIdx + 1} of {questions.length}</span>
        </div>

        <h2 className="text-2xl font-bold mb-6">{currentQ.prompt}</h2>

        {processing && <p className="text-yellow-400">Saving round scores...</p>}
        {errors.score && <p className="text-red-400">{errors.score}</p>}
      </div>
    </div>
  );
}

5. Sharing Global Props via HandleInertiaRequests

To share global data across every React component (such as authenticated user data, flash messages, or CSRF tokens), configure Laravel's HandleInertiaRequests middleware:

namespace App\Http\Middleware;

use Illuminate\Http\Request;
use Inertia\Middleware;

class HandleInertiaRequests extends Middleware
{
    public function share(Request $request): array
    {
        return [
            ...parent::share($request),
            'auth' => [
                'user' => $request->user() ? [
                    'id' => $request->user()->id,
                    'name' => $request->user()->name,
                    'tier_badge' => $request->user()->tier_badge,
                    'lifetime_points' => $request->user()->lifetime_points,
                ] : null,
            ],
            'flash' => [
                'success' => fn () => $request->session()->get('success'),
                'error' => fn () => $request->session()->get('error'),
            ],
        ];
    }
}

Inertia.js Architectural Benefits

  • Zero API Boilerplate: Controllers pass server data directly as React component props.
  • Single Source of Truth: Validation rules, authorization policies, and route guards stay in Laravel.
  • Rich SPA Transitions: Client-side route changes occur with zero browser page reloads.
  • Integrated Form Handling: The useForm hook handles submission states, automatic error binding, and file uploads.

Frequently Asked Questions (FAQ)

Is Inertia.js good for SEO?

For public content like landing pages, blogs, and public leaderboards, Inertia supports Server-Side Rendering (SSR) via Node.js integration, allowing search engine bots to crawl pre-rendered HTML identical to a traditional website.

How does authentication work in an Inertia app?

Authentication uses standard Laravel session cookies (web middleware). You do not need to manage JWT tokens, localStorage tokens, or OAuth refresh flows in JavaScript.

Can I use Ziggy for named Laravel routes in React?

Yes! Installing the tightenco/ziggy package exposes Laravel's route('blog.details', { slug: '...' }) helper directly in your React JSX templates.

Does Inertia add significant bundle size overhead?

No. The core @inertiajs/react package is under 12KB gzipped, making it substantially lighter than full frontend routing libraries and GraphQL client frameworks.
Lucas Vance

Fullstack Architect — Passionate about trivia strategy, speed mechanics, and competitive player rankings.

Keep Reading

Related Guides & Tips

Platform 10 min read

Understanding Monthly Leaderboard & Level Unlocks

A comprehensive breakdown of how monthly points reset, how the dual-ledger leaderboard functions, rank multipliers, and how to unlock exclusive level badges.