rompetomp/inertia-bundle
Inertia.js adapter for Laravel for building single-page apps with classic server-side routing and controllers. Ships the middleware, root view, and config so Vue, React, or Svelte pages plug into Laravel without a separate API, with asset versioning and shared props built in.
First, a scoping note that saves an afternoon: rompetomp/inertia-bundle is the Symfony adapter for Inertia.js, not a Laravel package. If you're on Laravel, stop here and use inertiajs/inertia-laravel — the mental model transfers, but almost every integration detail (service registration, middleware wiring, asset versioning, root view config) is different.
Minimal steps on Symfony:
composer require rompetomp/inertia-bundleconfig/bundles.phpconfig/packages/framework.yaml / kerneluse Inertia\Inertia;
public function index(): \Symfony\Component\HttpFoundation\Response
{
return Inertia::render('Blog/Index', 'app.html.twig', [
'posts' => $postRepository->findLatest(),
]);
}
First use case to get working end-to-end: a single GET route that returns a component name plus one array of props, rendered into a root template that outputs the JSON page object. Everything else in Inertia (partial reloads, versioning, shared data) builds on that one response shape — verify it in the browser's view-source and in the JSON response to an X-Inertia: true request before touching anything else.
Where to look first: the bundle's middleware (it owns the 409/303/redirect behaviour), the Response class (it owns props, lazy props, version, and — as of v2.4.3 — the page URL), and the Twig extension that renders the root element.
Controller responses. Inertia::render($component, $rootView, $props) returns a response object whose methods are chainable. Keep controllers thin: build props, return the response, let the middleware negotiate HTML vs JSON.
Shared data. Don't duplicate auth user, flash messages, and validation errors across every controller. Register an event subscriber / kernel request listener that calls Inertia::share([...]) once per request. This is the Symfony equivalent of Laravel's HandleInertiaRequests middleware — there is no artisan generator for it, you write it.
final class InertiaSharedDataSubscriber implements EventSubscriberInterface
{
public function onKernelRequest(RequestEvent $event): void
{
Inertia::share([
'auth' => fn () => $this->currentUserPayload(),
'flash' => fn () => $this->pullFlashMessages(),
'errors' => fn () => $this->validationErrors(),
]);
}
}
Lazy props and partial reloads. Wrap expensive props in closures and mark them lazy/optional so partial reloads (only / except from the client) skip them. This is the single biggest performance lever in an Inertia app; a closure that isn't marked lazy still runs on every partial reload that includes its key, and a plain closure passed as a prop is evaluated eagerly in the bundle's resolution step.
Asset versioning. Set a version derived from your build manifest (entrypoints.json / manifest.json hash). On mismatch, Inertia responds 409 with X-Inertia-Location for GET and forces a full reload — this is the mechanism that prevents users running stale JS against new props, and it only works if the version actually changes on deploy.
Custom page URL (v2.4.3). The response now accepts an explicit URL for the page object sent to the client, instead of always echoing the request URI. Use it when the URL the client should record differs from the one the server sees: localized/translated routes, a page rendered from a non-canonical path, or behind a proxy where the request URI isn't the public URL.
return Inertia::render('Blog/Show', 'app.html.twig', ['post' => $post])
->url($router->generate('blog_show', ['slug' => $post->getSlug()]));
Check PR #50 for the exact method signature in your version; the key point is that this changes only the url field in the page payload — it does not change the actual HTTP response URL or the router's notion of the current route.
Testing. Feature-test the JSON contract: send X-Inertia: true plus X-Inertia-Version, assert 200, Content-Type: application/json, and the component/props keys. Assert the new url override explicitly if you depend on it, since it's client-visible state that's easy to regress silently.
PUT/PATCH/DELETE requests that return a redirect must be converted to 303 or the browser will replay the method against the redirect target — the classic "my delete form now 405s" bug.X-Inertia-Version against the server version before debugging anything else. Usually it's a manifest read at container compile time or a version that's constant across deploys.[]. A prop you intend as an object (e.g. errors when there are none, or a config map) becomes a JS array and blows up on property access. Cast to (object) or stdClass for anything the frontend treats as a dictionary.page.url into history. If you set a URL that no real route serves, back/forward will land on a 404 or a different page than the one the user saw. Use it to correct a URL that genuinely corresponds to that resource, not to cosmetic-ise it.page.url server-side. Your controllers and generateUrl() calls still see the real request. The override exists to reconcile server and client views; it isn't a rewrite of the request context.419-equivalent after a deploy or session rotation, not during development.How can I help you explore Laravel packages today?