Home Quizzes Leaderboard Competitions Learn Hire Us
About Contact
Log In Sign Up
Learn Next.js Performance --- Caching, Suspense & Streaming

Performance --- Caching, Suspense & Streaming

⏱ 18 min read read
Next.js Caching Layers (from fastest to slowest):

1. Memory Cache --- per-request dedup (same fetch URL called twice →
one request)

2. Data Cache --- persistent across requests, stored on server
(force-cache)

3. Full Route Cache --- statically rendered pages cached on disk
(SSG/ISR)

4. Router Cache --- client-side, caches visited pages in browser
memory

loading.tsx --- Instant Loading UI:

Create loading.tsx next to page.tsx. It shows instantly while the page
fetches, using Suspense under the hood.

// app/dashboard/loading.tsx

export default function DashboardLoading() {

return (

<div className="animate-pulse">

<div className="h-8 bg-gray-200 rounded w-48 mb-4" />

<div className="h-4 bg-gray-200 rounded w-full mb-2" />

<div className="h-4 bg-gray-200 rounded w-3/4" />

</div>

);

}

Suspense --- Stream Individual Components:

import { Suspense } from 'react';

export default function Page() {

return (

<div>

<h1>Dashboard</h1> {/* renders instantly */}

<Suspense fallback={<StatsSkeletion />}>

<Stats /> {/* streams in when ready */}

</Suspense>

<Suspense fallback={<TableSkeleton />}>

<StudentsTable /> {/* streams in independently */}

</Suspense>

</div>

);

}

On-Demand Revalidation:

// Revalidate by path

import { revalidatePath } from 'next/cache';

revalidatePath('/students');

// Revalidate by tag

import { revalidateTag } from 'next/cache';

revalidateTag('students');

// Tag a fetch

fetch(url, { next: { tags: ['students'] } })

Use Suspense boundaries to parallelize slow fetches and stream
results.

Skeleton UIs (loading.tsx) make pages feel faster than spinners.

unstable_cache() wraps any async function (not just fetch) with
caching.

Server Actions automatically call revalidatePath/revalidateTag after
mutations.
Code Example
// app/dashboard/page.tsx --- streamed layout

import { Suspense } from 'react';

// Slow components that fetch independently

async function StatsPanel() {

const stats = await fetch('/api/stats', { next: { revalidate: 60 }
}).then(r => r.json());

return (

<div className="grid grid-cols-3 gap-4">

<StatCard label="Students" value={stats.total} />

<StatCard label="Avg Grade" value={stats.avg} />

<StatCard label="Passing" value={stats.passing} />

</div>

);

}

async function RecentActivity() {

await new Promise(r => setTimeout(r, 2000)); // simulate slow query

const activity = await fetch('/api/activity').then(r => r.json());

return <ActivityFeed items={activity} />;

}

function Skeleton({ className }: { className?: string }) {

return <div className={`animate-pulse bg-gray-200 rounded
${className}`} />;

}

function StatsSkeleton() {

return <div className="grid grid-cols-3
gap-4">{[...Array(3)].map((\_, i) => <Skeleton key={i}
className="h-24" />)}</div>;

}

export default function DashboardPage() {

return (

<main className="p-6 space-y-6">

<h1 className="text-2xl font-bold">Dashboard</h1>

{/* StatsPanel and RecentActivity stream in independently */}

<Suspense fallback={<StatsSkeleton />}>

<StatsPanel />

</Suspense>

<Suspense fallback={<Skeleton className="h-64" />}>

<RecentActivity />

</Suspense>

</main>

);

}

// app/dashboard/loading.tsx --- instant skeleton while page.tsx
loads

export default function Loading() {

return (

<main className="p-6 space-y-6 animate-pulse">

<div className="h-8 bg-gray-200 rounded w-48" />

<div className="grid grid-cols-3 gap-4">

{[...Array(3)].map((\_, i) => <div key={i} className="h-24
bg-gray-200 rounded" />)}

</div>

</main>

);

}
← State Management --- Zustand & React Que Testing --- Jest & Playwright →

Log in to track your progress and earn badges as you complete lessons.

Log In to Track Progress