
What is the difference between the Page and App router in Nextjs?

Next.js offers two primary routing, systems: traditional Page Router and the modern App Router, the App Router is now the recommended approach for new projects. Why the shift? The App Router introduces powerful features like Server Components, which allow Next.js to render UI on the server, resulting in faster initial page loads and a more responsive user experience.
Additionally, the App Router provides a more structured way to organize the application using layouts and offers improved performance through advanced caching mechanisms. This article explores the core differences between these two routers, helping to choose the right one for buid next Next.js project.
File Structure & Routing
Both routers determine routing based on the project's file structure, but they do so in different ways.
Page Router
- Uses the
pages
directory. - Each file within the
pages
directory automatically becomes a route based on its name and location. For example,pages/index.tsx
corresponds to the and/pages/about.tsx
becomes/about
. - Every page is a Client Component by default.
Example:
src/ └── pages/ ├── index.tsx ├── about.tsx └── contact/ └── index.tsx
This translates to the following routes:
/
/about
/contact
Dynamic Routes
To create dynamic routes in the Page Router, use square brackets in the file name. For example, pages/posts/[id].tsx
would create a dynamic route for individual posts.
- The
getStaticPaths
function is used to specify which dynamic routes should be pre-rendered at build time. - The
getStaticProps
function fetches data for the specific dynamic route.
App Router
- Uses the
app
directory. - Routing is defined by nested folders.
- A special
page.tsx
file within each folder defines the route's content and makes it publicly accessible. - Every page is a Server Component by default.
- Requires a root layout file (e.g.,
app/layout.tsx
) that applies to all routes.
Example:
src/ └── app/ ├── page.tsx ├── about/ │ └── page.tsx └── contact/ │ └── page.tsx └── layout.tsx
This also results in the routes:
/
/about
/contact
Dynamic Routes
Dynamic routes in the App Router are also created using square brackets in folder names (e.g., app/posts/[id]/page.tsx
).
- You can use the
generateStaticParams
function to pre-render dynamic routes at build time.
Key differences in file conventions:
Feature | App Router | Page Router |
---|---|---|
Default Component | Server Component | Client Component |
Directory | app | pages |
Routing | Folder-based routing with page.ts files | File-based in pages |
Dynamic Routes | [param]/page.ts | [param].ts |
Root Layout | app/layout.tsx | pages/_app.tsx |
404 Page | app/not-found.tsx | pages/404.tsx |
Error Handling | app/route.tsx | pages/_error.tsx |
API Routes | app/*/route.ts | Files in pages/api/* |
API Routes
API Routes in Next.js are a feature that allows developers to create server-side endpoints directly within the application. These endpoints can handle HTTP requests, perform server-side logic, and respond with data, making it easier to build APIs without needing a separate backend service.
Page Router
- Location: API routes in the Page Router are defined within the
pages/api
directory. - Routing Structure: Each file in this directory corresponds to a specific API endpoint. For example, the file
pages/api/hello.ts
would expose an API endpoint at/api/hello
. - Request Handling: API routes in the Page Router utilize a single exported function to handle incoming requests. The function receives the
req
(request) andres
(response) objects, which are similar to those found in Node.js'shttp
module.req
provides information about the incoming request (method, headers, body).res
is used to send the response (data in JSON format, status codes, etc.).
Example (App Router Route Handler):
jsx
import type { NextApiRequest, NextApiResponse } from 'next' export default async function handler( req: NextApiRequest, res: NextApiResponse ) { if (req.method === 'GET') { // Fetch data from a database or external API try { const data = await fetchDataFromDatabase(); res.status(200).json(data) } catch (err) { res.status(500).json({ error: 'Failed to fetch data' }) } } else if (req.method === 'POST') { // Handle form submission or data creation const newData = req.body; // Access posted data await saveDataToDatabase(newData); res.status(201).json({ message: 'Data created successfully' }); } else { // Handle any other HTTP methods res.setHeader('Allow', ['GET', 'POST']); res.status(405).end(`Method ${req.method} Not Allowed`); } }
jsx
import { FormEvent } from 'react' export default function Page() { async function onSubmit(event: FormEvent<HTMLFormElement>) { event.preventDefault() const formData = new FormData(event.currentTarget) const response = await fetch('/api/hello', { method: 'POST', body: formData, }) // Handle response if necessary const data = await response.json() // ... } return ( <form onSubmit={onSubmit}> <input type="text" name="name" /> <button type="submit">Submit</button> </form> ) }
Usage
API routes in the Next.js Page Router are ideal for backend operations, suitable for scenarios where the API needs to be decoupled from the front end, allowing for easier management of backend logic, like simple CRUD operations, form submissions, integrations with third-party APIs, and webhook handling. They are easy to set up and integrate directly with Next.js applications, providing flexibility in handling requests independently from the React rendering process, making them particularly useful for straightforward API needs in applications.
Use Cases Example:
- Content Management: Manage blog posts or other content via CRUD operations.
- Simple CRUD Operations: Building RESTful APIs for Create, Read, Update, and Delete operations on resources.
- Form Handling and Data Submission: Collecting and processing form data submitted by users and validating it on the server
- Data Fetching with Authentication: Creating secure endpoints that require authentication.
- Integration with External APIs: Create a proxy to interact with third-party APIs securely.
- Webhooks: Handling incoming webhooks from third-party services like Stripe or GitHub.
- Data Validation: Validating data sent from the client before saving it in the database.
- User Authentication and Session Management: Creating endpoints to manage user login, logout, registration, token management, and session validation.
- File Upload Handling: Accepting file uploads from the client and saving them on the server or cloud storage.
App Router
Route Handlers are the evolution of API routes in the App Router, which aim to provide a more streamlined and integrated approach to API development within Next.js.
- Location: API route handlers are defined in
route.ts
and located in theapp
directory, typically under a subdirectory namedapi
. e.g.,app/api/users/route.js
creates the endpoint/api/users
. - Routing Structure: The App Router supports nested routing, enabling more organized and modular code. For example, subdirectories within
app/api
can create hierarchical routes. - Request Handling: API route handlers in the App Router use named exports for each HTTP method, allowing for clearer and more structured code. which use the standard
Request
andResponse
objects from the Fetch API. This makes them more aligned with modern web standards and development practices.
Example:
jsx
// app/api/hello/route.ts export async function GET(request: Request) { const { searchParams } = new URL(request.url); const id = searchParams.get('id') try { const response = await fetch(`https://data.com/${id}`) const result = response.json(); return NextResponse.json(result); } catch (err) { return NextResponse.json({ error }); } }
jsx
import { FormEvent } from 'react' export default function Page() { async function onSubmit(event: FormEvent<HTMLFormElement>) { event.preventDefault() const formData = new FormData(event.currentTarget) const response = await fetch('/api/hello', { method: 'POST', body: formData, }) // Handle response if necessary const data = await response.json() // ... } return ( <form onSubmit={onSubmit}> <input type="text" name="name" /> <button type="submit">Submit</button> </form> ) }
Usage:
- Ideal for modern applications leveraging server components, where data fetching and UI rendering can be tightly integrated, enabling developers to build robust server-side functionalities that integrate smoothly with the overall application architecture. It provides flexibility and power of route handlers in managing data, handling user interactions, and connecting to external services while leveraging the benefits of modern React features.
Use Cases Example:
- Dynamic Data Fetching: Building APIs that return dynamic data based on incoming request parameters.
- Form Handling and Submission: Processing form submissions from client components and providing appropriate feedback.
- Integration with External APIs: Creating a proxy for external API calls, allowing for secure access and data manipulation before sending the response to the client.
- Authentication and Authorization: Implementing authentication mechanisms, such as logging in and managing sessions.
- Real-time Data Handling: Providing endpoints that facilitate real-time data updates, such as chat applications or live notifications.
- Custom Error Handling: Implementing custom error handling logic for specific types of requests or data validations.
- Batch Processing: Handling multiple requests or operations in a single API call, which is useful for optimizing network requests.
Key Differences:
Feature | API Routes (Page Router) | Route Handlers (App Router) |
---|---|---|
Location | pages/api | app/route.js |
Mechanism | req , res objects (Node.js-like) | Request , Response objects (Fetch API) |
Advantages | Familiarity, simplicity for basic use cases | Modern approach, streamlined, integrated with Server Components, flexible routing, built-in caching |
Disadvantages | Less integrated with React, more boilerplate, less flexible routing | Newer concept, might require a learning curvences and project requirements. |
Page Rendering & Data Fetching
Page Router
The Page Router offers flexible data fetching options:
Static Site Generation (SSG):
HTML is generated at build time. Ideal for content that doesn't change frequently. Can be used with or without data fetching. getStaticProps
and getStaticPaths
are key functions for SSG.
Example:
Let’s say we have a blog page that needs to display a list of blog posts fetched from an API, we would use getStaticProps
to fetch the list of posts at the build time, and then pass them as props to the blog page component.
jsx
// posts will be populated at build time by getStaticProps() export default function Blog({ posts }) { return ( <ul> {posts.map((post) => ( <li>{post.title}</li> ))} </ul> ) } // This function gets called at build time on server-side. // It won't be called on client-side, so we can even do // direct database queries. export async function getStaticProps() { // Call an external API endpoint to get posts. // You can use any data fetching library const response = await fetch('https://.../posts') const posts = await response.json() // By returning { props: { posts } }, the Blog component // will receive `posts` as a prop at build time return { props: { posts, }, } }
During the build process, Next.js will call getStaticProps
, fetch the list of posts from the API, and generate a pre-rendered HTML page with the blog post. This pre-recorded HTML page can then be served directly to the client, resulting in faster load times and better performance.
We might also want to pre render post/1
, post/2
…etc. at build time, for this case, our page paths depend on external data so we would use getStaticPaths
from a dynamic page (pages/posts/[id].js
) to specify which paths we want to pre-render at the build time.
jsx
function Post({ post }) { // Render post... } export async function getStaticPaths() { const response = await fetch('https://.../posts') const posts = await response.json() // Get the paths we want to pre-render based on posts const paths = posts.map((post) => ({ params: { id: post.id }, })) // We'll pre-render only these paths at build time. return { paths } } // This also gets called at build time export async function getStaticProps({ params }) { // params contains the post `id`. // If the route is like /posts/1, then params.id is 1 const res = await fetch(`https://.../posts/${params.id}`) const post = await res.json() // Pass post data to the page via props return { props: { post } } } export default Post
Server-Side Rendering (SSR):
HTML is generated on each request. Use getServerSideProps
to fetch data on the server. Ensures up-to-date data but can be less performant than SSG.
jsx
import type { InferGetServerSidePropsType, GetServerSideProps } from 'next' export const getServerSideProps = (async () => { // Fetch data from external API const response = await fetch('https://.../data') const data = await response.json() // Pass data to the page via props return { props: { data } } }) export default function Page({ data, }: InferGetServerSidePropsType<typeof getServerSideProps>) { // Render data... }
Client-Side Rendering(CSR):
We can also use Client Side data fetching along with Static Site Generation or Server Side Rendering. For interactive components where data needs to be fetched after the initial page load, the browser downloads a minimal HTML page and JavaScript needed for the page, then JavaScript is downloaded, parsed, and executed to update the DOM and render the page. we can use useEffect
or third party libraries like SWR to fetch data on the Client.
App Router
In the App Router, Next.js uses server side components by default, which allows processing and building parts of the UI components on the server and then sent to the user’s browser, we can also opt into using client components when needed.
Server Components:
By default, components in the App Router are Server Components that allow us to move data fetching to the server, closer to the data source, simplifying data handling. The result can be cached and reused on subsequent requests and across users. This can improve performance and reduce cost by reducing the amount of rendering and data fetching done on each request.
Example:
Fetching data on the server with the fetch
API:
jsx
export default async function Page() { let data = await fetch('https://.../data') let posts = await data.json() return ( <ul> {posts.map((post) => ( <li key={post.id}>{post.title}</li> ))} </ul> ) }
Fetching data on the server with an ORM or database:
jsx
import { db, posts } from '@/lib/db' export default async function Page() { let allPosts = await db.select().from(posts) return ( <ul> {allPosts.map((post) => ( <li key={post.id}>{post.title}</li> ))} </ul> ) }
Static Rendering (Default):
Static Rendering rendered route HTML at build time or in the background after data revalidation. The pre-render result is cached and can be served quickly to the client. The optimization allows us to share the result of rendering work between client and server requests. It’s great for pages where the content doesn’t change often and the result is the same for every user, like the static blog post.
Dynamic Rendering:
If a Server Component uses dynamic functions or fetches uncached data, Next.js switches to dynamic rendering, which generates the HTML on the server in real time for each user request (like navigating to a page). This is useful when a route HTML has data that is personalized or has information that is only known at request time, such as cookies or URL search parameters.
Switching to Dynamic Rendering
During rendering process, if a dynamic function or uncached data request is discovered, Next.js will switch to dynamically rendering the whole route. This table summarizes how dynamic functions and data caching affect whether a route is statically or dynamically rendered:
Dynamic Functions | Data | Route |
---|---|---|
No | Cached | Statically Rendered |
Yes | Cached | Dynamically Rendered |
No | Not Cached | Dynamically Rendered |
Yes | Not Cached | Dynamically Rendered |
Client Components:
As Nextjs uses the server component by default, we will need to add use client
at the top of our client component to declare a boundary between the Server and the Client.
On the Server:
- Next.js uses React to turn Server Components into a special format called the React Server Component Payload (RSC Payload). This payload contains references to the Client Components that will be used on the page.
- Next.js then combines this RSC Payload with JavaScript instructions to generate the HTML for the route on the server.
On the Client:
- The generated HTML is sent to the user's browser, allowing them to see a quick, non-interactive preview of the page immediately.
- The RSC Payload helps connect the Client and Server Component trees, allowing for efficient updates to the DOM.
- Finally, the JavaScript instructions are executed to "hydrate" the Client Components, making the user interface interactive.
Example:
jsx
// app/component/posts.js 'use client' import { useState, useEffect } from 'react' export function Posts() { const [posts, setPosts] = useState(null) useEffect(() => { async function fetchPosts() { let res = await fetch('https://api.vercel.app/blog') let data = await res.json() setPosts(data) } fetchPosts() }, []) if (!posts) return <div>Loading...</div> return ( <ul> {posts.map((post) => ( <li key={post.id}>{post.title}</li> ))} </ul> ) }
Conclusion
The Next.js App Router marks a significant shift in how developers can build web applications with the framework. By embracing Server Components, nested layouts, and a more flexible routing structure, it offers a modern approach that prioritizes performance and developer experience. While the learning curve might be steeper initially due to new concepts like Server Components and Route Handlers, the App Router's potential for improved performance, enhanced flexibility, and streamlined development makes it a compelling choice for new Next.js projects.
However, the Page Router remains a valuable tool, especially for simpler projects or when familiarity is paramount. By understanding the key differences, developers can choose the routing system that best aligns with their goals and embark on building high-quality Next.js applications.