Next.js offers a robust data-fetching system that integrates seamlessly with its server and client components. In this blog, we’ll explore different ways to fetch data in Next.js, manage loading and error states, set up a mock API with JSON Server, control caching, revalidate data, and use client-side fetching for dynamic features.
1. Fetching Data with Server Components
By default, components in Next.js are Server Components, meaning they fetch data on the server without impacting the bundle size sent to the client. This enhances SEO and improves performance since data fetching and rendering occur server-side.
// app/users/page.tsx export default async function UsersPage() { const res = await fetch('https://api.example.com/users'); const users = await res.json(); return ( {users.map((user) => ( {user.name} ))} ); }
In this example, data is fetched on the server, and the rendered HTML is sent to the client, ensuring optimal performance.
2. Loading and Error States
Next.js provides built-in mechanisms for handling loading and error states. For Server Components, you can use Suspense boundaries to display fallback content while data is being fetched.
Loading State
// app/users/loading.tsx export default function Loading() { return Loading...; }
Error State
// app/users/error.tsx export default function Error() { return Error loading data...; }
Next.js automatically uses these components when there's a delay in fetching data or if an error occurs.
3. Setting Up JSON Server for Mock APIs
JSON Server is a quick way to create a mock REST API for testing your Next.js application.
Step 1: Install JSON Server
npm install -g json-server
Step 2: Create a db.json File
{ "users": [ { "id": 1, "name": "John Doe" }, { "id": 2, "name": "Jane Doe" } ] }
Step 3: Run the JSON Server
json-server --watch db.json --port 3001
You can now fetch data from http://localhost:3001/users in your Next.js app.
4. Caching Data
Next.js automatically caches fetch responses in Server Components to optimize performance. Cached data can be revalidated periodically to ensure freshness.
export default async function CachedUsersPage() { const res = await fetch('https://api.example.com/users', { next: { revalidate: 60 }, }); const users = await res.json(); return ( {users.map((user) => ( {user.name} ))} ); }
In this example, data is cached and revalidated every 60 seconds, reducing unnecessary API calls.
5. Opting Out of Caching
If you require fresh data for every request, you can disable caching by setting the fetch option cache to "no-store".
export default async function NoCachePage() { const res = await fetch('https://api.example.com/users', { cache: 'no-store', }); const users = await res.json(); return ( {users.map((user) => ( {user.name} ))} ); }
This ensures the data is fetched fresh on every request.
6. Time-based Data Revalidation
Revalidation allows you to periodically update cached data. By setting the revalidate option in the fetch method, you can control the interval for data updates.
export default async function RevalidatePage() { const res = await fetch('https://api.example.com/users', { next: { revalidate: 30 }, }); const users = await res.json(); return ( {users.map((user) => ( {user.name} ))} ); }
This setup balances fresh content with minimized server load.
7. Client-side Data Fetching
For interactive or real-time features, client-side fetching is essential. Use the useEffect hook in Client Components to fetch data dynamically.
"use client"; import { useState, useEffect } from 'react'; export default function ClientFetchComponent() { const [data, setData] = useState(null); const [loading, setLoading] = useState(true); useEffect(() => { async function fetchData() { const res = await fetch('https://api.example.com/users'); const users = await res.json(); setData(users); setLoading(false); } fetchData(); }, []); if (loading) return Loading...; return ( {data.map((user) => ( {user.name} ))} ); }
Client-side fetching is ideal for dynamic pages, charts, or real-time data updates.
Conclusion
Next.js provides versatile data-fetching options to suit different application needs, from server-side caching and revalidation to dynamic client-side fetching. Mastering these techniques allows you to build high-performance applications that deliver fresh and optimized content to users.