Data Fetching
A page in the Pages Router gets data from a function exported next to it, which
runs only on the server: getStaticProps at build time (and on ISR
revalidation), getStaticPaths to list the dynamic routes to prebuild,
getServerSideProps on every request. The returned props are serialized
to JSON and passed to the page. Data that changes after load is fetched on the
client with SWR or TanStack Query.
Applies to: Next.js 16.4 (Pages Router).
Details
| Function | Runs | Returns |
|---|---|---|
getStaticProps | At build time; again in the background with revalidate | { props, revalidate? }, { notFound }, { redirect } |
getStaticPaths | At build time, for a dynamic route with getStaticProps | { paths, fallback } |
getServerSideProps | On every request | { props }, { notFound }, { redirect } |
getInitialProps | On the server for the first load, then in the browser on client navigation | Props. Legacy: not recommended |
- They work only in files under
pages/, not in components. propsmust be JSON-serializable: noDateobjects, functions orundefinedvalues.getServerSidePropsgetscontextwithparams,query,req,res(cookies, headers).
fallback in getStaticPaths
| Value | A path not returned at build time |
|---|---|
false | 404 |
true | Rendered on first request; meanwhile the page shows a state with router.isFallback |
'blocking' | Rendered on first request like SSR, then cached |
ISR and On-Demand Revalidation
revalidate: 60: after 60 seconds the next request still gets the old page and starts a regeneration in the background (stale-while-revalidate).- On-demand: an API Route calls
await res.revalidate('/posts/1'), for example from a CMS webhook, and the page is regenerated at once. - When ISR fits and when it does not is in Rendering Strategies.
Example
pages/posts/[id].js
export async function getStaticPaths() {
const posts = await fetch('https://api.example.com/posts').then((r) =>
r.json()
);
return {
paths: posts.map((post) => ({ params: { id: String(post.id) } })),
fallback: 'blocking', // other ids are rendered on first request
};
}
export async function getStaticProps({ params }) {
const res = await fetch(`https://api.example.com/posts/${params.id}`);
if (!res.ok) return { notFound: true };
return { props: { post: await res.json() }, revalidate: 60 };
}
export default function Post({ post }) {
return <h1>{post.title}</h1>;
}
On-demand revalidation and client-side fetching
pages/api/revalidate.js
export default async function handler(req, res) {
if (req.query.secret !== process.env.REVALIDATE_SECRET) {
return res.status(401).json({ message: 'Invalid token' });
}
await res.revalidate(`/posts/${req.query.id}`);
return res.json({ revalidated: true });
}
components/Profile.jsx
import { useQuery } from '@tanstack/react-query';
export function Profile() {
const { data, error, isPending } = useQuery({
queryKey: ['profile'],
queryFn: () => fetch('/api/profile').then((r) => r.json()),
});
if (isPending) return <p>Loading...</p>;
if (error) return <p>Error: {error.message}</p>;
return <p>{data.name}</p>;
}
Follow-up Questions
- Can
getStaticPropsread a database directly? Yes: it runs only on the server, and its code is removed from the client bundle. - Why is
getInitialPropsdiscouraged? It runs in the browser on navigation, so its code and dependencies ship to the client, and a customgetInitialPropsin_appturns off automatic static optimization for every page withoutgetStaticProps. - What does the App Router use instead?
asyncServer Components,generateStaticParamsinstead ofgetStaticPaths, and'use cache'withcacheLifeinstead ofrevalidate(fetchnext.revalidateorexport const revalidatein the previous model). - What happens with
getServerSidePropson client navigation? It still runs on the server; the client fetches its result as JSON and renders the page without a reload.
Common Mistakes
- Using
fallback: truewithout handlingrouter.isFallback: the page renders with empty props. - Calling your own API Route from
getStaticPropsinstead of importing the function.