Skip to main content

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​

FunctionRunsReturns
getStaticPropsAt build time; again in the background with revalidate{ props, revalidate? }, { notFound }, { redirect }
getStaticPathsAt build time, for a dynamic route with getStaticProps{ paths, fallback }
getServerSidePropsOn every request{ props }, { notFound }, { redirect }
getInitialPropsOn the server for the first load, then in the browser on client navigationProps. Legacy: not recommended
  • They work only in files under pages/, not in components.
  • props must be JSON-serializable: no Date objects, functions or undefined values.
  • getServerSideProps gets context with params, query, req, res (cookies, headers).

fallback in getStaticPaths​

ValueA path not returned at build time
false404
trueRendered 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 getStaticProps read a database directly? Yes: it runs only on the server, and its code is removed from the client bundle.
  • Why is getInitialProps discouraged? It runs in the browser on navigation, so its code and dependencies ship to the client, and a custom getInitialProps in _app turns off automatic static optimization for every page without getStaticProps.
  • What does the App Router use instead? async Server Components, generateStaticParams instead of getStaticPaths, and 'use cache' with cacheLife instead of revalidate (fetch next.revalidate or export const revalidate in the previous model).
  • What happens with getServerSideProps on 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: true without handling router.isFallback: the page renders with empty props.
  • Calling your own API Route from getStaticProps instead of importing the function.

See Also​