Next.js is a popular React framework that enables you to build fast, scalable, and SEO-friendly web applications. It provides a full-stack development environment where you can build both the frontend and backend of an application within a single project.
Server-Side Rendering (SSR): Next.js allows pages to be rendered on the server instead of the client, which improves performance and SEO. Server-side rendering means that the content is generated on the server for each request and sent to the browser as a fully rendered HTML page.
Static Site Generation (SSG): With SSG, Next.js can generate HTML pages at build time, which means these pages are pre-rendered and served as static files. This makes the app incredibly fast and scalable.
Hybrid Rendering: Next.js allows you to combine both SSR and SSG in the same project. You can choose to render some pages statically while others can be rendered on-demand on the server.
File-Based Routing:
Instead of manually configuring routes, Next.js creates routes based on the file structure inside the pages
directory. This eliminates the need for third-party routing libraries and simplifies navigation between pages.
API Routes:
Next.js allows you to create API endpoints directly in the project by adding JavaScript/TypeScript files in the pages/api
folder. These routes can handle backend logic and database operations without needing an external server like Express.
Image Optimization: Built-in support for automatic image optimization helps reduce image sizes and improve loading times. Images are optimized on-demand, and formats like WebP are used where possible.
Automatic Code Splitting: Next.js automatically splits your code into smaller bundles, so only the code necessary for the current page is loaded, improving performance.
Fast Refresh: Next.js includes a fast refresh feature for a better development experience, allowing you to see changes instantly as you modify your code without losing your component state.
SEO-Friendly: Since Next.js supports SSR and SSG, it is much easier to optimize pages for search engines, unlike client-side-only React apps.
TypeScript Support: Next.js has first-class TypeScript support, allowing you to build type-safe applications seamlessly.
Ensure Node.js is installed: Make sure you have Node.js installed on your machine. You can check by running:
node -v
Create a Next.js app:
You can use the create-next-app
command to set up a Next.js project with everything you need pre-configured. Run the following command in your terminal:
npx create-next-app@latest
You can also specify a project name:
npx create-next-app@latest my-next-app
Navigate to the project directory: Once the installation is complete, move into the project directory:
cd my-next-app
Run the development server: Start the development server by running:
npm run dev
Your Next.js app will now be accessible at http://localhost:3000
.
When you start a Next.js application, the framework follows a structured control flow to render the initial page (like Home()
). To understand how it works behind the scenes, let’s break it down step by step:
In Next.js, pages are built around the concept of file-based routing. Each file in the pages
directory corresponds to a route. For example:
pages/index.js
→ the home page (/
)pages/about.js
→ the about page (/about
)When you create a Home
component in pages/index.js
, Next.js automatically registers it as the root route (/
).
Example pages/index.js
:
export default function Home() {
return <h1>Welcome to the Home Page</h1>;
}
When a user accesses the homepage (e.g., http://localhost:3000/
), Next.js performs the following tasks:
npm run dev
):
Home()
component.npm run build
and npm start
):
Home()
ComponentIn many cases, the initial rendering of the Home()
component is done statically at build time. Here’s what happens:
Home()
function at build time (for static pages)..next
directory.In server-side rendering mode, Next.js renders the Home()
component at runtime for each request.
Home()
function on the server to generate the HTML page on the fly.Regardless of whether the page is statically generated or server-rendered, Next.js sends the fully rendered HTML to the browser. Once the HTML is loaded, hydration happens:
Home()
component is sent to the browser.If your Home()
component needs to fetch data, Next.js provides methods to do this:
getStaticProps
(SSG):
If you export a getStaticProps
function from your page, Next.js runs this function at build time and passes the returned data as props to the Home()
component.
export async function getStaticProps() {
const data = await fetchData(); // Fetch data from an API
return { props: { data } }; // Pass data as props to Home()
}
export default function Home({ data }) {
return <div>{data}</div>;
}
getServerSideProps
(SSR):
If you export a getServerSideProps
function, Next.js runs it at runtime, on each request. The returned data is passed to the Home()
component, and SSR is used to render the page on the server before sending it to the client.
export async function getServerSideProps() {
const data = await fetchData();
return { props: { data } };
}
export default function Home({ data }) {
return <div>{data}</div>;
}
If you use an API route within your Next.js app, it is handled similarly to traditional REST APIs. When the Home()
component makes a call to an API route (e.g., /api/hello
), Next.js intercepts the request, processes the backend logic within the pages/api/
file, and sends the response back.
Webpack/Babel Compilation: When the development server is running or when you build for production, Next.js uses Webpack to bundle your app and Babel to transpile your modern JavaScript/TypeScript code.
React Rendering:
Next.js extends React’s core rendering capabilities (both on the client and server). It uses React’s renderToString()
method for SSR, generating the HTML content that will be sent to the client.
Routing & Middleware:
Next.js has its own router built into the framework. It maps URLs to files in the pages/
directory and handles navigation between pages without needing third-party routing libraries. For SSR, Next.js uses a custom server and middleware to handle requests.
/
→ pages/index.js
).getStaticProps
or getServerSideProps
is used, data is fetched and passed to the component.In Next.js, you can use React Server Components and Client Components to optimize your app by determining which parts of your UI are rendered on the server and which are rendered on the client.
useEffect
or relying on APIs.// pages/index.js (Server Component by default)
export default function Home() {
const data = fetchSomeData(); // Server-side data fetching
return (
<div>
<h1>Welcome to the Home Page</h1>
<p>Data from server: {data}</p>
</div>
);
}
Home
component fetches data and renders it server-side. The client receives static HTML without JavaScript.useState
, useEffect
, and useContext
in Client Components to manage the UI and handle side effects.To explicitly declare a component as a Client Component in Next.js 13+ using the App Router, you need to add a special 'use client'
directive at the top of the component file.
// components/InteractiveComponent.js
"use client";
import { useState } from "react";
export default function InteractiveComponent() {
const [count, setCount] = useState(0);
return (
<div>
<p>Client-side counter: {count}</p>
<button onClick={() => setCount(count + 1)}>Increment</button>
</div>
);
}
InteractiveComponent
is a Client Component that updates the count when the button is clicked.// pages/index.js (Server Component)
import InteractiveComponent from "../components/InteractiveComponent";
export default function Home() {
const data = fetchSomeData(); // Server-side fetching
return (
<div>
<h1>Welcome to the Home Page</h1>
<p>Server-rendered data: {data}</p>
<InteractiveComponent /> {/* Client Component */}
</div>
);
}
In this example:
Home
component is a Server Component, and it renders static content.InteractiveComponent
is a Client Component that handles interactivity, like managing the count.Server Component Flow:
Client Component Flow:
This distinction enables more efficient rendering in Next.js and makes it easier to build fast, optimized applications.
Let’s take a deep dive into Next.js routing, incorporating detailed explanations, folder structures, and examples based on the latest Next.js conventions using both the Pages Router (pages/
directory) and App Router (app/
directory introduced in Next.js 13+). I’ll also reference official documentation concepts as we explore each topic.
Next.js provides two routing systems depending on whether you’re using the Pages Router or the App Router.
pages/
directory enables file-based routing. Each file inside this folder automatically becomes a route.index.tsx
file is the root route (/
).pages/
├── index.tsx // maps to '/'
├── about.tsx // maps to '/about'
├── profile.tsx // maps to '/profile'
// pages/index.tsx
export default function Home() {
return <h1>Welcome to the Homepage</h1>;
}
pages/
correspond to routes by their filenames, and you don’t need additional configuration.app/
directory for routing. This system allows you to organize your application using folders rather than just files, offering a more scalable routing system, especially for large applications.app/
├── page.tsx // maps to '/'
├── about/
│ ├── page.tsx // maps to '/about'
├── profile/
├── page.tsx // maps to '/profile'
// app/page.tsx
export default function Home() {
return <h1>Welcome to the Homepage</h1>;
}
page.tsx
. This enables a more modular structure, especially when combined with layouts.layout.tsx
)Layouts enable you to define shared UI elements (e.g., navigation bars, footers) that persist across different pages.
components/
├── Layout.tsx
pages/
├── about.tsx
├── profile.tsx
// components/Layout.tsx
export default function Layout({ children }) {
return (
<div>
<header>Header</header>
<main>{children}</main>
<footer>Footer</footer>
</div>
);
}
// pages/about.tsx
import Layout from "../components/Layout";
export default function About() {
return (
<Layout>
<h1>About Page</h1>
</Layout>
);
}
layout.tsx
file, and it automatically wraps around all pages inside that directory.app/
├── layout.tsx // Root layout
├── page.tsx // Homepage
├── about/
│ ├── layout.tsx // About-specific layout (optional)
│ ├── page.tsx // About page
├── profile/
├── page.tsx // Profile page
// app/layout.tsx (Root Layout)
export default function RootLayout({ children }) {
return (
<html>
<body>
<header>Site Header</header>
{children}
<footer>Site Footer</footer>
</body>
</html>
);
}
// app/about/page.tsx
export default function AboutPage() {
return <h1>About Us</h1>;
}
page.tsx
inside the folder will use the layout.tsx
as a wrapper.Nested routes are useful for structuring more complex pages.
pages/
├── about/
│ ├── team.tsx // maps to '/about/team'
│ ├── history.tsx // maps to '/about/history'
// pages/about/team.tsx
export default function Team() {
return <h1>Meet the Team</h1>;
}
page.tsx
.app/
├── about/
│ ├── page.tsx // maps to '/about'
│ ├── team/
│ │ ├── page.tsx // maps to '/about/team'
│ ├── history/
│ ├── page.tsx // maps to '/about/history'
// app/about/team/page.tsx
export default function TeamPage() {
return <h1>Meet the Team</h1>;
}
page.tsx
represents the actual route.Dynamic routes allow you to create pages that accept parameters, making them reusable.
pages/
├── profile/
│ ├── [username].tsx // maps to '/profile/:username'
// pages/profile/[username].tsx
import { useRouter } from "next/router";
export default function Profile() {
const router = useRouter();
const { username } = router.query;
return <h1>Profile of {username}</h1>;
}
app/
├── profile/
│ ├── [username]/
│ │ ├── page.tsx // maps to '/profile/:username'
// app/profile/[username]/page.tsx
import { useParams } from "next/navigation";
export default function ProfilePage() {
const { username } = useParams();
return <h1>Profile of {username}</h1>;
}
[username]
indicates a dynamic route. The value of username
is extracted from the URL.Nested dynamic routes allow for complex dynamic parameters in your application.
pages/
├── blog/
│ ├── [category]/
│ │ ├── [postId].tsx // maps to '/blog/:category/:postId'
// pages/blog/[category]/[postId].tsx
import { useRouter } from "next/router";
export default function BlogPost() {
const router = useRouter();
const { category, postId } = router.query;
return (
<div>
<h1>Category: {category}</h1>
<p>Post ID: {postId}</p>
</div>
);
}
app/
├── blog/
│ ├── [category]/
│ │ ├── [postId]/
│ │ │ ├── page.tsx // maps to '/blog/:category/:postId'
// app/blog/[category]/[postId]/page.tsx
import { useParams } from "next/navigation";
export default function BlogPost() {
const { category, postId } = useParams();
return (
<div>
<h1>Category: {category}</h1>
<p>Post ID: {postId}</p>
</div>
);
}
[...param]
to capture all segments.[[...param]]
to make the parameter optional.pages/
├── [...slug].tsx // maps to '/a/b/c'
├── [[...slug]].tsx // maps to '/' or '/a/b/c'
:
// pages/[...slug].tsx
import { useRouter } from "next/router";
export default function CatchAll() {
const router = useRouter();
const { slug } = router.query;
return <p>Slug: {slug?.join("/")}</p>;
}
[...param]
or [[...param]]
.app/
├── [...slug]/
│ ├── page.tsx // maps to '/a/b/c'
├── [[...slug]]/
├── page.tsx // maps to '/' or '/a/b/c'
404.tsx
file in pages/
handles non-matching routes.// pages/404.tsx
export default function Custom404() {
return <h1>404 - Page Not Found</h1>;
}
not-found.tsx
file in app/
is used to handle non-matching routes.// app/not-found.tsx
export default function NotFound() {
return <h1>404 - Page Not Found</h1>;
}
File colocation in Next.js refers to the practice of organizing files related to a particular component, page, or feature in the same directory. Instead of scattering related files across different folders (e.g., separating components, styles, and tests), file colocation groups everything together. This practice promotes modularity, maintainability, and scalability, especially in large applications.
In Next.js, this pattern is especially useful when using the App Router (app/
directory) introduced in Next.js 13, which supports grouping page, layout, loading, error, metadata, and styles files in the same directory, corresponding to a specific route.
In the Pages Router (pages/
directory), file colocation can be manually achieved by grouping files related to a specific page inside a folder, even though Next.js doesn’t natively enforce this structure.
pages/
├── about/
│ ├── index.tsx // About page
│ ├── About.module.css // Styles specific to the About page
│ ├── About.test.tsx // Unit tests for the About page
// pages/about/index.tsx
import styles from "./About.module.css";
export default function About() {
return (
<div className={styles.about}>
<h1>About Us</h1>
</div>
);
}
Here, everything related to the About
page is colocated inside the about/
directory: the page itself, its styles, and its tests.
In the App Router (app/
directory), file colocation is a more natural and structured pattern. Each route or segment has its own folder, and Next.js automatically recognizes certain files like page.tsx
, layout.tsx
, loading.tsx
, etc., inside that folder.
app/
├── about/
│ ├── page.tsx // About page component
│ ├── layout.tsx // Layout for the About page
│ ├── loading.tsx // Loading state for the About page
│ ├── error.tsx // Error handling for the About page
│ ├── styles.css // Styles specific to the About page
│ ├── meta.ts // Metadata for the About page
// app/about/page.tsx
export default function About() {
return (
<div>
<h1>About Us</h1>
<p>This is the About page.</p>
</div>
);
}
// app/about/layout.tsx
export default function AboutLayout({ children }) {
return (
<section>
<header>About Page Header</header>
{children}
<footer>About Page Footer</footer>
</section>
);
}
In this structure:
page.tsx
: Defines the content of the About
page (/about
route).layout.tsx
: Defines a layout specific to the About
page, which wraps around the content.loading.tsx
: Handles the loading state for the page.error.tsx
: Handles errors that occur during rendering.styles.css
: Contains styles specific to this route.meta.ts
: Provides metadata (such as title and description) for the About
page.Let’s imagine a scenario where you have a blog section with dynamic routes, loading states, and specific layouts.
app/
├── blog/
│ ├── layout.tsx // Blog layout used for all blog-related pages
│ ├── loading.tsx // Loading state for blog pages
│ ├── [slug]/ // Dynamic routing for individual blog posts
│ │ ├── page.tsx // Page for a specific blog post
│ │ ├── error.tsx // Error handling for individual blog posts
│ │ ├── styles.css // Styles specific to blog posts
│ ├── styles.css // Styles for the blog index
│ ├── page.tsx // Blog index page
app/blog/page.tsx
(Blog Index Page):export default function Blog() {
return (
<div>
<h1>Welcome to the Blog</h1>
</div>
);
}
app/blog/layout.tsx
(Blog Layout):export default function BlogLayout({ children }) {
return (
<section>
<header>Blog Header</header>
{children}
<footer>Blog Footer</footer>
</section>
);
}
app/blog/[slug]/page.tsx
(Dynamic Blog Post Page):export default function BlogPost({ params }) {
const { slug } = params;
return (
<div>
<h1>Blog Post: {slug}</h1>
<p>This is a dynamic blog post.</p>
</div>
);
}
In this example, the blog post’s layout (layout.tsx
), the loading state (loading.tsx
), and the error state (error.tsx
) are colocated with the specific blog/[slug]/
dynamic route. Similarly, styles can be colocated within each segment to maintain modularity.
page.tsx
, layout.tsx
, etc.) in the directory structure.This approach makes it easy to manage Next.js apps of any size by maintaining a clear, consistent structure, improving both the developer experience and code maintainability.
In Next.js, Private Folders and Route Groups are advanced concepts introduced in Next.js 13+ with the new App Router (app/
directory). These concepts help in organizing routes and keeping certain parts of the application separate from the public routes, which improves maintainability, modularity, and control over routing behavior.
Let’s explore both of these concepts in detail, with official definitions and examples based on the Next.js documentation.
Definition (from Next.js documentation):
Private folders are any folders that start with an underscore (
_
) in theapp/
directory. These folders are not part of the route structure and do not result in actual routes being generated. They are used for organizing and storing components, utilities, or other files that should not create routes.
Use Case: Private folders are useful when you want to store files like shared components, utilities, or other resources related to the app, but you don’t want them to create a route or be directly accessible via the browser.
app/
directory that begin with an underscore (_
) are ignored when Next.js generates the route structure.app/
├── about/
│ ├── page.tsx // Accessible at '/about'
├── _components/ // Private folder for shared components
│ ├── Navbar.tsx // Navbar component (not routable)
├── _utils/ // Private folder for utility functions
│ ├── fetchData.ts // Utility function (not routable)
_components/
: Stores shared UI components like Navbar
, Footer
, etc., without generating routes for them._utils/
: Contains utility functions, like fetchData.ts
, that you can import into other components or pages but which won’t be exposed via routing.app/_components/Navbar.tsx
:export default function Navbar() {
return <nav>Navigation Bar</nav>;
}
app/about/page.tsx
(importing from a private folder):import Navbar from "../_components/Navbar";
export default function AboutPage() {
return (
<div>
<Navbar />
<h1>About Us</h1>
</div>
);
}
Key Points:
_
) to indicate that they are private.Definition (from Next.js documentation):
Route Groups allow you to group routes without affecting the URL structure. Route groups let you organize and manage routes by creating a folder that doesn’t impact the final route, allowing you to group related pages under one folder.
Use Case: Route groups are particularly useful for grouping routes that share common functionality (such as layouts, authentication, or feature sets) while keeping the URL structure clean. This helps with the maintainability of large applications, without modifying the routes exposed to the end user.
()
is a Route Group. This folder groups other routes but is not reflected in the URL.app/
directory without affecting the routing path.app/
├── (marketing)/ // Route Group for marketing pages (doesn't affect URL)
│ ├── home/
│ │ ├── page.tsx // Accessible at '/'
│ ├── about/
│ │ ├── page.tsx // Accessible at '/about'
├── (dashboard)/ // Route Group for dashboard (doesn't affect URL)
│ ├── settings/
│ │ ├── page.tsx // Accessible at '/settings'
│ ├── profile/
│ │ ├── page.tsx // Accessible at '/profile'
In this structure:
(marketing)
: Groups the marketing pages (home
, about
) but does not add marketing
to the URL.(dashboard)
: Groups the dashboard pages (settings
, profile
), but the URLs are /settings
and /profile
instead of /dashboard/settings
or /dashboard/profile
.app/(marketing)/about/page.tsx
:export default function AboutPage() {
return (
<div>
<h1>About Us</h1>
</div>
);
}
The about
page is accessible at /about
despite being nested under the (marketing)
route group.
app/(dashboard)/settings/page.tsx
:export default function SettingsPage() {
return (
<div>
<h1>Settings</h1>
</div>
);
}
The settings
page is accessible at /settings
even though it is grouped under the (dashboard)
folder.
Route groups can also be used to share layouts or common components across a group of routes.
Example Folder Structure:
app/
├── (dashboard)/
│ ├── layout.tsx // Layout for the dashboard section
│ ├── settings/
│ │ ├── page.tsx // Accessible at '/settings'
│ ├── profile/
│ ├── page.tsx // Accessible at '/profile'
app/(dashboard)/layout.tsx
:export default function DashboardLayout({ children }) {
return (
<div>
<nav>Dashboard Navigation</nav>
<main>{children}</main>
</div>
);
}
app/(dashboard)/settings/page.tsx
(using the layout):export default function SettingsPage() {
return (
<div>
<h1>Settings</h1>
</div>
);
}
Here, all routes under the (dashboard)
group will use the same layout.tsx
, but the (dashboard)
part of the folder structure will not appear in the URLs.
_
), e.g., _components/
.()
), e.g., (dashboard)/
.Both of these features help in maintaining large-scale Next.js applications by keeping the folder structure organized while ensuring clean, user-friendly URLs.