Dan Matthews

Improve page responsiveness with lazy loading in InertiaJS

Part of the attractiveness of A javascript single page app vs. the more traditional server-rendered ones is a perception of speed for the users - page loads and navigation can feel instantaneous while presenting loading states to the user to show them that something's happening, rather than just waiting for the entire page to load.

We're currently using InertiaJS, which allows us to marry all the best parts of a single page app with the server-side routing and data management benefits of a more traditional javascript app.

Now normally you'd pass your data to Inertia by passing data in a similar way you'd pass it to blade views:

1return Inertia::render('MyComponent.svelte', [
2 'name' => auth()->user()->name,
3 'posts' => $posts,

And in our frontend component:

2// Grab the data as props.
3export let user = undefined;
4export let posts = [];
7{#each posts as post}
8 <div>
9 <h2>{post.title}</h2>
10 </div>
12 <p>No Posts Yet</p>

Simple right? But what if $posts is a complicated query takes 3-4 seconds to pull from the database? Laravel won't even send anything to the browser until it's completed that query. Your users will see a blank screen, or even worse, it'll just look like they've stayed on the same page and nothing has happened.

Now normally, we'd fix this by returning the empty page skeleton, and then making an AJAX request - usually with something like Axios, to retrieve our data.

This approach has a few drawbacks:

  1. You usually have to register another, separate route to make the request to which means you have to write a separate controller method or handler.
  2. The new controller or handler doesn't automatically have the context from the current page load, so you have to push that context (any query arguments or route parameters), in the ajax request.
  3. All of that is extra code to maintain and test.

Lazy loading solves this by allowing you run a partial reload on data that wasn't included at all during the initial page load. The Inertia documentation has a great guide on how to set up data for lazy loading:

1return Inertia::render('Users/Index', [
3 // ALWAYS included on first visit
4 // OPTIONALLY included on partial reloads
5 // ALWAYS evaluated
6 'users' => User::get(),
8 // ALWAYS included on first visit
9 // OPTIONALLY included on partial reloads
10 // ONLY evaluated when needed
11 'users' => fn () => User::get(),
13 // NEVER included on first visit
14 // OPTIONALLY included on partial reloads
15 // ONLY evaluated when needed
16 'users' => Inertia::lazy(fn () => User::get()),

You can see the big difference with the third approach is that it's "NEVER included on the first visit". This makes your initial page load much faster, and allows you to set up a spinner, skeleton, or loading component of your own to show that data is being loaded in.

To do this, first, wrap any large datasets or queries in the Inertia::lazy() function:

1return Inertia::render('MyComponent.svelte', [
2 'name' => auth()->user()->name,
3 'posts' => Inertia::lazy(function () {
4 return Post::query()
5 ->with('author')
6 ->where('published','=', 1)
7 ->limit(50)
8 ->get();
9 }

Now, because this is lazy-loaded, in our javascript component, the prop will have whatever initial value we set. Here, i'm setting it as undefined by default.

2 export let posts = undefined;
4 // mounted() in VueJS / useEffect() in React
5 onMount(() => {
7 // This will show the variable as empty.
8 console.log(posts);
10 // We fire a partial reload to load the data in:
11 Inertia.reload({
12 only: ['posts']
13 });
14 })
17<!-- In our template, we determine the state -->
18<!-- of the posts variable to show a loader -->
19{#if posts === undefined}
20 <!-- Show our loader component -->
21 <p>Loading</p>
23 {#each posts as post}
24 <!-- Render our blog post components -->
25 {/each}

We're doing a few things here:

  • Defining our prop as undefined by default, but you could also make this an empty array if you like.
  • When the component is mounted, we're making a partial reload call using Inertia.reload.
  • Checking for the state of the posts variable, and if it's undefined, we're showing a loader.

And that's it! You can use this approach to load in data when it's needed, not just on the mount hook.

For example, you could pair it with the Intersection API to detect when an element enters the brower viewport and fire the partial reload function.