3. Loading UI , Data fetching - Next.JS

Loading UI with React suspense component.
React Suspense :
Suspense
is a React component that lets you "wait" for some part of the component tree to load or resolve, and while it's doing that, it renders a fallback UI (like a loader).File Structure :
app/ ├── layout.js # Root layout for the entire app ├── loading.js # Global fallback loading UI ├── page.js # Your homepage or main route ├── globals.css # Global styles
In loading.js -
// app/loading.js
export default function Loading() {
return <h1 className="text-5xl">Loading ....</h1>;
}
//This will be used as the fallback UI when components are streaming or waiting for data/code.
In Layout .js -
// app/layout.js
import { Suspense } from "react";
import Loading from "./loading";
export default function RootLayout({ children }) {
return (
<html lang="en">
<body>
<Suspense fallback={<Loading />}>
{children}
</Suspense>
</body>
</html>
);
}
What This Does:
Wraps your entire app in a
Suspense
boundary.Whenever a child component under this layout is:
Async
Lazy-loaded
Server-rendered with delay then it will temporarily render
<Loading />
instead, until the content is ready.
Server side data fetching
Data is fetched on the server (Node.js) before the page is sent to the browser.
💡 When to Use:
SEO is important (search engines should see content)
Sensitive data (tokens, DB calls)
You want fast initial page loads
You use a CMS or backend API
// /app/server-example/page.js
async function fetchUsers() {
const res = await fetch('https://dummyjson.com/users');
const data = await res.json();
return data.users;
}
export default async function ServerPage() {
const users = await fetchUsers(); // ✅ Server fetch
return (
<div>
<h1>Server-Side Rendered User List</h1>
<ul>
{users.map((user) => (
<li key={user.id}>{user.firstName}</li> // this id will extract using params in userDetail page
))}
</ul>
</div>
);
}
Behind the Scenes:
This runs on the server (not browser)
HTML is pre-rendered with full data
Delivered to browser
Blazing fast
✅ Pros:
✅ Great for SEO
✅ Fast initial load
✅ Can use secure tokens (not exposed to browser)
✅ Full SSR (server-side rendering)
❌ Cons:
❌ Not interactive on fetch failure (page breaks)
❌ Can’t use browser-only features like
localStorage
,window
Client side data fetching
The page is sent without data, and then data is fetched from the browser (client) using
useEffect()
or libraries like SWR, Axios, Fetch, etc.💡 When to Use:
SEO is not critical
Data changes frequently
You need to show spinners, loaders, etc.
Logged-in user dashboards (browser-only)
// /app/client-example/page.js
'use client';
import { useEffect, useState } from "react";
export default function ClientPage() {
const [users, setUsers] = useState([]);
const [loading, setLoading] = useState(true);
useEffect(() => {
fetch('https://dummyjson.com/users')
.then(res => res.json())
.then(data => {
setUsers(data.users);
setLoading(false);
});
}, []);
return (
<div>
<h1>Client-Side Rendered Users</h1>
{loading ? (
<p>Loading...</p>
) : (
<ul>
{users.map(user => (
<li key={user.id}>{user.firstName}</li>
))}
</ul>
)}
</div>
);
}
⚙️ Behind the Scenes:
Loads blank page initially
Then fetches data via browser
Shows loading indicator
✅ Pros:
✅ Can interact with browser (
window
,localStorage
)✅ Good for dynamic dashboards
✅ Can use spinners/loaders nicely
❌ Cons:
❌ Bad for SEO
❌ Slow first load (data comes after)
❌ No SSR
Data fetching with SWR(Client Side, Better UX)
'use client';
import useSWR from 'swr';
const fetcher = (...args) => fetch(...args).then(res => res.json());
export default function WithSWR() {
const { data, error, isLoading } = useSWR('https://dummyjson.com/users', fetcher);
if (isLoading) return <p>Loading...</p>;
if (error) return <p>Error fetching data!</p>;
return (
<ul>
{data.users.map((u) => (
<li key={u.id}>{u.firstName}</li>
))}
</ul>
);
}
Subscribe to my newsletter
Read articles from Ayush Rajput directly inside your inbox. Subscribe to the newsletter, and don't miss out.
Written by
