Try Live
Add Docs
Rankings
Pricing
Docs
Install
Install
Docs
Pricing
More...
More...
Try Live
Rankings
Enterprise
Create API Key
Add Docs
Inertia.js Laravel Adapter
https://github.com/inertiajs/inertia-laravel
Admin
The Inertia.js Laravel Adapter enables seamless integration of Inertia.js with the Laravel PHP
...
Tokens:
5,565
Snippets:
36
Trust Score:
7.3
Update:
2 weeks ago
Context
Skills
Chat
Benchmark
76.2
Suggestions
Latest
Show doc for...
Code
Info
Show Results
Context Summary (auto-generated)
Raw
Copy
Link
# Inertia.js Laravel Adapter Inertia.js is a modern approach to building single-page applications (SPAs) without the complexity of a separate API. The Laravel adapter allows you to create fully client-side rendered, single-page apps using classic server-side routing and controllers. Instead of returning JSON responses, Inertia enables Laravel controllers to return JavaScript page components (Vue, React, or Svelte) that receive data as props, eliminating the need for a separate API layer while maintaining the benefits of both server-side and client-side development. This adapter provides the server-side implementation for Laravel applications, handling the protocol between Laravel backend and JavaScript frontend frameworks. It manages page rendering, shared data, partial reloads, deferred props, history encryption, server-side rendering (SSR), and seamless navigation between pages. The library integrates deeply with Laravel's routing, middleware, validation, and testing systems. ## Rendering Inertia Pages The `Inertia::render()` method creates responses that render JavaScript components with data passed as props. This is the primary method for returning Inertia responses from controllers. ```php use Inertia\Inertia; // Basic rendering with props class UserController extends Controller { public function index() { return Inertia::render('Users/Index', [ 'users' => User::all(), 'filters' => request()->only(['search', 'status']), ]); } public function show(User $user) { return Inertia::render('Users/Show', [ 'user' => $user->load('posts', 'comments'), 'canEdit' => auth()->user()->can('update', $user), ]); } } // Using the helper function public function create() { return inertia('Users/Create', [ 'roles' => Role::all(), ]); } // Adding props fluently with the with() method public function edit(User $user) { return Inertia::render('Users/Edit') ->with('user', $user) ->with(['roles' => Role::all(), 'departments' => Department::all()]); } ``` ## Sharing Global Data The `Inertia::share()` method makes data available to all Inertia responses. Commonly used for authentication state, flash messages, and application-wide settings. ```php // In app/Http/Middleware/HandleInertiaRequests.php 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, 'email' => $request->user()->email, ] : null, ], 'flash' => [ 'success' => session('success'), 'error' => session('error'), ], 'appName' => config('app.name'), ]; } } // Share data dynamically in a service provider public function boot() { Inertia::share('appVersion', fn () => config('app.version')); Inertia::share([ 'locale' => fn () => app()->getLocale(), 'timezone' => config('app.timezone'), ]); } // Retrieve shared data $allShared = Inertia::getShared(); $authData = Inertia::getShared('auth'); $defaultValue = Inertia::getShared('missing.key', 'default'); ``` ## Deferred Props Deferred props are loaded asynchronously after the initial page render, improving perceived performance by showing the page immediately while slower data loads in the background. ```php use Inertia\Inertia; class DashboardController extends Controller { public function index() { return Inertia::render('Dashboard', [ // Fast data loads immediately 'user' => auth()->user(), 'notifications' => auth()->user()->unreadNotifications->take(5), // Slow data loads after page render (default group) 'analytics' => Inertia::defer(fn () => AnalyticsService::getStats()), // Group related deferred props to load together 'recentOrders' => Inertia::defer( fn () => Order::recent()->with('items')->get(), 'activity' ), 'recentComments' => Inertia::defer( fn () => Comment::recent()->with('user')->get(), 'activity' ), // Heavy report loads in its own group 'salesReport' => Inertia::defer( fn () => ReportService::generateSalesReport(), 'reports' ), ]); } } ``` ## Optional Props Optional props are only evaluated when explicitly requested via partial reloads, useful for expensive data that isn't always needed. ```php use Inertia\Inertia; class ProductController extends Controller { public function show(Product $product) { return Inertia::render('Products/Show', [ 'product' => $product, 'reviews' => $product->reviews()->paginate(10), // Only loaded when explicitly requested 'relatedProducts' => Inertia::optional( fn () => ProductService::findRelated($product, limit: 12) ), 'priceHistory' => Inertia::optional( fn () => $product->priceHistory()->orderByDesc('date')->get() ), 'inventoryLevels' => Inertia::optional( fn () => WarehouseService::getInventory($product) ), ]); } } // Frontend can request optional props via partial reload: // router.reload({ only: ['relatedProducts', 'priceHistory'] }) ``` ## Always Props Always props are included in every response, even during partial reloads. Essential for data that must always be current like validation errors or CSRF tokens. ```php use Inertia\Inertia; class HandleInertiaRequests extends Middleware { public function share(Request $request): array { return [ // Always included even in partial reloads 'errors' => Inertia::always(fn () => $this->resolveValidationErrors($request)), 'csrf_token' => Inertia::always(fn () => csrf_token()), // Regular shared props (excluded during partial reloads unless requested) 'auth' => fn () => [ 'user' => $request->user(), ], ]; } } // In controller - always include current permissions public function index() { return Inertia::render('Posts/Index', [ 'posts' => Post::paginate(), 'permissions' => Inertia::always(fn () => auth()->user()->permissions), ]); } ``` ## Merge Props Merge props combine new data with existing client-side state during partial reloads, enabling infinite scroll and pagination without replacing the entire dataset. ```php use Inertia\Inertia; class PostController extends Controller { public function index() { $posts = Post::query() ->orderByDesc('created_at') ->paginate(15); return Inertia::render('Posts/Index', [ // Append new pages to existing posts array 'posts' => Inertia::merge($posts), ]); } } // Deep merge for nested objects public function dashboard() { return Inertia::render('Dashboard', [ // Deep merge preserves nested structure 'settings' => Inertia::deepMerge([ 'notifications' => NotificationSettings::forUser(auth()->user()), 'privacy' => PrivacySettings::forUser(auth()->user()), ]), ]); } ``` ## Scroll Props for Infinite Scrolling Scroll props provide specialized merge behavior with pagination metadata for implementing infinite scroll functionality. ```php use Inertia\Inertia; class FeedController extends Controller { public function index() { $posts = Post::query() ->with('author', 'comments') ->orderByDesc('created_at') ->cursorPaginate(20); return Inertia::render('Feed/Index', [ // Scroll prop with automatic pagination metadata 'posts' => Inertia::scroll($posts) ->configureMergeIntent() // Reads merge direction from request header ->append('data'), // Append new items to 'data' wrapper ]); } } // Custom scroll metadata public function timeline() { $events = Event::paginate(25); return Inertia::render('Timeline', [ 'events' => Inertia::scroll( $events, wrapper: 'items', metadata: fn ($paginator) => new class($paginator) implements ProvidesScrollMetadata { public function __construct(private $paginator) {} public function getPageName(): string { return 'page'; } public function getCurrentPage(): int { return $this->paginator->currentPage(); } public function getNextPage(): ?int { return $this->paginator->hasMorePages() ? $this->paginator->currentPage() + 1 : null; } public function getPreviousPage(): ?int { return $this->paginator->currentPage() > 1 ? $this->paginator->currentPage() - 1 : null; } } ), ]); } ``` ## Once Props Once props are evaluated only on the first request and remembered by the client across subsequent navigations, useful for expensive data that rarely changes. ```php use Inertia\Inertia; class HandleInertiaRequests extends Middleware { public function shareOnce(Request $request): array { return [ // Loaded once and remembered across navigations 'permissions' => fn () => PermissionService::forUser($request->user()), 'featureFlags' => fn () => FeatureFlag::all(), ]; } } // In controller public function index() { return Inertia::render('Settings', [ 'settings' => UserSettings::forUser(auth()->user()), // Expensive lookup done only once 'availableTimezones' => Inertia::once(fn () => timezone_identifiers_list()), 'countries' => Inertia::once(fn () => Country::with('states')->get()), ]); } ``` ## Flash Data Flash data is included with the response but not persisted in browser history state, ideal for toast notifications and one-time messages. ```php use Inertia\Inertia; class OrderController extends Controller { public function store(Request $request) { $order = Order::create($request->validated()); // Flash data for one-time display Inertia::flash('toast', [ 'type' => 'success', 'message' => 'Order #' . $order->id . ' created successfully!', ]); return redirect()->route('orders.show', $order); } public function destroy(Order $order) { $order->delete(); // Multiple flash values Inertia::flash('notification', 'Order deleted') ->flash('highlight', 'orders-table'); return redirect()->route('orders.index'); } } // Flash directly on response public function update(Request $request, Post $post) { $post->update($request->validated()); return Inertia::render('Posts/Show', ['post' => $post]) ->flash('saved', true) ->flash('message', 'Post updated successfully'); } ``` ## External Redirects The `Inertia::location()` method triggers a full page visit to external URLs or forces a server-side redirect when needed. ```php use Inertia\Inertia; class PaymentController extends Controller { public function checkout() { $checkoutUrl = PaymentGateway::createSession([ 'amount' => Cart::total(), 'success_url' => route('payment.success'), 'cancel_url' => route('payment.cancel'), ]); // Full page redirect to external payment gateway return Inertia::location($checkoutUrl); } public function oauth(string $provider) { // Redirect to OAuth provider return Inertia::location( Socialite::driver($provider)->redirect() ); } } // Using the helper function public function externalLink() { return inertia_location('https://external-service.com/callback'); } ``` ## History Encryption History encryption prevents sensitive data from being accessible in the browser's history state after logout, enhancing security for sensitive applications. ```php // config/inertia.php return [ 'history' => [ 'encrypt' => env('INERTIA_ENCRYPT_HISTORY', true), ], ]; // Enable per-request in middleware class HandleInertiaRequests extends Middleware { public function rootView(Request $request): string { // Encrypt history for authenticated users if ($request->user()) { Inertia::encryptHistory(); } return 'app'; } } // Enable via route middleware Route::middleware(['auth', 'inertia.encrypt'])->group(function () { Route::get('/account', [AccountController::class, 'index']); Route::get('/billing', [BillingController::class, 'index']); }); // Clear history on logout public function logout(Request $request) { Auth::logout(); Inertia::clearHistory(); return redirect()->route('login'); } ``` ## Route Macros The `Route::inertia()` macro provides a shorthand for simple pages that don't need controller logic. ```php use Illuminate\Support\Facades\Route; // Simple static pages Route::inertia('/', 'Home'); Route::inertia('/about', 'About'); Route::inertia('/contact', 'Contact'); // With props Route::inertia('/pricing', 'Pricing', [ 'plans' => fn () => Plan::active()->get(), ]); // With middleware Route::middleware(['auth'])->group(function () { Route::inertia('/dashboard', 'Dashboard', [ 'stats' => fn () => DashboardStats::forUser(auth()->user()), ]); }); // Named routes Route::inertia('/terms', 'Legal/Terms')->name('terms'); Route::inertia('/privacy', 'Legal/Privacy')->name('privacy'); ``` ## Custom Middleware Create custom Inertia middleware to customize shared data, versioning, and root view behavior. ```php // Generate middleware: php artisan inertia:middleware namespace App\Http\Middleware; use Illuminate\Http\Request; use Inertia\Middleware; class HandleInertiaRequests extends Middleware { protected $rootView = 'app'; public function version(Request $request): ?string { // Custom versioning based on deployment return config('app.version') . '-' . config('app.asset_version'); } public function share(Request $request): array { return [ ...parent::share($request), 'auth' => [ 'user' => $request->user()?->only('id', 'name', 'email', 'avatar'), 'permissions' => $request->user()?->permissions ?? [], ], 'flash' => [ 'success' => session('success'), 'error' => session('error'), 'warning' => session('warning'), ], 'app' => [ 'name' => config('app.name'), 'locale' => app()->getLocale(), 'supportedLocales' => config('app.supported_locales'), ], ]; } public function rootView(Request $request): string { // Different layout for admin routes if ($request->routeIs('admin.*')) { return 'admin'; } return $this->rootView; } } // Register in bootstrap/app.php (Laravel 11+) or Kernel.php ->withMiddleware(function (Middleware $middleware) { $middleware->web(append: [ \App\Http\Middleware\HandleInertiaRequests::class, ]); }) ``` ## Testing Inertia Responses The testing utilities provide fluent assertions for verifying Inertia responses in feature tests. ```php use Inertia\Testing\AssertableInertia; class UserControllerTest extends TestCase { public function test_users_index_displays_users() { $users = User::factory()->count(3)->create(); $response = $this->actingAs($users->first()) ->get('/users'); $response->assertInertia(fn (AssertableInertia $page) => $page ->component('Users/Index') ->has('users', 3) ->has('users.0', fn ($user) => $user ->has('id') ->has('name') ->has('email') ->missing('password') ) ); } public function test_user_show_page() { $user = User::factory()->create(['name' => 'John Doe']); $this->get("/users/{$user->id}") ->assertInertia(fn (AssertableInertia $page) => $page ->component('Users/Show') ->where('user.name', 'John Doe') ->where('user.id', $user->id) ); } public function test_partial_reload() { $user = User::factory()->create(); $this->get('/dashboard') ->assertInertia(fn (AssertableInertia $page) => $page ->component('Dashboard') ->has('stats') // Test deferred props loading ->loadDeferredProps('activity', fn ($page) => $page ->has('recentOrders') ->has('recentComments') ) // Test partial reload ->reloadOnly('notifications', fn ($page) => $page ->has('notifications') ->missing('stats') ) ); } public function test_flash_data() { $this->post('/orders', ['product_id' => 1]) ->assertRedirect('/orders/1') ->assertInertiaFlash('toast.type', 'success') ->assertInertiaFlash('toast.message'); } public function test_get_props_directly() { $response = $this->get('/users'); $props = $response->inertiaProps(); $users = $response->inertiaProps('users'); $page = $response->inertiaPage(); $this->assertCount(10, $users); $this->assertEquals('Users/Index', $page['component']); } } ``` ## Server-Side Rendering Configuration Configure server-side rendering for improved SEO and initial page load performance. ```php // config/inertia.php return [ 'ssr' => [ 'enabled' => env('INERTIA_SSR_ENABLED', true), 'url' => env('INERTIA_SSR_URL', 'http://127.0.0.1:13714'), 'ensure_bundle_exists' => true, 'throw_on_error' => env('INERTIA_SSR_THROW_ON_ERROR', false), ], ]; // Exclude paths from SSR (e.g., heavy dashboards) class HandleInertiaRequests extends Middleware { protected $withoutSsr = [ '/admin/*', '/dashboard/analytics', ]; } // Or dynamically exclude paths Inertia::withoutSsr(['/reports/*', '/exports/*']); ``` ```bash # Start SSR server php artisan inertia:start-ssr # Stop SSR server php artisan inertia:stop-ssr # Check SSR server health php artisan inertia:check-ssr ``` ## Blade Directives The `@inertia` and `@inertiaHead` directives render the Inertia root element and SSR head content in your Blade templates. ```blade {{-- resources/views/app.blade.php --}} <!DOCTYPE html> <html lang="{{ str_replace('_', '-', app()->getLocale()) }}"> <head> <meta charset="utf-8"> <meta name="viewport" content="width=device-width, initial-scale=1"> <title>{{ config('app.name') }}</title> {{-- SSR head content (title, meta tags, etc.) --}} @inertiaHead @vite(['resources/js/app.js', 'resources/css/app.css']) </head> <body> {{-- Inertia root element with page data --}} @inertia {{-- Custom root element ID --}} {{-- @inertia('my-app') --}} </body> </html> ``` ## Exception Handling Register a custom exception handler for Inertia requests to render error pages as Inertia components. ```php // In app/Providers/AppServiceProvider.php use Inertia\Inertia; use Inertia\ExceptionResponse; public function boot() { Inertia::handleExceptionsUsing(function (ExceptionResponse $response) { $status = $response->response->status(); // Render error pages as Inertia components if (in_array($status, [403, 404, 500, 503])) { return $response->render("Errors/{$status}", [ 'status' => $status, 'message' => $response->exception->getMessage(), ]); } // Return original response for other errors return $response; }); } // With custom logic per error type Inertia::handleExceptionsUsing(function (ExceptionResponse $response) { if ($response->exception instanceof AuthorizationException) { return $response->render('Errors/Forbidden', [ 'message' => 'You do not have permission to access this resource.', ]); } if ($response->exception instanceof ModelNotFoundException) { return $response->render('Errors/NotFound', [ 'model' => class_basename($response->exception->getModel()), ]); } return $response; }); ``` The Inertia.js Laravel Adapter is ideally suited for building modern single-page applications that leverage Laravel's powerful backend features. Common use cases include admin dashboards, e-commerce platforms, SaaS applications, and content management systems where the developer experience of a traditional server-rendered app is preferred over maintaining a separate API. The adapter excels in projects where teams want the interactivity of Vue, React, or Svelte without the overhead of building and maintaining a REST or GraphQL API. Integration patterns typically involve creating a custom middleware that extends `Inertia\Middleware` for sharing authentication state, flash messages, and application configuration. Controllers return Inertia responses instead of views, passing data directly to frontend components as props. For performance optimization, use deferred props for slow-loading data, optional props for rarely-needed information, and merge/scroll props for infinite scrolling. The testing utilities integrate seamlessly with Laravel's testing framework, enabling comprehensive feature tests that verify both the component being rendered and the props being passed.