Inertia.js is fast by default, but production applications slow down for predictable reasons: oversized props, a single JavaScript bundle that grows with every page, unnecessary full-page reloads and a database that runs an N+1 query behind every list. This guide is a practical performance checklist for Laravel + React + Inertia, ordered by return on effort.
Key takeaway: The short version — split pages into separate chunks, send only the props the UI needs, use partial reloads and deferred props, prefetch likely next pages, cache expensive queries, eliminate N+1 and monitor the SSR process. Measure before and after; do not optimize by intuition.
Where Inertia performance problems come from
Inertia adds one thing to a Laravel application: a JSON payload with the page component and props. The performance profile is therefore dominated by:
- Payload size — how much data each page sends.
- Bundle size — how much JavaScript the browser must download and parse.
- Request count — how many visits and partial reloads the UI triggers.
- Database work — how many queries run to build the props.
- Rendering — whether SSR is enabled and whether the SSR process is healthy.
Most "Inertia is slow" reports turn out to be a 2 MB JSON payload, a 1.5 MB bundle or a 300-query dashboard — not a problem with Inertia itself.
Measure first
Use the same tools you would use for any Laravel application
| Tool | What it shows |
|---|---|
| Laravel Debugbar | query count, duplicates, timeline (local) |
| Laravel Telescope | requests, queries, jobs, exceptions (staging) |
| Laravel Pulse | slow queries, slow endpoints, cache and queues (production) |
| Lighthouse / PageSpeed Insights | LCP, INP, CLS, bundle opportunities |
| WebPageTest | waterfall, filmstrip, caching |
| Vite bundle analyzer | which chunks are large and why |
Measure the three most important screens, not the whole app. If the dashboard is slow and the settings page is fine, optimize the dashboard.
Code splitting
The default import.meta.glob('./Pages/**/*.jsx', { eager: true }) imports every page into the main bundle. That is convenient in development and expensive in production.
Use lazy imports
const pages = import.meta.glob('./Pages/**/*.jsx');
Each page becomes a separate chunk, loaded only when the user visits it. Combine this with a loading state
createInertiaApp({
resolve: async (name) => {
const pages = import.meta.glob('./Pages/**/*.jsx');
const page = await pages[`./Pages/${name}.jsx`]();
return page;
},
});
Check the build output. A page that imports a charting library, a rich text editor or a date library should be its own chunk. If the shared vendor chunk is still large, inspect it with vite-bundle-visualizer and replace or lazy-load heavy dependencies.
Props discipline
Every prop is serialized to JSON, sent over the network and hydrated in the browser. Treat props like an API response.
- Use API Resources to select only the columns the UI needs.
- Paginate lists; never send 10,000 rows.
- Avoid nested models with all relations loaded. Shape the data for the screen.
- Do not share expensive data in
HandleInertiaRequests::share()on every request. - Use
withCount()instead of loading a relation just to count it.
return Inertia::render('Orders/Index', [
'orders' => OrderResource::collection(
Order::query()
->select(['id', 'customer_id', 'total', 'status', 'created_at'])
->with('customer:id,name')
->latest()
->paginate(20)
),
]);
- A useful rule: if a prop is not visible on the first screen and is not needed for interaction, defer it.
Partial reloads and deferred props
A filter change should not reload the whole page. Use partial reloads
router.reload({ only: ['orders'] });
Heavy secondary data should load after the first render
return Inertia::render('Dashboard', [
'stats' => Inertia::defer(fn () => $this->stats()),
'audit' => Inertia::optional(fn () => $this->auditLog()),
]);
<Deferred data="stats" fallback={<Skeleton />}>
{(stats) => <StatsGrid stats={stats} />}
</Deferred>
This improves perceived performance even when the total data volume is unchanged: the page becomes interactive sooner and the user sees the primary content first.
Prefetching
Inertia can prefetch a page before the user clicks
<Link href="/orders" prefetch="hover">Orders</Link>
<Link href="/reports" prefetch="mount">Reports</Link>
hoveris cheap and safe for most navigation.mountis aggressive; use it only for the most likely next page.- Prefetching does not help if the page payload is huge — fix the payload first.
Caching
Cache data that does not change on every request
$stats = Cache::remember('dashboard:stats', now()->addMinutes(5), fn () =>
Order::query()->selectRaw('status, count(*) as total')->groupBy('status')->get()
);
For expensive pages, cache the rendered fragment or the API resource collection. Invalidate with tags (Redis) or key versioning, not Cache::flush().
HTTP caching is also useful for public pages: Cache-Control: public, s-maxage=60, stale-while-revalidate=300 at the CDN or reverse proxy. Do not cache authenticated pages.
Database and N+1
Inertia makes N+1 visible because the query runs while building props. Enable Model::preventLazyLoading() in development and add query budget assertions to your tests.
Order::with('customer', 'items.product')->paginate(20);
Use withCount(), withSum() and withAvg() for aggregates. Add indexes for the columns used in filters and sorting. See the Laravel performance guide for the full database checklist.
SSR performance
SSR improves first paint but adds a Node process. Keep it healthy and fast
- Build the SSR bundle in CI and deploy it with the application.
- Run the SSR server under Supervisor or systemd with a restart policy.
- Monitor memory and response time; restart on leaks.
- Do not run heavy queries twice (once for SSR, once for hydration). Cache shared data.
- Use
stale-while-revalidatefor public pages where appropriate. - If SSR is down, Inertia falls back to client rendering — fast to recover, bad for SEO. Alert on it.
Laravel Octane
Inertia works with Octane, but Octane keeps the application in memory between requests. Watch for
- static state and singletons that leak between users;
- shared props that capture the first request's user;
- Inertia responses cached accidentally;
- memory growth in long-running workers.
Test with a staging environment and a load test before switching production. For most applications, OPcache and PHP-FPM tuning deliver most of the benefit with less risk.
Performance budget
A practical budget for a SaaS page
| Metric | Target |
|---|---|
| JavaScript per route (gzip) | < 200 KB |
| LCP (mobile) | < 2.5 s |
| INP | < 200 ms |
| CLS | < 0.1 |
| TTFB | < 500 ms |
| Props payload | < 100 KB per page |
Track the budget in CI with Lighthouse CI. A performance regression is easier to fix in a pull request than in production.
Checklist
- Lazy page imports and separate chunks.
- API Resources and selected columns.
- Pagination on every list.
- Partial reloads for filters and pagination.
- Deferred props for heavy secondary data.
- Prefetch on hover for likely navigation.
- Cache for expensive queries.
- No N+1 queries.
- SSR process monitored.
- Lighthouse CI budget enforced.
- Octane tested before enabling.
FAQ
Why is my Inertia page slow on the first load?
Usually a large JavaScript bundle or a large props payload. Check the build output and the JSON size, then split pages and reduce props.
Does Inertia send all props on every visit?
It sends the props returned by the controller. Use partial reloads to request only what changed and deferred props for heavy data.
Is SSR faster than client-side rendering?
SSR improves first contentful paint and SEO, but it adds server cost. It is not automatically faster for every interaction; client-side navigation is still fast after hydration.
How do I reduce the JavaScript bundle?
Use lazy import.meta.glob, split heavy pages, replace large dependencies, and analyze the bundle with vite-bundle-visualizer.
Does prefetching improve performance?
It improves perceived performance for likely navigation. It does not reduce total bytes; use it selectively.
Can I use React Query with Inertia?
You can, but you usually do not need it. Inertia already handles server state through props. Use React Query for client-side data that is not part of the page visit.
How do I measure Inertia performance in production?
Use Laravel Pulse for slow endpoints and queries, and Chrome UX Report or PageSpeed Insights for field Core Web Vitals.
Sources
- Inertia.js documentation
- Laravel performance documentation
- Vite build documentation
- web.dev — Core Web Vitals
- Laravel Pulse
Need help making an Inertia.js application faster? Check our Laravel development services or hire a Laravel developer.
Read more: Inertia.js with Laravel and React, Laravel performance optimization, Inertia.js SEO guide and Inertia.js SSR with Laravel.