Dan Newns.
Back to Content

Inertia.js Once Props: Stop Sending the Same Data Over and Over Again

27th December 2025

This post was originally published on the Jump24 blog, where I serve as Technical Director. I've syndicated it here because I wrote it, I'm proud of the work, and I want my personal site to reflect the full scope of what I'm building - both professionally and personally. If you're here for Laravel insights, you'll find them alongside golf breakdowns and life updates. If you found this helpful, you might enjoy Jump24's other content too.

We've been long time fans of Inertia.js here at Jump24 and honestly, it's become our go-to for building modern Laravel applications without the faff of maintaining a separate API layer. Between deferred props, prefetching, and partial reloads, the Inertia team has been absolutely smashing it with performance features lately.

So when we spotted a new Inertia::once() method land in version 2.0.12 of the Laravel adapter (released December 2025), we got pretty excited as we could see straight away how this could help us improve the way we write Inertia apps.

The Problem We've All Been silently Ignoring

Here's something we've all done but probably haven't thought too hard about. Every single request to your Inertia application sends the same shared data. Every. Single. Request.

That list of countries for your dropdown? Sent again. Those user permissions that haven't changed in months? Sent again. The available subscription plans that your marketing team updates once a quarter? You guessed it – sent again.

Now, for small amounts of data, this isn't really a big deal I mean you're already probably caching the query anyway right? But when you start adding things like comprehensive permission sets, lookup tables, or configuration options, you're suddenly sending kilobytes of identical data on every page navigation. It's wasteful, and deep down, we all knew it.

Enter Once Props

The Inertia::once() method is deceptively simple. You wrap your prop in it, and Inertia will send it on the first request, then remember it client-side for subsequent navigations. The server knows not to bother sending it again, and the client knows to reuse what it already has.

Let's look at an example – a permissions-based dashboard.

php
1<?php
2 
3// Before: Every navigation sends this data
4return Inertia::render('Dashboard/Index', [
5 'stats' => $this->getDashboardStats(),
6 'recentActivity' => $user->recentActivity()->take(10)->get(),
7 'permissions' => Permission::all(), // 150+ permissions, every time
8 'availableRoles' => Role::with('permissions')->get(),
9]);
php
1<?php
2 
3// After: Permissions and roles are sent once, cached client-side
4return Inertia::render('Dashboard/Index', [
5 'stats' => $this->getDashboardStats(),
6 'recentActivity' => $user->recentActivity()->take(10)->get(),
7 'permissions' => Inertia::once(fn () => Permission::all()),
8 'availableRoles' => Inertia::once(fn () => Role::with('permissions')->get()),
9]);

That's it. The closure ensures lazy evaluation (so it doesn't run if the client already has the data), and Inertia handles all the client-side caching magic.

Sharing Once Props Globally

The real power comes when you combine this with shared data. We have plenty of applications where we're sharing lookup data across every single page – things that rarely change but are needed everywhere.

You can do this in your HandleInertiaRequests middleware:

php
1<?php
2 
3class HandleInertiaRequests extends Middleware
4{
5 public function share(Request $request): array
6 {
7 return array_merge(parent::share($request), [
8 'auth' => [
9 'user' => fn () => $request->user()?->only('id', 'name', 'email'),
10 ],
11 // These are now sent once and cached
12 'currencies' => Inertia::once(fn () => Currency::active()->get()),
13 'timezones' => Inertia::once(fn () => Timezone::all()),
14 'industryTypes' => Inertia::once(fn () => IndustryType::ordered()->get()),
15 ]);
16 }
17}

But there's an even cleaner approach – the new shareOnce() method:

php
1<?php
2 
3class HandleInertiaRequests extends Middleware
4{
5 public function share(Request $request): array
6 {
7 return array_merge(parent::share($request), [
8 'auth' => [
9 'user' => fn () => $request->user()?->only('id', 'name', 'email'),
10 ],
11 ]);
12 }
13 
14 public function shareOnce(Request $request): array
15 {
16 return array_merge(parent::shareOnce($request), [
17 'currencies' => fn () => Currency::active()->get(),
18 'timezones' => fn () => Timezone::all(),
19 'industryTypes' => fn () => IndustryType::ordered()->get(),
20 ]);
21 }
22}

Notice how in shareOnce() you don't need to wrap things in Inertia::once() – it's implicit. Keeps things tidy.

Combining Once Props with Other Modifiers

Here's where I think things get interesting. The once() modifier plays nicely with other Inertia prop types.

With Deferred Props

Got something that's expensive to compute but doesn't change often? Defer it AND mark it as once:

php
1<?php
2 
3return Inertia::render('Reports/Analytics', [
4 'summary' => $this->getQuickSummary(),
5 'historicalTrends' => Inertia::defer(fn () => $this->calculateHistoricalTrends())
6 ->once(),
7]);

The historical trends will load after the page renders (deferred), and once loaded, Inertia will reuse them on subsequent visits to this page.

With Optional Props

For data that should only be included when explicitly requested:

php
1<?php
2 
3return Inertia::render('Settings/Account', [
4 'profile' => $user->profile,
5 'exportFormats' => Inertia::optional(fn () => ExportFormat::all())
6 ->once(),
7]);

With Merge Props

This one's particularly useful for things like infinite scroll where you're also caching reference data:

php
1<?php
2 
3return Inertia::render('Products/Catalogue', [
4 'products' => Inertia::merge(fn () => $this->getProducts($page)),
5 'categories' => Inertia::optional(fn () => Category::tree()->get())
6 ->once(),
7 'filterOptions' => Inertia::once(fn () => $this->getFilterOptions()),
8]);

When You Need Fresh Data

Sometimes data that's usually static needs to be refreshed – maybe after an admin updates the system configuration, or when certain events occur. The fresh() method handles this:

php
1<?php
2 
3return Inertia::render('Billing/Plans', [
4 'plans' => Inertia::once(fn () => Plan::active()->get())
5 ->fresh($request->boolean('refresh_plans')),
6]);

Or just always force a refresh (but do you really want to do this?):

php
1<?php
2 
3return Inertia::render('Billing/Plans', [
4 'plans' => Inertia::once(fn () => Plan::active()->get())
5 ->fresh(),
6]);

Setting Expiration Times

For data that should be refreshed periodically there is a nice helper to allow you to set an expiry time for that data:

php
1<?php
2 
3return Inertia::render('Exchange/Rates', [
4 'rates' => Inertia::once(fn () => ExchangeRate::current()->get())
5 ->until(now()->addHours(4)),
6]);

The until() method accepts a DateTimeInterface, DateInterval, or an integer representing seconds. After the specified time, Inertia will fetch fresh data on the next navigation.

Sharing Keys Across Different Pages

Here's a clever pattern we've been exploring. Sometimes you have the same underlying data but want to expose it under different prop names on different pages. The as() method lets you assign a custom cache key:

php
1<?php
2 
3// On the team members list page
4return Inertia::render('Team/Members', [
5 'memberRoles' => Inertia::once(fn () => Role::all())->as('system.roles'),
6]);
7 
8// On the invite page
9return Inertia::render('Team/Invite', [
10 'availableRoles' => Inertia::once(fn () => Role::all())->as('system.roles'),
11]);

Both pages reference the same cached data under the hood, even though they use different prop names. One fetch, two pages, happy users.

A Practical Example: Building a Multi-Step Form

Let's put this all together with something we build regularly – a multi-step registration or onboarding flow:

php
1<?php
2 
3class OnboardingController extends Controller
4{
5 public function companyDetails()
6 {
7 return Inertia::render('Onboarding/CompanyDetails', [
8 'progress' => 1,
9 'companySizes' => Inertia::once(fn () => CompanySize::all())
10 ->as('onboarding.lookups'),
11 'industries' => Inertia::once(fn () => Industry::ordered()->get())
12 ->as('onboarding.lookups'),
13 ]);
14 }
15 
16 public function teamSetup()
17 {
18 return Inertia::render('Onboarding/TeamSetup', [
19 'progress' => 2,
20 'departmentTypes' => Inertia::once(fn () => DepartmentType::all())
21 ->as('onboarding.lookups'),
22 'roleTemplates' => Inertia::once(fn () => RoleTemplate::with('permissions')->get())
23 ->as('onboarding.lookups'),
24 ]);
25 }
26 
27 public function preferences()
28 {
29 return Inertia::render('Onboarding/Preferences', [
30 'progress' => 3,
31 'timezones' => Inertia::once(fn () => Timezone::all())
32 ->as('onboarding.lookups'),
33 'notificationChannels' => Inertia::once(fn () => NotificationChannel::all())
34 ->as('onboarding.lookups'),
35 ]);
36 }
37}

As users navigate back and forth through the steps, all those lookup tables are loaded once and reused. The onboarding flow feels snappy because we're not hammering the database on every step change.

What About Testing?

At the moment we've upgraded one project to start using the new Once method and we've not found any big issues with testing as normal which is great..

Our Honest Take

Looking at the implementation and thinking about our existing applications, there are several places where this is going to make a meaningful difference:

Where we're excited to use it:

  • Permission and role data that's shared across admin dashboards

  • Dropdown options and lookup tables

  • Configuration data that rarely changes

  • Any large datasets that are the same across multiple pages (think country lists, currency lists etc)

Where we probably won't bother:

  • Small pieces of data where the overhead isn't worth it

  • Data that genuinely changes frequently

  • Page-specific props that aren't reused elsewhere

Getting Started

To use once props, make sure you're on inertiajs/inertia-laravel version 2.0.12 or higher:

shell
1composer require inertiajs/inertia-laravel:^2.0.12

The feature was contributed by Pascal Baljet and is available now. You can read the official documentation for more details, but honestly, the API is so straightforward that you can probably just start using it.

Wrapping Up

Inertia.js continues to evolve in ways that show the team really understands what Laravel developers need. Once props solve a real problem – redundant data transfer - in a way that's both elegant and unintrusive.

Is this going to revolutionise your application's performance? Probably not on its own. But it's another tool in the optimisation toolkit, and for applications dealing with substantial shared data, it could make a noticeable difference.

We'll be integrating this into more of our applications over the coming weeks and will share our findings. Have you started using once props yet? What lookup tables are you most excited to cache? Let us know in the comments or give us a shout on Twitter.