| Install | |
|---|---|
composer require crenspire/yii3-inertia |
|
| Latest Version: | v1.0.0 |
| PHP: | ^8.1 |
An Inertia.js adapter for Yii3 framework, providing a seamless bridge between your Yii3 backend and modern JavaScript frontend frameworks (React, Vue, Svelte).
inertia-laravelInstall via Composer:
composer require crenspire/yii3-inertia
For Yii3 applications, the package provides automatic configuration via ConfigProvider:
// config/web.php or your main config file
return [
// Include Inertia ConfigProvider for auto-configuration
\Crenspire\Inertia\ConfigProvider::class,
// ... your other config
];
// config/web.php
return [
'middleware' => [
// Error handling middleware
// Authentication middleware
\Crenspire\Inertia\Middleware\InertiaMiddleware::class, // ← Add here
// Routing middleware
// Controller/Action execution
],
];
use Crenspire\Inertia\ControllerTrait;
use Crenspire\Inertia\ResponseFactory;
use Psr\Http\Message\ResponseInterface;
use Psr\Http\Message\ServerRequestInterface;
class HomeController
{
use ControllerTrait;
public function __construct(
private ResponseFactory $responseFactory
) {}
protected function getResponseFactory(): ResponseFactory
{
return $this->responseFactory;
}
public function index(ServerRequestInterface $request): ResponseInterface
{
return $this->inertiaRender('Home', [
'title' => 'Welcome',
], $request);
}
}
That's it! The ConfigProvider automatically configures all services. See examples/yii3-web for a complete example.
Register the Inertia middleware in your application's middleware stack:
use Crenspire\Inertia\Middleware\InertiaMiddleware;
use Crenspire\Inertia\ResponseFactory;
use Nyholm\Psr7\Factory\Psr17Factory;
$psr17Factory = new Psr17Factory();
$responseFactory = new ResponseFactory($psr17Factory, $psr17Factory);
$inertiaMiddleware = new InertiaMiddleware($responseFactory, $psr17Factory);
// Add to your middleware stack
// Note: Middleware should be registered early in the stack to set up the request
use Crenspire\Inertia\Inertia;
use Crenspire\Inertia\ResponseFactory;
use Psr\Http\Message\ServerRequestInterface;
use Psr\Http\Message\ResponseInterface;
class HomeAction
{
public function __invoke(
ServerRequestInterface $request,
ResponseFactory $responseFactory
): ResponseInterface {
Inertia::setRequest($request);
$payload = Inertia::render('Home', [
'title' => 'Welcome',
'user' => $user,
]);
if (Inertia::isInertiaRequest($request)) {
return $responseFactory->json($payload);
}
return $responseFactory->html($payload, Inertia::getRootView());
}
}
For easier usage in controllers (works with Yii3 DI):
use Crenspire\Inertia\ControllerTrait;
use Crenspire\Inertia\ResponseFactory;
use Psr\Http\Message\ResponseInterface;
use Psr\Http\Message\ServerRequestInterface;
class HomeController
{
use ControllerTrait;
public function __construct(
private ResponseFactory $responseFactory
) {}
protected function getResponseFactory(): ResponseFactory
{
return $this->responseFactory;
}
public function index(ServerRequestInterface $request): ResponseInterface
{
return $this->inertiaRender('Home', [
'title' => 'Welcome',
], $request);
}
}
Note: With Yii3 ConfigProvider, ResponseFactory is automatically injected via DI container.
For actions, extend the InertiaAction base class to eliminate boilerplate:
use Crenspire\Inertia\Action\InertiaAction;
use Psr\Http\Message\ResponseInterface;
class HomeAction extends InertiaAction
{
public function __invoke(): ResponseInterface
{
// Helper methods available:
// - $this->render() - Render Inertia page
// - $this->getRequest() - Get current request
// - $this->getQueryParam() - Get query parameter
// - $this->getBodyParam() - Get body parameter
// - $this->redirect() - Create redirect response
// - $this->isInertiaRequest() - Check if Inertia request
return $this->render('Home', [
'title' => 'Welcome',
'page' => $this->getQueryParam('page', 1),
]);
}
}
With DI Container (Yii3):
// Action is automatically instantiated with request and ResponseFactory
$action = $container->get(HomeAction::class);
return $action();
Manual instantiation:
$action = new HomeAction($request, $responseFactory);
return $action();
The base class automatically:
Install Inertia.js and your frontend framework:
npm install @inertiajs/inertia @inertiajs/inertia-react react react-dom
Create src/main.jsx:
import React from 'react';
import ReactDOM from 'react-dom/client';
import { createInertiaApp } from '@inertiajs/inertia-react';
import Home from './pages/Home';
createInertiaApp({
resolve: (name) => {
const pages = { Home };
return pages[name];
},
setup({ el, App, props }) {
ReactDOM.createRoot(el).render(<App {...props} />);
},
});
The InertiaAction base class provides a convenient way to create Inertia actions with automatic request handling and helper methods.
Available Helper Methods:
render(string $component, array $props = []): ResponseInterface - Render an Inertia pagegetRequest(): ServerRequestInterface - Get the current requestgetQueryParam(string $name, $default = null) - Get a query parametergetQueryParams(): array - Get all query parametersgetBodyParam(string $name, $default = null) - Get a request body parametergetParsedBody(): array - Get parsed request bodygetAttribute(string $name, $default = null) - Get a request attributegetMethod(): string - Get request methodisGet(): bool - Check if request is GETisPost(): bool - Check if request is POSTisInertiaRequest(): bool - Check if request is an Inertia requestredirect(string $url): ResponseInterface - Create an Inertia redirect responsegetResponseFactory(): ResponseFactory - Get the ResponseFactory instanceExample:
use Crenspire\Inertia\Action\InertiaAction;
use Psr\Http\Message\ResponseInterface;
class UserAction extends InertiaAction
{
public function __invoke(): ResponseInterface
{
if ($this->isPost()) {
// Handle POST request
$name = $this->getBodyParam('name');
// ... save user
return $this->redirect('/users');
}
// Handle GET request
$userId = (int) $this->getQueryParam('id', 0);
return $this->render('User', [
'userId' => $userId,
]);
}
}
Render an Inertia page:
$payload = Inertia::render('Dashboard', [
'users' => $users,
]);
Share data with all Inertia responses:
// Single key-value
Inertia::share('appName', 'My App');
// Multiple values
Inertia::share([
'user' => $user,
'flash' => $flash,
]);
// Using closures
Inertia::share('timestamp', function () {
return time();
});
Set or get the asset version:
// String version
Inertia::version('1.0.0');
// Callback version
Inertia::version(function () {
return filemtime('/path/to/manifest.json');
});
// Get current version
$version = Inertia::version();
Create an Inertia redirect response:
$location = Inertia::location('/dashboard');
// Returns: ['location' => '/dashboard', 'status' => 409 or 302]
The status code depends on the request type:
You can use this in your actions:
public function store(ServerRequestInterface $request): ResponseInterface
{
// ... save data
$location = Inertia::location('/dashboard');
$response = $responseFactory->responseFactory->createResponse($location['status']);
if ($location['status'] === 409) {
return $response->withHeader('X-Inertia-Location', $location['location']);
}
return $response->withHeader('Location', $location['location']);
}
You can also use the global inertia() helper function:
$payload = inertia('Home', ['title' => 'Welcome'], $request);
Inertia supports partial reloads for better performance. The client can request only specific props:
// Client sends: X-Inertia-Partial-Component: Dashboard
// Client sends: X-Inertia-Partial-Data: users,stats
// Only 'users' and 'stats' props will be returned (plus shared props)
$payload = Inertia::render('Dashboard', [
'users' => $users,
'stats' => $stats,
'other' => $other, // This will be excluded
]);
Configure Vite dev server and production asset paths via Yii3 params:
// config/params.php
return [
'inertia' => [
'assetConfig' => [
'viteHost' => 'localhost', // Vite dev server host
'vitePort' => 5173, // Vite dev server port
'viteEntryPath' => 'src/main.jsx', // Entry point for Vite dev server
'manifestEntryKey' => 'src/main.jsx', // Manifest entry key (matches vite.config.js input)
'publicPath' => 'public', // Public directory path
'buildOutputDir' => 'dist', // Build output directory
'manifestFileName' => 'manifest.json', // Manifest file name
],
],
];
The AssetConfig is automatically resolved from params when using ConfigProvider. You can also inject it directly:
use Crenspire\Inertia\AssetConfig;
// Get from container
$assetConfig = $container->get(AssetConfig::class);
// Or create manually
$assetConfig = new AssetConfig(
viteHost: 'localhost',
vitePort: 5173,
viteEntryPath: 'src/main.jsx',
manifestEntryKey: 'src/main.jsx',
publicPath: 'public',
buildOutputDir: 'dist',
manifestFileName: 'manifest.json'
);
You can configure the root view path:
Inertia::setRootView('custom-inertia');
For Yii3 applications, use ConfigProvider (recommended):
// config/web.php
return [
// ConfigProvider automatically configures all services
\Crenspire\Inertia\ConfigProvider::class,
];
Manual DI configuration (if not using ConfigProvider):
use Crenspire\Inertia\Middleware\InertiaMiddleware;
use Crenspire\Inertia\ResponseFactory;
use Psr\Http\Message\ResponseFactoryInterface;
use Psr\Http\Message\StreamFactoryInterface;
// In your DI container configuration
$container->set(ResponseFactory::class, function ($container) {
$responseFactory = $container->get(ResponseFactoryInterface::class);
$streamFactory = $container->get(StreamFactoryInterface::class);
// Required: provide a view renderer callback
// With Yii3 View (recommended):
$view = $container->get(\Yiisoft\View\WebView::class);
$viewRenderer = \Crenspire\Inertia\ResponseFactory::createViewRenderer($view);
// Or custom view renderer:
// $viewRenderer = function (string $view, array $payload): string {
// return $yourViewRenderer->render($view, ['page' => $payload]);
// };
return new ResponseFactory($responseFactory, $streamFactory, $viewRenderer);
});
$container->set(InertiaMiddleware::class, function ($container) {
$responseFactory = $container->get(ResponseFactory::class);
$psrResponseFactory = $container->get(ResponseFactoryInterface::class);
return new InertiaMiddleware($responseFactory, $psrResponseFactory);
});
Important: ResponseFactory now requires a view renderer. The ConfigProvider automatically configures it using Yii3's WebView or View if available.
Manual configuration:
use Crenspire\Inertia\ResponseFactory;
use Crenspire\Inertia\ViewRenderer;
// With Yii3 View
$viewRenderer = ResponseFactory::createViewRenderer($yii3View);
// Or custom view renderer
$viewRenderer = function (string $view, array $payload): string {
// Use your view rendering system (Twig, Blade, etc.)
return $yourViewRenderer->render($view, ['page' => $payload]);
};
$responseFactory = new ResponseFactory(
$psr17Factory,
$psr17Factory,
$viewRenderer
);
Note: If yiisoft/view is not installed, you must provide a custom view renderer. The ConfigProvider will throw an exception if no view renderer is available.
For shared props that should be available on every page, set them in your application bootstrap:
Using Inertia::share() directly (recommended):
use Crenspire\Inertia\Inertia;
// In your application bootstrap or middleware
Inertia::share('user', function () use ($userService) {
return $userService->getCurrentUser();
});
Inertia::share('app', [
'name' => 'My App',
'version' => '1.0.0',
]);
Using Bootstrap helper (optional convenience):
use Crenspire\Inertia\Bootstrap;
// In your application bootstrap
Bootstrap::setupSharedProps($userService, $flashService);
Bootstrap::setupVersion('/path/to/manifest.json');
Bootstrap::setupRootView('inertia');
// Or use the complete setup method
Bootstrap::setup([
'userService' => $userService,
'flashService' => $flashService,
'manifestPath' => '/path/to/manifest.json',
'rootView' => 'inertia',
'shared' => [
'app' => ['name' => 'My App'],
],
]);
Note: The Bootstrap helper is completely optional. It provides convenience methods but has zero overhead if not used. You can use Inertia::share() directly for the same result.
Inertia.js uses version checking to ensure the frontend and backend stay in sync. When the client's version doesn't match the server's version, a full page reload is triggered.
By default, the version is automatically detected from your manifest.json file:
// Automatically uses manifest.json mtime if it exists
$version = Inertia::version();
You can set a custom version:
// String version
Inertia::version('1.0.0');
// Callback version (evaluated on each request)
Inertia::version(function () {
return filemtime('/path/to/manifest.json');
});
When a client sends an X-Inertia-Version header that doesn't match the current version, the middleware automatically returns a location redirect (409 status) to trigger a full page reload. This ensures users always have the latest assets.
The InertiaMiddleware should be registered early in your middleware stack, but after any authentication/authorization middleware that sets up the user context. This ensures:
Example middleware stack order:
1. Error handling middleware
2. Authentication middleware
3. InertiaMiddleware ← Register here
4. Routing middleware
5. Controller/Action execution
The repository includes several example applications:
Full Yii3 web application with ConfigProvider, controllers, and middleware:
cd examples/yii3-web
composer install
php -S localhost:8000 -t public
See examples/yii3-web/README.md for details.
Minimal Yii3 setup (DI + Router only):
cd examples/yii3-minimal
composer install
php -S localhost:8000 -t public
See examples/yii3-minimal/README.md for details.
Basic PSR-7/PSR-15 example (for non-Yii3 frameworks):
# Install dependencies
cd examples/basic
composer install
# Install frontend dependencies
cd vite
npm install
# Build frontend assets
npm run build
# Or run dev server
npm run dev
# Start PHP server
cd ../public
php -S localhost:8000
Note: For Yii3 applications, use the Yii3 examples above instead.
If you're experiencing frequent full page reloads:
manifest.json file exists and is accessibleIf the middleware isn't processing requests correctly:
Inertia::setRequest() is called (middleware does this automatically)ResponseFactory and ResponseFactoryInterfaceIf redirects aren't working as expected:
Inertia::location() and handling the response correctlyX-Inertia header for Inertia requestsIf you get "Request not set" errors:
InertiaMiddleware is registered and processes requestsInertia::setRequest($request) in your actionsRun the test suite:
composer install
vendor/bin/phpunit
MIT License. See LICENSE file for details.
Please see CONTRIBUTING.md for details.
See CHANGELOG.md for a list of changes.