docs

Introduction to Next.js

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.

Key Features of Next.js

  1. 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.

  2. 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.

  3. 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.

  4. 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.

  5. 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.

  6. 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.

  7. 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.

  8. 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.

  9. 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.

  10. TypeScript Support: Next.js has first-class TypeScript support, allowing you to build type-safe applications seamlessly.

Use Cases of Next.js

To install Next.js, follow these steps:

  1. Ensure Node.js is installed: Make sure you have Node.js installed on your machine. You can check by running:

    node -v
    
  2. 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
    
  3. Navigate to the project directory: Once the installation is complete, move into the project directory:

    cd my-next-app
    
  4. 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.

Next js control flow, how pages renders and stuff

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:

1. File-Based Routing

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:

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>;
}

2. Initial Request Handling

When a user accesses the homepage (e.g., http://localhost:3000/), Next.js performs the following tasks:

3. Rendering the Home() Component

a. Static Rendering (Static Site Generation - SSG):

In many cases, the initial rendering of the Home() component is done statically at build time. Here’s what happens:

b. Server-Side Rendering (SSR):

In server-side rendering mode, Next.js renders the Home() component at runtime for each request.

4. Hydration on the Client-Side

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:

5. Data Fetching Mechanism (Optional)

If your Home() component needs to fetch data, Next.js provides methods to do this:

6. API Routes Handling (Optional)

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.

Behind the Scenes

Flow Summary

  1. Routing: Next.js determines the route by checking the file structure (e.g., /pages/index.js).
  2. Static/Server-Side Rendering:
    • Static pages are rendered at build time and served as HTML.
    • SSR pages are rendered on each request and sent as HTML.
  3. Client-Side Hydration: The browser receives the static HTML and hydrates it with React to make the page interactive.
  4. Data Fetching: If getStaticProps or getServerSideProps is used, data is fetched and passed to the component.
  5. API Routes (if applicable): API routes process backend logic if called by the page.

React Server Components (RSC) and React Client Components

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.

1. React Server Components

Characteristics:

Example of a Server Component:

// 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>
  );
}

2. React Client Components

Characteristics:

Example of a Client Component:

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>
  );
}

3. Mixed Usage: Server and Client Components Together

Example: Combining Server and Client Components

// 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:

4. When to Use Server vs Client Components

5. Server vs Client Component Flow

Key Takeaways:

This distinction enables more efficient rendering in Next.js and makes it easier to build fast, optimized applications.

Routing

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.

1. Basic Routing

Next.js provides two routing systems depending on whether you’re using the Pages Router or the App Router.

Pages Router (File-based routing):

Example Folder Structure:

pages/
  ├── index.tsx      // maps to '/'
  ├── about.tsx      // maps to '/about'
  ├── profile.tsx    // maps to '/profile'

Code Example:

// pages/index.tsx
export default function Home() {
  return <h1>Welcome to the Homepage</h1>;
}

App Router (Folder-based routing):

Example Folder Structure:

app/
  ├── page.tsx       // maps to '/'
  ├── about/
  │   ├── page.tsx   // maps to '/about'
  ├── profile/
      ├── page.tsx   // maps to '/profile'

Code Example:

// app/page.tsx
export default function Home() {
  return <h1>Welcome to the Homepage</h1>;
}

2. Layouts (layout.tsx)

Layouts enable you to define shared UI elements (e.g., navigation bars, footers) that persist across different pages.

Pages Router (File-based Layout):

Example Folder Structure:

components/
  ├── Layout.tsx
pages/
  ├── about.tsx
  ├── profile.tsx

Code Example:

// 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>
  );
}

App Router (Folder-based Layout):

Example Folder Structure:

app/
  ├── layout.tsx       // Root layout
  ├── page.tsx         // Homepage
  ├── about/
  │   ├── layout.tsx   // About-specific layout (optional)
  │   ├── page.tsx     // About page
  ├── profile/
      ├── page.tsx     // Profile page

Code Example:

// 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>;
}

3. Nested Routes

Nested routes are useful for structuring more complex pages.

Pages Router (File-based Nested Routing):

Example Folder Structure:

pages/
  ├── about/
  │   ├── team.tsx    // maps to '/about/team'
  │   ├── history.tsx // maps to '/about/history'

Code Example:

// pages/about/team.tsx
export default function Team() {
  return <h1>Meet the Team</h1>;
}

App Router (Folder-based Nested Routing):

Example Folder Structure:

app/
  ├── about/
  │   ├── page.tsx         // maps to '/about'
  │   ├── team/
  │   │   ├── page.tsx     // maps to '/about/team'
  │   ├── history/
      │   ├── page.tsx     // maps to '/about/history'

Code Example:

// app/about/team/page.tsx
export default function TeamPage() {
  return <h1>Meet the Team</h1>;
}

4. Dynamic Routes

Dynamic routes allow you to create pages that accept parameters, making them reusable.

Pages Router (File-based Dynamic Routing):

Example Folder Structure:

pages/
  ├── profile/
  │   ├── [username].tsx  // maps to '/profile/:username'

Code Example:

// 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 Router (Folder-based Dynamic Routing):

Example Folder Structure:

app/
  ├── profile/
  │   ├── [username]/
  │   │   ├── page.tsx  // maps to '/profile/:username'

Code Example:

// app/profile/[username]/page.tsx
import { useParams } from "next/navigation";

export default function ProfilePage() {
  const { username } = useParams();

  return <h1>Profile of {username}</h1>;
}

5. Nested Dynamic Routes

Nested dynamic routes allow for complex dynamic parameters in your application.

Pages Router (File-based Nested Dynamic Routing):

Example Folder Structure:

pages/
  ├── blog/
  │   ├── [category]/
  │   │   ├── [postId].tsx  // maps to '/blog/:category/:postId'

Code Example:

// 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 Router (Folder-based Nested Dynamic Routing):

Example Folder Structure:

app/
  ├── blog/
  │   ├── [category]/
  │   │   ├── [postId]/
  │   │   │   ├── page.tsx  // maps to '/blog/:category/:postId'

Code Example:

// 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>
  );
}

6. Catch-all and Optional Catch-all Segments

Pages Router (File-based Catch-all and Optional Catch-all):

Example Folder Structure:

pages/
  ├── [...slug].tsx         // maps to '/a/b/c'
  ├── [[...slug]].tsx       // maps to '/' or '/a/b/c'

Code Example

:

// 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>;
}

App Router (Folder-based Catch-all and Optional Catch-all):

Example Folder Structure:

app/
  ├── [...slug]/
  │   ├── page.tsx       // maps to '/a/b/c'
  ├── [[...slug]]/
      ├── page.tsx       // maps to '/' or '/a/b/c'

7. Handling Non-matching Routes

Pages Router:

Example:

// pages/404.tsx
export default function Custom404() {
  return <h1>404 - Page Not Found</h1>;
}

App Router:

Example:

// app/not-found.tsx
export default function NotFound() {
  return <h1>404 - Page Not Found</h1>;
}

File Colocation

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.

Benefits of File Colocation:

  1. Modularity: Each feature or route is self-contained and modular, making it easy to update or move without affecting other parts of the codebase.
  2. Contextuality: When you work on a route or component, all related files are located together, improving the developer experience by reducing the need to navigate different directories.
  3. Scalability: As your app grows, colocating files simplifies management, especially when working with nested routes or shared layouts.

File Colocation in the Pages Router (Next.js < 13)

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.

Example Folder Structure (Pages Router):

pages/
  ├── about/
  │   ├── index.tsx      // About page
  │   ├── About.module.css  // Styles specific to the About page
  │   ├── About.test.tsx    // Unit tests for the About page

Code Example (Pages Router):

// 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.

File Colocation in the App Router (Next.js 13+)

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.

Example Folder Structure (App Router):

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

Code Example (App Router):

// 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:

Detailed Colocation Example: Blog Section

Let’s imagine a scenario where you have a blog section with dynamic routes, loading states, and specific layouts.

Folder Structure:

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

Code Examples:

export default function Blog() {
  return (
    <div>
      <h1>Welcome to the Blog</h1>
    </div>
  );
}
export default function BlogLayout({ children }) {
  return (
    <section>
      <header>Blog Header</header>
      {children}
      <footer>Blog Footer</footer>
    </section>
  );
}
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.

File Colocation in Practice:


Recap of File Colocation:

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.

Private Folder and Route Group

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.


1. Private Folders

Definition (from Next.js documentation):

Private folders are any folders that start with an underscore (_) in the app/ 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.

How Private Folders Work:

Example Folder Structure with Private Folders:

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)

Code Example:

export default function Navbar() {
  return <nav>Navigation Bar</nav>;
}
import Navbar from "../_components/Navbar";

export default function AboutPage() {
  return (
    <div>
      <Navbar />
      <h1>About Us</h1>
    </div>
  );
}

Key Points:


2. Route Groups

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.

How Route Groups Work:

Example Folder Structure with Route Groups:

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:

Code Example:

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.

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.

Key Points:

Combining Route Groups with Layouts:

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'
export default function DashboardLayout({ children }) {
  return (
    <div>
      <nav>Dashboard Navigation</nav>
      <main>{children}</main>
    </div>
  );
}
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.


Summary of Concepts:

Private Folders:

Route Groups:

Both of these features help in maintaining large-scale Next.js applications by keeping the folder structure organized while ensuring clean, user-friendly URLs.

Part 2