A fast interface is not only about reducing server response time. It is also about choosing the right moment to request data.
Inertia 3.x gives Laravel and Vue developers several ways to do that. You can prefetch likely destinations on hover, load predictable pages on mount, and serve cached responses while fresh data arrives in the background.
Combine those strategies with deferred props and usePoll, and your Laravel + Vue SPA can feel immediate without turning every screen into a client-side API project.
This approach fits naturally into Laravel’s Vue starter kit. You keep Laravel routes and controllers while Vue handles the interactive page layer.
Prefetching: Request the Next Page Early
Prefetching loads an Inertia page before the user navigates to it. The response is stored in Inertia’s client-side cache.
When the user clicks the link, Inertia can use that response instead of starting from zero.
The simplest form uses hover prefetching:
<script setup lang="ts">
import { Link } from '@inertiajs/vue3'
</script>
<template>
<Link href="/projects" prefetch>
Projects
</Link>
</template>
By default, Inertia starts prefetching after the user hovers for more than 75 milliseconds. You can adjust that delay in your client-side defaults.
Hover prefetching works well for navigation menus, tables, and cards. It does not request every page on initial load. It waits for a clear signal that the user may continue there.
For links with an almost certain next destination, use mount prefetching:
<Link
href="/checkout/shipping"
prefetch="mount"
>
Continue to shipping
</Link>
This starts the request when the link component mounts. It suits checkout flows, onboarding steps, and multi-page forms.
You can also prefetch on mousedown with the click strategy:
<Link
href="/reports"
prefetch="click"
>
Open reports
</Link>
This delays the request until the user begins clicking. It is useful when a page is expensive and hover intent is less reliable.
Inertia also lets you combine strategies:
<Link
href="/dashboard"
:prefetch="['mount', 'hover']"
>
Dashboard
</Link>
This warms the page when the component appears. A later hover can trigger another prefetch when the cached response needs refreshing.

Use cacheFor for Freshness Control
Prefetching is most useful when you control how long cached data remains valid.
Inertia caches prefetched responses for 30 seconds by default. You can set a custom duration on each link:
<Link
href="/invoices"
prefetch
cache-for="1m"
>
Invoices
</Link>
You can also use a numeric value in milliseconds:
<Link
href="/activity"
prefetch
:cache-for="15000"
>
Activity
</Link>
For more control, pass a tuple. The first value defines the fresh period. The second defines the full stale period.
<Link
href="/dashboard"
prefetch
:cache-for="['30s', '2m']"
>
Dashboard
</Link>
This creates a stale-while-revalidate flow:
- During the first 30 seconds, Inertia uses the cached response immediately.
- Between 30 seconds and two minutes, Inertia serves the stale response immediately.
- In the background, Inertia requests fresh data.
- When the response arrives, it merges the updated page into the application.
- After two minutes, the cache expires and navigation makes a regular request.

This pattern is a good fit for dashboards, project lists, and reporting pages. Users get a fast transition without treating old data as permanently correct.
The right cache window depends on the page. A billing screen may need a short stale period. A product catalogue may tolerate several minutes.
You can invalidate prefetched pages after mutations. Cache tags make that easier:
<Link
href="/projects"
prefetch
cache-tags="projects"
>
Projects
</Link>
After creating or updating a project, invalidate the related tag:
import { router } from '@inertiajs/vue3'
router.post('/projects', form, {
invalidateCacheTags: ['projects', 'dashboard'],
})
You can also flush a specific page or the complete prefetch cache:
router.flush('/projects')
router.flushAll()
See the Inertia 3.x prefetching documentation for cache tags, programmatic prefetching, and usePrefetch.
Return Useful Initial Data from Laravel
Prefetching only helps when the server response is shaped well.
Keep the initial page payload focused. Return the records and metadata needed to render the first screen. Avoid loading every chart, audit entry, and secondary relationship in the same request.
Here is a simple Laravel controller:
<?php
namespace App\Http\Controllers;
use App\Models\Project;
use Illuminate\Http\Request;
use Inertia\Inertia;
use Inertia\Response;
class ProjectController extends Controller
{
public function index(Request $request): Response
{
$projects = Project::query()
->where('team_id', $request->user()->current_team_id)
->latest()
->paginate(20)
->withQueryString();
return Inertia::render('Projects/Index', [
'projects' => $projects,
'filters' => [
'search' => $request->string('search')->toString(),
],
]);
}
}
The route remains a normal Laravel route:
use App\Http\Controllers\ProjectController;
use Illuminate\Support\Facades\Route;
Route::get('/projects', [ProjectController::class, 'index'])
->middleware('auth')
->name('projects.index');
This is one of Inertia’s main advantages. You can build a modern Vue SPA without creating a separate JSON API for every screen.
That differs from the workflow used when you build a REST API with PHP. Inertia still sends structured page data, but Laravel remains responsible for routing, authorization, validation, and data composition.
Deferred Props: Keep Heavy Data Out of the First Response
Some data should not block the first render.
Laravel’s Inertia::defer() allows you to resolve expensive props in a follow-up request:
public function show(Project $project): Response
{
return Inertia::render('Projects/Show', [
'project' => $project->load('owner'),
'activity' => Inertia::defer(
fn () => $project->activity()
->latest()
->limit(50)
->get()
),
'metrics' => Inertia::defer(
fn () => $project->metrics()->forLastThirtyDays()
),
]);
}
The page can render the project summary first. Activity and metrics arrive afterward.
On the Vue side, use the Deferred component:
<script setup lang="ts">
import { Deferred } from '@inertiajs/vue3'
defineProps<{
project: {
name: string
description: string | null
}
activity?: Array<{
id: number
message: string
created_at: string
}>
}>()
</script>
<template>
<section>
<h1>{{ project.name }}</h1>
<p>{{ project.description }}</p>
</section>
<Deferred data="activity">
<template #fallback>
<div class="rounded-lg border p-4">
Loading activity…
</div>
</template>
<ul>
<li
v-for="event in activity"
:key="event.id"
>
{{ event.message }}
</li>
</ul>
</Deferred>
</template>
Prefetching and deferred props solve different timing problems.
Prefetching warms the initial page response before navigation. Deferred props keep expensive work out of that response. In practice, prefetch the page shell and lightweight props, then let deferred sections load after the page appears.
Do not assume that prefetching resolves every deferred callback before navigation. Treat deferred data as post-navigation work and design its fallback state accordingly.
Read the deferred props documentation for grouped requests, rescue states, and the reloading slot value.
Keep Deferred Data Fresh with usePoll
Some pages need more than a fast first render. They also need regular updates.
Inertia’s usePoll helper reloads the current page at a defined interval. It stops automatically when the component unmounts.
<script setup lang="ts">
import { usePoll } from '@inertiajs/vue3'
const { polling, stop, start } = usePoll(10_000, {
only: ['activity'],
}, {
mode: 'rest',
})
</script>
<template>
<div class="flex items-center gap-3">
<span v-if="polling">Live updates enabled</span>
<button
v-if="polling"
type="button"
@click="stop"
>
Pause
</button>
<button
v-else
type="button"
@click="start"
>
Resume
</button>
</div>
</template>
The only option is important. It limits each poll to the props that need updating.
For a dashboard, you might poll only stats:
usePoll(15_000, {
only: ['stats'],
preserveScroll: true,
preserveState: true,
}, {
mode: 'rest',
})
The rest mode waits for the previous request to finish before starting the next interval. It prevents slow requests from stacking up.
By default, polling is also throttled when the browser tab is inactive. That is usually the right choice. If a background tab must continue updating, pass keepAlive: true.

A Practical Instant-and-Fresh Pattern
A useful combination for a dashboard looks like this:
<Link
href="/dashboard"
:prefetch="['mount', 'hover']"
:cache-for="['15s', '60s']"
:cache-tags="['dashboard', 'stats']"
>
Dashboard
</Link>
The controller returns the essential page data immediately:
public function index(): Response
{
return Inertia::render('Dashboard', [
'summary' => $this->dashboard->summary(),
'stats' => Inertia::defer(
fn () => $this->dashboard->stats()
),
'recentActivity' => Inertia::defer(
fn () => $this->dashboard->recentActivity()
),
]);
}
The Vue page renders summary first. Deferred handles the secondary sections. usePoll then reloads only the live props:
<script setup lang="ts">
import { Deferred, usePoll } from '@inertiajs/vue3'
usePoll(10_000, {
only: ['stats', 'recentActivity'],
preserveState: true,
preserveScroll: true,
}, {
mode: 'rest',
})
</script>
This gives each feature a clear responsibility:
- Mount prefetch prepares a highly likely destination.
- Hover prefetch covers navigation discovered later.
- SWR caching makes repeat visits feel immediate.
- Deferred props protect the first render from expensive queries.
-
usePollkeeps live sections current after navigation.
Measure Before You Widen the Cache
Prefetching creates extra requests. Use it selectively.
Start with the links users follow most often. Keep mount prefetching for predictable flows. Use hover prefetching for broader navigation. Avoid prefetching pages with expensive, low-probability queries.
Watch query duration, response size, and cache hit behavior. Laravel’s ecosystem gives you several useful PHP developer tools for this work, including Nightwatch for application monitoring and Laravel Cloud for managed infrastructure.
The goal is not to fetch everything early. The goal is to align each request with user intent.
When Laravel prepares the right page data, Inertia warms likely destinations, and Vue renders deferred sections cleanly, a traditional server-driven application can feel remarkably close to instant.