tinymvc / inertia-php
A simple and lightweight PHP library for building Inertia.js applications.
Installs: 8
Dependents: 0
Suggesters: 0
Security: 0
Stars: 1
Watchers: 0
Forks: 0
Open Issues: 0
pkg:composer/tinymvc/inertia-php
README
A server-side adapter for Inertia.js v2, enabling you to build modern single-page applications using classic server-side routing and controllers.
Inertia eliminates the complexity of building SPAs. There's no need for client-side routing, API endpoints, or data fetching logic. Simply build controllers and page views like you've always done.
Installation
composer require tinymvc/inertia-php
Setup
Register the service provider in your application:
// bootstrap/providers.php return [ \Inertia\InertiaServiceProvider::class, ];
The service provider automatically registers:
- The
Inertiasingleton - The
@inertiaBlade directive - The
Route::inertia()router macro
Basic Usage
There are multiple ways to create Inertia responses:
Using the Inertia Class
use Inertia\Facades\Inertia; class UsersController { public function index() { return Inertia::render('Users/Index', [ 'users' => User::all(), ]); } }
Using the Helper Function
The inertia() helper provides a convenient shorthand:
class UsersController { public function index() { // Render a component with props return inertia('Users/Index', [ 'users' => User::all(), ]); } public function show(User $user) { // Access Inertia instance without rendering $inertia = inertia(); $inertia->setRootView('layouts.admin'); return $inertia->render('Users/Show', [ 'user' => $user, ]); } }
Using Route Macro
For simple pages that don't require a controller, use the Route::inertia() macro:
// routes/web.php use Spark\Facades\Route; Route::inertia('/', 'Home'); Route::inertia('/about', 'About', ['version' => '1.0.0']); Route::inertia('/contact', 'Contact');
This is equivalent to:
Route::get('/', fn() => inertia('Home'));
Root Template
Create a root Blade template that will load your JavaScript application:
<!-- resources/views/app.blade.php --> <!DOCTYPE html> <html> <head> <meta charset="utf-8"> <meta name="viewport" content="width=device-width, initial-scale=1"> <title>My App</title> @vite('app.tsx') </head> <body> @inertia </body> </html>
The @inertia Blade directive renders a <div> element with a data-page attribute containing the page data:
<div id="app" data-page="{"component":"Home","props":{...}}"></div>
Custom Root View
By default, Inertia uses the app view. To change it:
Inertia::setRootView('layouts.admin');
Sharing Data
Share data globally across all Inertia responses. This is useful for data like the authenticated user, flash messages, or application settings.
// In a middleware or service provider use Inertia\Facades\Inertia; // Share a single value Inertia::share('appName', config('app.name')); // Share multiple values Inertia::share([ 'auth' => [ 'user' => auth()->user(), ], 'flash' => [ 'success' => session('success'), 'error' => session('error'), ], 'locale' => app()->getLocale(), ]); // Retrieve shared data $appName = Inertia::getShared('appName'); $allShared = Inertia::getShared(); // Returns all shared data
Props
Props are the data passed to your page components. Inertia supports various prop types for different use cases.
Standard Props
Regular props are always included in responses:
return inertia('Users/Index', [ 'users' => User::all(), 'filters' => $request->only(['search', 'status']), ]);
Lazy Evaluation with Closures
Wrap props in closures for lazy evaluation. The closure is only executed when the prop is actually needed:
return inertia('Dashboard', [ // Always evaluated 'user' => $user, // Only evaluated when included in response 'users' => fn () => User::all(), 'companies' => fn () => Company::all(), ]);
Optional Props
Optional props are never included in the initial response. They must be explicitly requested via partial reloads:
return inertia('Reports', [ 'summary' => $summary, // Only loaded when explicitly requested 'detailedReport' => Inertia::optional(fn () => Report::generateDetailed()), ]);
Client-side request:
router.reload({ only: ['detailedReport'] })
Always Props
Always props are included in every response, even during partial reloads when not explicitly requested:
return inertia('Dashboard', [ 'users' => fn () => User::all(), // Always fresh, even in partial reloads 'notifications' => Inertia::always(fn () => auth()->user()->unreadNotifications), 'flash' => Inertia::always(fn () => session('flash')), ]);
Deferred Props
Deferred props are excluded from the initial page load and loaded in a subsequent request after the page renders. This improves perceived performance for pages with expensive data.
return inertia('Dashboard', [ // Loaded immediately 'user' => $user, // Loaded after page renders 'stats' => Inertia::defer(fn () => Stats::calculate()), 'recentActivity' => Inertia::defer(fn () => Activity::recent()), ]);
Grouping Deferred Props
Group related deferred props to load them in a single request:
return inertia('Dashboard', [ // Default group - separate request 'permissions' => Inertia::defer(fn () => Permission::all()), // 'sidebar' group - loaded together in one request 'teams' => Inertia::defer(fn () => Team::all(), 'sidebar'), 'projects' => Inertia::defer(fn () => Project::all(), 'sidebar'), // 'charts' group - another separate request 'salesChart' => Inertia::defer(fn () => Chart::sales(), 'charts'), 'trafficChart' => Inertia::defer(fn () => Chart::traffic(), 'charts'), ]);
Merge Props
Merge props append or prepend data to existing client-side data instead of replacing it. Perfect for infinite scrolling, real-time feeds, and pagination.
Append (Default)
return inertia('Feed', [ // New items are appended to existing items 'posts' => Inertia::merge(fn () => Post::paginate(10)), ]);
Prepend
return inertia('Chat', [ // New messages appear at the top 'messages' => Inertia::prepend(fn () => Message::latest()->take(20)->get()), ]);
Deep Merge
For nested objects where you want to merge at all levels:
return inertia('Settings', [ 'config' => Inertia::deepMerge(fn () => [ 'notifications' => ['email' => true], 'privacy' => ['showProfile' => false], ]), ]);
Deduplication
Prevent duplicate items when merging by specifying a match key:
return inertia('Feed', [ // Match by 'id' field to avoid duplicates 'posts' => Inertia::merge(fn () => Post::paginate())->matchOn('id'), ]);
Once Props
Once props are evaluated and sent only once. On subsequent page visits, the client reuses the cached value, reducing server load for static or rarely-changing data.
return inertia('Settings', [ // Fetched once, then cached on client 'countries' => Inertia::once(fn () => Country::all()), 'timezones' => Inertia::once(fn () => Timezone::all()), ]);
Expiration
Set an expiration time after which the prop will be re-fetched:
return inertia('Dashboard', [ // Refresh after 1 day 'exchangeRates' => Inertia::once(fn () => ExchangeRate::all()) ->until(now()->addDay()), // Refresh after 1 hour (using seconds) 'weather' => Inertia::once(fn () => Weather::current()) ->until(3600), ]);
Custom Keys
Share once props across different pages using custom keys:
// On Team/Index page return inertia('Team/Index', [ 'memberRoles' => Inertia::once(fn () => Role::all())->as('roles'), ]); // On Team/Invite page - reuses the same cached data return inertia('Team/Invite', [ 'availableRoles' => Inertia::once(fn () => Role::all())->as('roles'), ]);
Force Refresh
Force a prop to be re-fetched even if cached:
return inertia('Billing', [ 'plans' => Inertia::once(fn () => Plan::all())->fresh(), // Conditional refresh 'features' => Inertia::once(fn () => Feature::all())->fresh($planChanged), ]);
Global Once Props
Share once props across all pages:
// In a service provider or middleware Inertia::shareOnce('countries', fn () => Country::all()); Inertia::shareOnce('config', fn () => AppConfig::all())->until(now()->addHour());
Chaining Modifiers
Combine prop types for advanced behavior:
return inertia('Dashboard', [ // Load after render, then cache 'stats' => Inertia::defer(fn () => Stats::generate())->once(), // Merge new data, then cache 'activity' => Inertia::merge(fn () => $user->recentActivity())->once(), // Only load when requested, then cache 'reports' => Inertia::optional(fn () => Report::all())->once(), // Defer loading, then merge with existing data 'notifications' => Inertia::defer(fn () => Notification::paginate())->merge(), ]);
History Encryption
Encrypt the page data stored in browser history to protect sensitive information after logout.
Per-Request Encryption
// Enable encryption for this response return inertia() ->withEncryptedHistory() ->render('Account/Settings', $props);
Clear History
Clear previously encrypted history entries (useful after logout):
return inertia() ->withClearedHistory() ->render('Auth/Login');
Redirects
Inertia handles redirects intelligently, maintaining SPA behavior.
Standard Redirects
public function store(Request $request) { User::create($request->validated()); return inertia()->redirect('/users'); }
Back Redirect
public function update(Request $request, User $user) { $user->update($request->validated()); return inertia()->back(); }
External Redirects
For redirects to external URLs or non-Inertia pages:
return inertia()->location('https://external-site.com'); return inertia()->location('/non-inertia-page');
Note: Inertia automatically uses 303 status codes for redirects after PUT, PATCH, or DELETE requests to prevent browser confirmation dialogs.
View Composers
View composers run before specific components are rendered, allowing you to attach data dynamically.
// Single component Inertia::composer('Dashboard', function ($inertia) { Inertia::share([ 'dashboardStats' => Stats::forDashboard(), ]); }); // Multiple components Inertia::composer(['Users/Index', 'Users/Show', 'Users/Edit'], function ($inertia) { Inertia::share([ 'roles' => Role::all(), 'departments' => Department::all(), ]); }); // All components (useful for global data) Inertia::composer('*', function ($inertia) { Inertia::share([ 'appVersion' => config('app.version'), 'currentYear' => date('Y'), ]); });
Asset Versioning
Inertia uses asset versioning to ensure clients always have the latest assets. When assets change, Inertia triggers a full page reload.
By default, the adapter uses the Vite manifest file hash:
// Automatic (default behavior) // Uses: public/build/.vite/manifest.json
Custom Version
$inertia = Inertia::instance(); // Using manifest hash $inertia->setVersion(md5_file(public_path('build/manifest.json'))); // Using deployment timestamp $inertia->setVersion(config('app.deploy_version')); // Using git commit hash $inertia->setVersion(exec('git rev-parse --short HEAD'));
API Reference
| Method | Description |
|---|---|
Inertia::instance() |
Get the Inertia Adapter Instance |
Inertia::render($component, $props) |
Render an Inertia response |
Inertia::redirect($url, $status) |
Create a redirect response |
Inertia::back($status) |
Redirect to previous page |
Inertia::location($url) |
External redirect (409 response) |
Inertia::setRootView($view) |
Set the root Blade template |
Inertia::setVersion($version) |
Set the asset version |
Inertia::withEncryptedHistory($encrypt) |
Enable history encryption |
Inertia::withClearedHistory($clear) |
Clear encrypted history |
Inertia::share($key, $value) |
Share data globally |
Inertia::getShared($key, $default) |
Retrieve shared data |
Inertia::shareOnce($key, $callback) |
Share a once prop globally |
Inertia::flushShared() |
Clear all shared data |
Inertia::forceRefresh() |
Force the client to reload the page |
Inertia::composer($components, $callback) |
Register a view composer |
Inertia::lazy($callback) |
Create a lazy prop |
Inertia::optional($callback) |
Create an optional prop |
Inertia::always($callback) |
Create an always prop |
Inertia::defer($callback, $group) |
Create a deferred prop |
Inertia::merge($callback) |
Create a merge prop (append) |
Inertia::prepend($callback) |
Create a merge prop (prepend) |
Inertia::deepMerge($callback) |
Create a deep merge prop |
Inertia::once($callback) |
Create a once prop |
Helper Function
| Function | Description |
|---|---|
inertia() |
Get the Inertia instance |
inertia($component, $props) |
Render an Inertia response |
Blade Directive
| Directive | Description |
|---|---|
@inertia |
Render the root element with page data |
Router Macro
| Method | Description |
|---|---|
Route::inertia($path, $component, $props) |
Register an Inertia route |
License
MIT