Data fetching in Next.js 15 introduces powerful techniques to handle data securely and efficiently, including caching and revalidating. Let’s break it down into best practices and practical steps.
Fetching data on the server provides key advantages:
Direct access to data sources (e.g., databases) without intermediaries.
Improved security, as you avoid exposing API keys or tokens in the client.
Reduced latency: Server rendering fetches and renders data in the same environment, avoiding unnecessary round trips.
Better user experience: No need for loading spinners during the initial render.
Client Fetching: Browser → API Route → Database → API Route → Browser (adds latency).
Server Fetching: Server → Database → Server → Browser (faster, more efficient).
By default, Next.js 15 renders pages on the server, so take advantage of this by fetching data server-side during the initial render.
Fetch Data Where It’s Needed:
Avoid fetching global data in top-level components unless necessary. Fetching data closer to the components that use it keeps the application modular and avoids unnecessary re-fetching.
Caching in Fetch:
Next.js 15 enhances the built-in fetch
function to utilize caching effectively, making subsequent requests faster and seamless.
Streaming allows React and Next.js 15 to:
Render part of the page immediately (e.g., navigation, layout).
Defer rendering of data-dependent components while they fetch required data.
This approach improves user experience by ensuring visible progress and interactive elements even when data is still loading.
getServerSideProps
Fetch data on the server to pre-render pages with the required content. Example:
export async function getServerSideProps(context) {
const res = await fetch('https://api.example.com/data');
const data = await res.json();
return {
props: { data },
};
}
export default function Page({ data }) {
return <div>Data: {JSON.stringify(data)}</div>;
}
getStaticProps
For content that rarely changes, use static generation:
export async function getStaticProps() {
const res = await fetch('https://api.example.com/static-data');
const data = await res.json();
return {
props: { data },
};
}
export default function StaticPage({ data }) {
return <div>Static Data: {JSON.stringify(data)}</div>;
}
useEffect
Use this for interactive features or when data is fetched post-initial render:
import { useEffect, useState } from 'react';
export default function ClientPage() {
const [data, setData] = useState(null);
useEffect(() => {
fetch('https://api.example.com/data')
.then((res) => res.json())
.then((data) => setData(data));
}, []);
return <div>Client Data: {data ? JSON.stringify(data) : 'Loading...'}</div>;
}
Enable streaming in React Server Components for complex layouts:
Render layout immediately.
Defer rendering data-dependent components with Suspense
.
import React, { Suspense } from 'react';
function DataComponent() {
// Simulate fetching data
const data = fetchData();
return <div>Fetched Data: {data}</div>;
}
export default function Layout() {
return (
<div>
<h1>Streaming Layout</h1>
<Suspense fallback={<div>Loading Data...</div>}>
<DataComponent />
</Suspense>
</div>
);
}
Built-in caching for fetch
ensures efficient API calls.
Streaming support enhances rendering for modern user experiences.
Server-first approach aligns perfectly with secure, fast, and scalable applications.
By following these practices and examples, you can leverage Next.js 15 to create performant, dynamic, and user-friendly applications.