Inertia lets you create interfaces with React using Laravel routes and controllers, without maintaining a separate API.
Welcome to modern web development. So far, we have used Blade to render HTML. Blade is fantastic, but sometimes you need that instant “App-like” interactivity (without page reloads) offered by frameworks like React, Vue, or Svelte.
The traditional way to do this was to split your project into two:
- Backend API (Laravel): Only sends JSON.
- Frontend SPA (React): A separate application that consumes the JSON.
Splitting both projects is still valid, but Inertia.js offers another option: build an SPA while keeping Laravel routes and controllers, without creating an API for every screen.
In this final article, we will create a project that uses Laravel as the engine and React as the bodywork.
The architecture: what is Inertia.js?
Inertia is the “glue” between Laravel and React. Think about how you worked with Blade:
- Blade: The controller returns
view('profile', ['user' => $user]). - Inertia: The controller returns
Inertia::render('Profile', ['user' => $user]).
The pattern is very similar. On the first visit, Inertia delivers the initial document; on subsequent navigations, it exchanges the page data and React updates the component without a full document reload.
Requirements
For this exercise, besides PHP and Composer, you need Node.js installed on your computer (since React is compiled with JavaScript).
Check that the tools respond from the terminal with php -v, composer --version, and node -v. Use an LTS version of Node compatible with the project dependencies.
Create a project with the React starter kit
Install the official CLI and create the project:
composer global require laravel/installer
laravel new mi-app-reactWhen the installer asks for the starter kit, select React. The current project includes Inertia, React, TypeScript, Tailwind, and Vite.
Then install the resources and start the environment:
cd mi-app-react
npm install
npm run build
composer run devGo to http://localhost:8000. composer run dev coordinates the server and the development process for the resources.
Pass data from PHP to React
Let’s see how data is passed from the server to the client. We will modify the home page.
The route and controller (PHP)
Open routes/web.php. You will see something new:
use Illuminate\Support\Facades\Route;
use Inertia\Inertia; // <--- Important
Route::get('/', function () {
// Instead of view(), we use Inertia::render()
// 1st parameter: file name in resources/js/pages
// 2nd parameter: Data (Props) for React
return Inertia::render('demo', [
'phpVersion' => PHP_VERSION,
'nombreUsuario' => 'Luis',
]);
});The React component
Page components are located in resources/js/pages. Create resources/js/pages/demo.tsx and use the same name in Inertia::render('demo', ...).
If you know a bit of React, this will feel familiar. If not, pay attention to how we receive the data.
import { Head } from '@inertiajs/react';
type DemoProps = {
phpVersion: string;
nombreUsuario: string;
};
export default function Demo({ phpVersion, nombreUsuario }: DemoProps) {
return (
<>
<Head title="Welcome" />
<div className="flex justify-center items-center h-screen bg-gray-100">
<div className="text-center p-10 bg-white shadow-xl rounded-lg">
<h1 className="text-4xl font-bold text-blue-600 mb-4">
Hello, {nombreUsuario}!
</h1>
<p className="text-gray-700 text-lg">
Your Laravel is running with PHP v{phpVersion}
</p>
<button className="mt-6 px-4 py-2 bg-black text-white rounded hover:bg-gray-800 transition">
Interactive React Button
</button>
</div>
</div>
</>
);
}When you save the file, Vite will update the browser while composer run dev is still running.
Create a counter with React state
To demonstrate that this is a real interactive app, we will add a React counter that does not need to call the server to work.
Edit demo.tsx:
- Import the
useStatehook from React at the top:import { useState } from 'react'; - Inside the function, before the
return:
const [counter, setCounter] = useState(0);- Modify the button in the HTML (JSX):
<button
onClick={() => setCounter(counter + 1)}
className="mt-6 px-4 py-2 bg-blue-500 text-white rounded hover:bg-blue-600"
>
You have clicked {counter} times
</button>Test it. The counter goes up instantly. It is pure JavaScript running in the browser, but served from your Laravel project.
What if I prefer Vue.js?
The beauty of this system is that the Backend (Laravel) does not change. If you prefer Vue:
- When running
laravel new, choose Vue instead of React. - Your routes still use
Inertia::render(). - Your page components will be
.vueinstead of.tsx.
Example in Vue (Welcome.vue):
<script setup>
defineProps({ nombreUsuario: String });
</script>
<template>
<h1>Hello, {{ nombreUsuario }}</h1>
</template>Laravel also offers an official starter kit for Svelte, maintaining the same way of working with routes and controllers.