The Pages Router is Next.js's original routing system — still supported, still asked about in interviews, and still the mental model many existing production codebases run on. This topic is deliberately brief: interview fluency and a clean migration map to App Router equivalents, not a new system to build in.
1. In the Pages Router, what determines a route's URL?
Interview Questions
2 questions
Cheatsheet
Download-ready reference
Cheatsheet
Pages Router: file-system routing via pages/ — still fully supported,
App Router is the recommended default for NEW projects.
getStaticProps → build time (or ISR revalidate)
≈ async Server Component + "use cache"/cacheLife
getStaticPaths → lists paths to prerender at build time
≈ generateStaticParams
getServerSideProps → EVERY request, server-side, no caching
≈ Server Component reading runtime data (no cache directive)
pages/api/*.js → custom API endpoint
≈ app/api/*/route.ts (Route Handlers)
getInitialProps (legacy, older than the above) — runs on BOTH
server (initial load) AND client (subsequent nav). Rarely used
directly in modern Pages Router code.
revalidate: N in getStaticProps = ISR, stale-while-revalidate —
conceptually identical to the App Router's cacheLife/revalidateTag.
fallback: false/true/'blocking' in getStaticPaths controls what
happens for paths NOT explicitly listed (404 / generate-on-demand).
Client-side fetching (SWR, React Query) works the SAME way in
either router — it's router-agnostic.
The beginner framing: before the App Router, Next.js routed based on files inside a pages/ directory — each file directly became a route, and data fetching was done through a small set of exported functions rather than await-ing inside the component itself.
The precise mental model: the Pages Router still exists and is fully supported, but the App Router (covered throughout the rest of this domain) is the current, recommended default for new projects. This topic exists because production codebases and interview questions both still assume Pages Router fluency — the goal here is recognition and a clean mental map to App Router equivalents, not depth for new development.
// pages/blog/[slug].js — the file itself IS the route: /blog/:slugexport default function BlogPost({ post }) { return <h1>{post.title}</h1>;}export async function getStaticPaths() { const posts = await getPosts(); return { paths: posts.map((p) => ({ params: { slug: p.slug } })), fallback: false };}export async function getStaticProps({ params }) { const post = await getPost(params.slug); return { props: { post }, revalidate: 3600 }; // ISR: revalidate every hour}
The three data-fetching functions, and their App Router equivalents#
Pages Router
When it ran
App Router equivalent
getStaticProps
Build time (or on ISR revalidation)
An async Server Component await-ing data directly, with "use cache"/cacheLife for the caching behavior
getStaticPaths
Build time, alongside getStaticProps, to list dynamic paths to prerender
The conceptual shift: Pages Router separated "the component" from "the function that fetches its props," with an explicit function name signaling the rendering strategy (getStaticProps = static, getServerSideProps = dynamic). App Router collapses this — a single async Server Component fetches its own data directly, and whether that data is cached (and for how long) is a property of the data fetching itself ("use cache", cacheLife), not a separate, page-level exported function.
Client-side data fetching: getInitialProps (legacy) vs. hooks#
getInitialProps (older than getStaticProps/getServerSideProps, runs on both server and client) is now rarely used directly — most Pages Router codebases favor getStaticProps/getServerSideProps for initial data and a client-side library (SWR, React Query) for anything fetched after mount, the same client-fetching pattern still valid in the App Router today.
Predict what happens before checking the solution.
export async function getStaticProps() { const data = await fetchHomepageContent(); return { props: { data }, revalidate: 60 };}
A deploy ships new homepage content. A user requests the homepage 30 seconds after the deploy. Do they see the new content immediately?
Solution
Not necessarily. revalidate: 60 means Incremental Static Regeneration (ISR) — the statically generated page is served as-is until 60 seconds have passed since it was last generated, at which point the next request triggers a background regeneration (stale-while-revalidate, conceptually identical to the App Router's revalidateTag/cacheLife behavior covered in Caching). A request 30 seconds post-deploy, within that window, still gets the previously generated (now-stale relative to the new deploy, unless the deploy itself triggered a fresh build) version.
Model the decision logic mapping a Pages Router data-fetching choice to its App Router equivalent:
function migrateToAppRouter(pagesRouterFunction) { const map = { getStaticProps: "async Server Component + 'use cache' (+ cacheLife for revalidate timing)", getStaticPaths: "generateStaticParams", getServerSideProps: "async Server Component reading runtime data (cookies/headers) — dynamic by default, no caching directive needed", "pages/api/*.js": "app/api/*/route.ts (Route Handlers)", }; return map[pagesRouterFunction] ?? "no direct equivalent — reconsider the pattern";}migrateToAppRouter(
This is the exact translation table worth having memorized for interviews — Pages Router's explicit function names map cleanly onto App Router's implicit, directive-driven equivalents.
The underlying shift from "a separate function declares the rendering strategy" to "the component itself, plus a directive, declares it" mirrors the same shift from imperative configuration to declarative co-location that runs through the rest of the App Router — compare to how Caching's "use cache" directive lives directly next to the code it affects, rather than in a separately-named, separately-exported function.
The concepts map cleanly (see the table above), but the code doesn't — getServerSideProps's object-returning signature and getStaticPaths's { paths, fallback } shape have no direct syntactic equivalent in an async Server Component; the underlying behavior maps, not the exact API shape.
2. Not recognizing pages/api/*.js as the direct ancestor of Route Handlers#
Confusing Pages Router API routes with something entirely unrelated to App Router's Route Handlers — they solve the identical problem (a custom request handler for a given path), just with a different file convention and Request/Response API.
3. Treating the Pages Router as fully deprecated or unsupported#
It remains fully supported for existing projects — the App Router is the recommended default for new projects, not a forced migration for everything already running on Pages Router.
For new projects, default to the App Router — it's the current recommendation and where new Next.js features land first.
For existing Pages Router codebases, know the migration map (this topic's table) rather than assuming a rewrite is always warranted — many production apps run Pages Router indefinitely without issue.
In interviews, be fluent in both the Pages Router terms AND their App Router equivalents — this is one of the most commonly tested "do you actually know Next.js across versions" question types.
ISR (revalidate in getStaticProps) and the App Router's tag/time-based revalidation (see Caching) solve the identical problem — stale-while-revalidate for content that changes occasionally — just with different APIs.
getServerSideProps runs on every request with no caching by default, identical in spirit to an App Router Server Component that reads runtime data without any caching directive — both are the "always fresh, always request-time" end of the spectrum.
generateStaticParams
getServerSideProps
Every request, server-side
A Server Component that simply doesn't opt into caching — dynamic by default when it reads runtime data (cookies(), headers(), uncached fetches)