Skip to main content
Engineering Blog
Engineering Blog2026-07-18

Next.js Evolution: The Paradigm Shift from Pages Router to App Router

Quan Van|Technical Essay|4 min read

The transition from the Pages Router to the App Router (introduced in Next.js 13 and standardized in versions 14 and 15) is not merely a directory name change. It is an architectural revolution that completely redefines how we build React applications.

While the Pages Router operated on a "Render the entire page" mental model, the App Router shifts the paradigm to "Render individual components."

Below are the four core differences that reshape how you write and structure your code.


1. Architectural Foundation: React Server Components (RSC)

  • Pages Router: All components are Client Components by default. Even if the HTML is generated on the server (via SSR/SSG), the JavaScript payload for the entire page is bundled and shipped to the browser to execute Hydration.
  • App Router: Everything is a Server Component by default. As discussed in previous architectural breakdowns, the JavaScript for Server Components is never sent to the browser (0 bytes JS). You only explicitly opt-in to sending JS for interactive components by adding the "use client" directive. This drastically reduces the page payload and accelerates loading times.

2. File Naming Conventions and Routing

The mechanism for defining a new URL route has fundamentally changed.

  • Pages Router: Any .js file placed inside the pages/ directory automatically maps to a public URL route (excluding internal files prefixed with _).
    • pages/about.js/about
  • App Router: You create nested folders to define the URL path, and you must use a designated file named page.js within that folder to render the UI.
    • app/about/page.js/about

The Advantage: You can safely colocate CSS files, images, or child components (like Button.js or api-helpers.js) directly alongside page.js within the same folder without the risk of them being accidentally exposed as public URL routes.


3. Nested Layouts and State Preservation

This is arguably the most valuable UI enhancement introduced by the App Router.

  • Pages Router: Managing layouts was notoriously cumbersome. You often had to configure global states in _app.js or manually wrap individual pages with a custom <Layout> component. Upon navigation, the Layout would typically re-render from scratch, destroying localized state (e.g., scroll positions would reset, or playing videos would stop).
  • App Router: Natively supports Nested Layouts via the layout.js file. When you navigate between child pages within a Layout, the outer Layout wrapper does not re-render, flawlessly preserving its state. The framework also provides dedicated file conventions like loading.js (rendered instantly while data fetches in the background) and error.js (isolating crashes without taking down the entire application).

4. Data Fetching Paradigms

  • Pages Router: Relied heavily on specialized, page-level exported functions like getServerSideProps (for SSR) or getStaticProps (for SSG). You could not invoke these functions deep within a nested child component, forcing engineers into tedious "prop drilling" to pass data from the top-level page down to the leaf components.
  • App Router: Completely abandons those specialized functions. Leveraging the immense power of Server Components, you can transform any component into an async function and use native await fetch() to query the Database or API directly within the component body.

Next.js has extended the native Web fetch API, granting you granular, highly powerful cache controls:

  • fetch(url, { cache: 'no-store' }) bypasses the cache entirely (replacing SSR).
  • fetch(url, { next: { revalidate: 60 } }) executes Incremental Static Regeneration (ISR) to automatically update stale data every 60 seconds.

5. Quick Comparison Matrix

Feature Pages Router (Legacy) App Router (Modern)
Root Directory /pages /app
Route Creation page-name.js page.js (inside a named folder)
Default Component Client Component (with SSR) Server Component (0 bytes JS to client)
Layouts Complex, prone to unwanted re-rendering Simple (layout.js), preserves UI state
Data Fetching getServerSideProps, getStaticProps Native async/await and fetch() anywhere
Pending UI States Manually managed via state (isLoading) Native loading.js file (powered by React Suspense)

[!NOTE] Engineering Insight: If you are currently maintaining a legacy project, there is no immediate need to rush a complete rewrite, as Next.js continues to support the Pages Router. However, if you are initializing a greenfield project today, choose the App Router with 100% certainty. It is the definitive standard of the future, and the React/Next.js ecosystems are funneling all of their resources into optimizing it.

End of article

Want to link PomaiDB into your project?

Read the Engineering Manual