Skip to content

callmeskyy111/nextjs-15-routing

Folders and files

NameName
Last commit message
Last commit date

Latest commit

Β 

History

9 Commits
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 

Repository files navigation

What is Next.js?

Next.js is a React framework that enables server-side rendering (SSR) and static site generation (SSG) for building fast, scalable, and SEO-friendly web applications. It is developed by Vercel and provides powerful features like:

  • Server-side rendering (SSR)
  • Static site generation (SSG)
  • Incremental Static Regeneration (ISR)
  • API routes (backend logic within the same project)
  • Built-in CSS and image optimization
  • Middleware for request handling
  • File-based routing system (No need for React Router)

Why Do We Need Next.js?

1. Better SEO (Search Engine Optimization)

React applications built with Create React App (CRA) render everything on the client-side, meaning search engines struggle to index the page properly. Next.js solves this problem by pre-rendering pages on the server before sending them to the browser.

  • SSR (Server-Side Rendering) generates HTML dynamically on each request, improving SEO.
  • SSG (Static Site Generation) creates pre-rendered pages at build time, making them load faster.

πŸš€ Result: Better search engine rankings and increased organic traffic.


2. Faster Page Load Speed

Next.js optimizes performance by pre-rendering pages and lazy loading assets, which means:

  • Users get the initial page load faster (SSR or SSG).
  • Image optimization improves performance and user experience.
  • Built-in code splitting reduces unnecessary JavaScript loading.

πŸš€ Result: Faster websites lead to better user engagement and retention.


3. File-Based Routing (No Need for React Router)

In a React app, we usually set up routing with react-router-dom. However, Next.js simplifies routing with a file-based system:

πŸ“‚ Example:

pages/
 β”œβ”€β”€ index.js       β†’ Homepage ("/")
 β”œβ”€β”€ about.js       β†’ About page ("/about")
 β”œβ”€β”€ blog/
 β”‚   β”œβ”€β”€ index.js   β†’ Blog listing ("/blog")
 β”‚   β”œβ”€β”€ [id].js    β†’ Dynamic route ("/blog/:id")

Key Benefits:
βœ… No need for react-router-dom
βœ… Automatic route creation
βœ… Dynamic routes with [id].js (e.g., /blog/1, /blog/2)

πŸš€ Result: Simpler, more maintainable routing system.


4. Built-in API Routes (Backend Inside Next.js)

Next.js allows us to create backend APIs directly within the project under the pages/api/ directory. This eliminates the need for a separate backend server in many cases.

πŸ“‚ Example:

pages/api/
 β”œβ”€β”€ hello.js       β†’ "/api/hello"
 β”œβ”€β”€ users.js       β†’ "/api/users"

πŸ“Œ API Example (pages/api/hello.js)

export default function handler(req, res) {
  res.status(200).json({ message: "Hello from Next.js API!" });
}

πŸš€ Result: No need for an external backend (Express.js, Node.js) in many cases.


5. Server-Side Rendering (SSR) vs. Static Site Generation (SSG)

Next.js provides two types of pre-rendering:

Feature Server-Side Rendering (SSR) Static Site Generation (SSG)
When HTML is generated? On each request At build time
Data is fetched? On every request Once during build
Use cases Real-time data (e.g., user dashboards) Blogs, marketing pages

πŸ“Œ SSR Example:

export async function getServerSideProps() {
  const data = await fetch("https://api.example.com/posts").then(res => res.json());
  return { props: { posts: data } };
}

πŸ“Œ SSG Example:

export async function getStaticProps() {
  const data = await fetch("https://api.example.com/posts").then(res => res.json());
  return { props: { posts: data } };
}

πŸš€ Result: We can choose the best rendering strategy based on project requirements.


6. Incremental Static Regeneration (ISR)

Next.js allows us to update static pages without rebuilding the entire site.

πŸ“Œ Example:

export async function getStaticProps() {
  return { props: { time: new Date().toISOString() }, revalidate: 10 };
}

πŸ‘† This will regenerate the page every 10 seconds, keeping it up-to-date without affecting performance.

πŸš€ Result: Perfect for blogs, e-commerce, and frequently updated pages.


7. Automatic Image Optimization

Next.js provides an optimized <Image> component that automatically resizes and optimizes images.

πŸ“Œ Example:

import Image from 'next/image';

export default function Home() {
  return <Image src="/image.jpg" width={500} height={300} alt="Example Image" />;
}

πŸš€ Result: Faster image loading and better performance.


8. Middleware for Custom Request Handling

Middleware allows us to run custom logic before a request is completed.

πŸ“Œ Example: Redirect users based on authentication:

import { NextResponse } from "next/server";

export function middleware(req) {
  const loggedIn = checkUserAuth(req); // Custom function
  if (!loggedIn) {
    return NextResponse.redirect("/#");
  }
}

πŸš€ Result: More control over authentication, security, and logging.


9. Support for TypeScript & Tailwind CSS

Next.js has built-in TypeScript support, making it easier to build type-safe applications. It also integrates seamlessly with Tailwind CSS for styling.

πŸ“Œ Enable TypeScript:

npx create-next-app@latest my-app --typescript

πŸ“Œ Tailwind CSS in Next.js:

npm install tailwindcss postcss autoprefixer
npx tailwindcss init -p

πŸš€ Result: Easier development with TypeScript and Tailwind CSS.


When to Use Next.js?

βœ… When SEO is important (blogs, e-commerce, marketing websites)
βœ… When performance is a priority (faster page loads)
βœ… When we need SSR, SSG, or ISR
βœ… When we want built-in API routes
βœ… When we want a simpler file-based routing system
βœ… When we need image and performance optimizations


When NOT to Use Next.js?

❌ If we are building a small project without SEO needs (Create React App may be enough)
❌ If we need a backend-heavy project (better to use Express.js or Nest.js separately)
❌ If we don't need SSR/SSG and just want a simple React SPA


Conclusion

Next.js is a powerful framework that enhances React with server-side rendering, static site generation, API routes, and performance optimizations. It is ideal for SEO-friendly, fast, and scalable web applications.

πŸ“‚ Folder Structure of a Basic Next.js 15 App

When we create a new Next.js 15 app using:

npx create-next-app@latest my-next-app

or

npx create-next-app@latest my-next-app --typescript

It generates the following folder structure:

my-next-app/
β”œβ”€β”€ .next/                  # Build output (generated after running `next build`)
β”œβ”€β”€ node_modules/           # Installed npm packages
β”œβ”€β”€ public/                 # Static assets (images, fonts, icons, etc.)
β”œβ”€β”€ src/                    # Main source folder (new in Next.js 13+)
β”‚   β”œβ”€β”€ app/                # New App Router (Next.js 13+ with React Server Components)
β”‚   β”‚   β”œβ”€β”€ layout.tsx      # Root layout (persistent layout for all pages)
β”‚   β”‚   β”œβ”€β”€ page.tsx        # Homepage (`/`)
β”‚   β”‚   β”œβ”€β”€ about/page.tsx  # About page (`/about`)
β”‚   β”‚   β”œβ”€β”€ blog/           # Blog route
β”‚   β”‚   β”‚   β”œβ”€β”€ page.tsx    # Blog listing page (`/blog`)
β”‚   β”‚   β”‚   β”œβ”€β”€ [id]/page.tsx # Dynamic route for blog (`/blog/:id`)
β”‚   β”‚   β”œβ”€β”€ api/            # API routes (`/api/*`)
β”‚   β”‚   β”‚   β”œβ”€β”€ hello.ts    # Example API route (`/api/hello`)
β”‚   β”‚   β”œβ”€β”€ globals.css     # Global CSS file
β”‚   β”‚   β”œβ”€β”€ layout.tsx      # Root layout
β”‚   β”‚   β”œβ”€β”€ loading.tsx     # Loading UI
β”‚   β”‚   β”œβ”€β”€ error.tsx       # Error handling page
β”‚   β”œβ”€β”€ components/         # Reusable UI components
β”‚   β”œβ”€β”€ styles/             # CSS, Tailwind, or SCSS styles
β”‚   β”œβ”€β”€ lib/                # Utility functions, helpers, services
β”‚   β”œβ”€β”€ hooks/              # Custom React hooks
β”‚   β”œβ”€β”€ context/            # React Context API providers
β”œβ”€β”€ .env.local              # Environment variables (API keys, secrets)
β”œβ”€β”€ .gitignore              # Files to ignore in Git
β”œβ”€β”€ next.config.js          # Next.js configuration file
β”œβ”€β”€ package.json            # Project dependencies & scripts
β”œβ”€β”€ tsconfig.json           # TypeScript configuration (if using TS)
└── README.md               # Project documentation

πŸ” Detailed Explanation of Key Folders & Files

1️⃣ .next/ (Generated after build)

  • Contains the compiled output of our project.
  • Not meant to be modified manually.
  • Should be ignored in Git (.gitignore).

2️⃣ node_modules/

  • Stores all installed npm dependencies.
  • Automatically created when we run npm install or yarn install.

3️⃣ public/

  • Contains static assets like images, fonts, and icons.
  • Everything in this folder is served as-is from the root (/).

πŸ“Œ Example:

  • If we put an image inside public/images/logo.png, we can access it in the browser as:
    /images/logo.png
    
  • Inside React components, we use it as:
    import Image from "next/image";
    
    export default function Logo() {
      return <Image src="/images/logo.png" width={100} height={50} alt="Logo" />;
    }

4️⃣ src/ (Main Source Folder)

New in Next.js 13+, it contains all source code and the new App Router (app/ folder).

πŸ“‚ app/ (Next.js 13+ App Router)

The app/ folder introduces the new React Server Components model with file-based routing.

πŸ“Œ Example structure:

app/
β”œβ”€β”€ layout.tsx    # Root layout (applies to all pages)
β”œβ”€β”€ page.tsx      # Homepage (`/`)
β”œβ”€β”€ about/
β”‚   β”œβ”€β”€ page.tsx  # About page (`/about`)
β”œβ”€β”€ blog/
β”‚   β”œβ”€β”€ page.tsx  # Blog listing (`/blog`)
β”‚   β”œβ”€β”€ [id]/
β”‚   β”‚   β”œβ”€β”€ page.tsx  # Dynamic blog page (`/blog/:id`)
β”œβ”€β”€ api/
β”‚   β”œβ”€β”€ hello.ts  # API route (`/api/hello`)
β”œβ”€β”€ globals.css   # Global styles
β”œβ”€β”€ loading.tsx   # Loading indicator
β”œβ”€β”€ error.tsx     # Error handling page

πŸ“‚ app/api/ (API Routes)

Next.js allows us to create backend API routes inside app/api/.

πŸ“Œ Example: API Route (/api/hello)

export async function GET() {
  return Response.json({ message: "Hello, Next.js API!" });
}

5️⃣ components/

  • Reusable React components used across the project.
  • Helps keep the codebase clean.

πŸ“Œ Example: Button Component (components/Button.tsx)

export default function Button({ label }: { label: string }) {
  return <button className="bg-blue-500 text-white p-2">{label}</button>;
}

6️⃣ styles/

  • Contains global CSS, TailwindCSS, or SCSS styles.

πŸ“Œ Example: Global CSS (styles/globals.css)

body {
  font-family: Arial, sans-serif;
  background-color: #f4f4f4;
}

7️⃣ lib/ (Utility Functions & Services)

  • Stores helper functions or services like database connections.

πŸ“Œ Example: Helper Function (lib/fetchData.ts)

export async function fetchData(url: string) {
  const res = await fetch(url);
  return res.json();
}

8️⃣ hooks/ (Custom Hooks)

  • Stores custom React hooks for state management.

πŸ“Œ Example: Custom Hook (hooks/useTheme.ts)

import { useState, useEffect } from "react";

export function useTheme() {
  const [theme, setTheme] = useState("light");

  useEffect(() => {
    document.body.className = theme;
  }, [theme]);

  return { theme, setTheme };
}

9️⃣ context/ (React Context API)

  • Stores global state using React’s Context API.

πŸ“Œ Example: Theme Context (context/ThemeContext.tsx)

import { createContext, useContext, useState } from "react";

const ThemeContext = createContext(null);

export function ThemeProvider({ children }: { children: React.ReactNode }) {
  const [theme, setTheme] = useState("light");
  return (
    <ThemeContext.Provider value={{ theme, setTheme }}>
      {children}
    </ThemeContext.Provider>
  );
}

export function useTheme() {
  return useContext(ThemeContext);
}

πŸ”Ÿ Configuration & Other Files

βœ… .env.local (Environment Variables)

  • Stores API keys, database URLs, secrets.
  • Example:
    DATABASE_URL=mongodb+srv://username:password@cluster.mongodb.net/mydb
    

βœ… next.config.js (Next.js Configuration)

  • Customizes Next.js settings.
  • Example:
    module.exports = {
      reactStrictMode: true,
      images: {
        domains: ["example.com"],
      },
    };

βœ… package.json (Dependencies & Scripts)

  • Stores project dependencies & scripts.
  • Example:
    {
      "scripts": {
        "dev": "next dev",
        "build": "next build",
        "start": "next start"
      }
    }

πŸš€ Conclusion

Next.js 15 follows a modern folder structure with the app/ directory, making it easier to manage server components, pages, API routes, and reusable logic.

πŸ”₯ React Server Components (RSC) - In-Depth Guide


πŸš€ What are React Server Components (RSC)?

React Server Components (RSC) allow us to render React components on the server instead of the client. This helps in reducing JavaScript bundle size, improving performance, and enabling direct access to databases, APIs, and files without exposing sensitive logic to the browser.

βœ… Key Features of RSC:

  • No Client-Side JavaScript Execution β†’ Reduces JS sent to the browser.
  • Direct Database & API Calls β†’ No need for fetching data on the client.
  • Automatic Code Splitting β†’ Only sends required data to the frontend.
  • SEO-Friendly β†’ Renders content on the server before sending it to the client.
  • Improved Performance β†’ Faster page loads as less JavaScript is processed in the browser.

πŸ“Œ How RSC Works?

React now categorizes components into two types:
1️⃣ Server Components β†’ Run only on the server (default in Next.js 13+).
2️⃣ Client Components β†’ Run on both the server and browser but require JavaScript.


πŸ” Differences Between Server and Client Components

Feature Server Components (RSC) πŸ–₯️ Client Components πŸ–₯οΈπŸ“±
Runs on Server only Server + Client
JavaScript in Browser? ❌ No βœ… Yes
Can Fetch Data? βœ… Yes (Directly) βœ… Yes (Via useEffect)
Can Use State (useState)? ❌ No βœ… Yes
Can Use Effects (useEffect)? ❌ No βœ… Yes
Can Use Event Handlers? ❌ No βœ… Yes (onClick, onChange, etc.)
Bundle Size Impact πŸš€ Smaller (No JS sent) πŸ“ˆ Larger (JS sent)
SEO Optimization βœ… Better ❌ Worse

🎯 When to Use Server vs. Client Components?

βœ… Use Server Components when:
βœ”οΈ Fetching data from a database or API.
βœ”οΈ Rendering static content (e.g., blog posts, articles, products).
βœ”οΈ Improving SEO (Pre-rendered content).
βœ”οΈ Reducing client-side JavaScript.

βœ… Use Client Components when:
βœ”οΈ Using state (useState, useReducer).
βœ”οΈ Handling user interactions (onClick, onChange).
βœ”οΈ Using effects (useEffect, useRef).
βœ”οΈ Implementing animations (e.g., framer-motion).


πŸ“‚ Folder Structure in Next.js 15 with RSC

Next.js 13+ follows the App Router (app/) pattern where all components are Server Components by default.

app/
β”œβ”€β”€ layout.tsx       # Root layout (Server Component)
β”œβ”€β”€ page.tsx         # Home page (Server Component)
β”œβ”€β”€ about/page.tsx   # About page (Server Component)
β”œβ”€β”€ components/
β”‚   β”œβ”€β”€ Navbar.tsx   # Server Component
β”‚   β”œβ”€β”€ Button.tsx   # Client Component (interactive)
β”‚   β”œβ”€β”€ UserList.tsx # Server Component (fetches data)
β”œβ”€β”€ api/
β”‚   β”œβ”€β”€ hello.ts     # API Route (Server Side)

βœ… Creating Server Components (Default Behavior)

  • By default, all components in app/ are Server Components in Next.js 13+.
  • Can fetch data directly from a database/API without client-side fetching.

πŸ“Œ Example: Fetching Data in a Server Component

// app/components/Users.tsx (Server Component)
import React from "react";

async function fetchUsers() {
  const res = await fetch("https://jsonplaceholder.typicode.com/users");
  return res.json();
}

export default async function Users() {
  const users = await fetchUsers();

  return (
    <div>
      <h2>User List (Fetched on Server)</h2>
      <ul>
        {users.map((user: any) => (
          <li key={user.id}>{user.name}</li>
        ))}
      </ul>
    </div>
  );
}

βœ… No useEffect needed to fetch data.
βœ… No extra JavaScript sent to the client.


βœ… Creating Client Components ("use client")

  • Client Components must be explicitly marked using "use client".
  • Used for interactivity, state, event handlers, and effects.

πŸ“Œ Example: Client Component with State

// app/components/Counter.tsx
"use client"; // πŸ‘ˆ Marks this as a Client Component
import { useState } from "react";

export default function Counter() {
  const [count, setCount] = useState(0);

  return (
    <div>
      <p>Count: {count}</p>
      <button onClick={() => setCount(count + 1)}>Increase</button>
    </div>
  );
}

βœ… Can use useState, useEffect, and event handlers.
βœ… JavaScript is sent to the browser, increasing bundle size.


🎯 Mixing Server & Client Components

  • Client Components can import Server Components, but NOT vice versa.
  • This allows fetching data on the server and passing it to client components.

πŸ“Œ Example: Hybrid Approach

// app/components/UserList.tsx (Server Component)
import React from "react";
import Counter from "./Counter"; // βœ… Importing a Client Component

async function fetchUsers() {
  const res = await fetch("https://jsonplaceholder.typicode.com/users");
  return res.json();
}

export default async function UserList() {
  const users = await fetchUsers();

  return (
    <div>
      <h2>Users:</h2>
      <ul>
        {users.map((user: any) => (
          <li key={user.id}>{user.name}</li>
        ))}
      </ul>
      <Counter /> {/* βœ… Client Component for Interactivity */}
    </div>
  );
}

βœ… Users fetched on the server (no client-side fetching).
βœ… Counter is interactive (uses useState).


πŸš€ Benefits of RSC in Next.js

βœ… Smaller Bundle Size: No unnecessary JavaScript is sent to the browser.
βœ… Better Performance: Fetch data on the server without affecting UI.
βœ… Improved SEO: Content is rendered before reaching the browser.
βœ… Security: Prevents exposing database queries or API calls to the client.


πŸ›‘ Limitations of Server Components

❌ No Client-Side State (useState) β†’ Must wrap with a Client Component.
❌ No Event Handlers (onClick, onChange) β†’ Must wrap with a Client Component.
❌ No useEffect, useRef, useContext β†’ Only works in Client Components.


πŸ”₯ Summary

Feature Server Component (Default) Client Component ("use client")
State (useState) ❌ No βœ… Yes
Event Handlers (onClick) ❌ No βœ… Yes
Fetch Data βœ… Yes (Directly) βœ… Yes (useEffect)
SEO Optimization βœ… Yes ❌ No
Bundle Size Impact βœ… Small πŸ“ˆ Larger

🎯 Final Thoughts

  • Next.js defaults to Server Components, making it easier to fetch data, optimize performance, and improve SEO.
  • Client Components are only needed for state, events, and interactivity.
  • The best approach is to mix both β†’ Fetch data on the server, then pass it to a Client Component for interactions.

πŸš€ Routing in Next.js 15 (App Router) – A Complete Guide

Next.js 15 uses the App Router (app/), which is a file-based routing system. This makes navigation in Next.js easier and more powerful.


πŸ”₯ 1. Understanding App Router in Next.js 15

  • The App Router (app/) replaces the old Pages Router (pages/).
  • Routing is based on the file structure inside the app/ directory.
  • Every file named page.tsx or page.jsx automatically becomes a route.
  • Nested directories create nested routes.

πŸ“Œ Example Folder Structure:

app/
β”œβ”€β”€ layout.tsx     # Root Layout (Shared UI)
β”œβ”€β”€ page.tsx       # Home Page β†’ "/"
β”œβ”€β”€ about/
β”‚   β”œβ”€β”€ page.tsx   # About Page β†’ "/about"
β”œβ”€β”€ blog/
β”‚   β”œβ”€β”€ page.tsx   # Blog Page β†’ "/blog"
β”‚   β”œβ”€β”€ [id]/      # Dynamic Route
β”‚   β”‚   β”œβ”€β”€ page.tsx # Blog Post β†’ "/blog/:id"
β”œβ”€β”€ dashboard/
β”‚   β”œβ”€β”€ layout.tsx # Dashboard Layout
β”‚   β”œβ”€β”€ page.tsx   # Dashboard Home β†’ "/dashboard"
β”‚   β”œβ”€β”€ settings/
β”‚   β”‚   β”œβ”€β”€ page.tsx # Dashboard Settings β†’ "/dashboard/settings"

βœ… Each page.tsx represents a route
βœ… Folders represent URL structure
βœ… Dynamic routing ([id]) is supported


πŸ”₯ 2. Creating Routes in Next.js 15

Routes in Next.js 15 are defined using page.tsx inside folders.

πŸ“Œ Example: Home Page (/)

// app/page.tsx
export default function HomePage() {
  return <h1>Welcome to Next.js 15!</h1>;
}

πŸ“Œ Example: About Page (/about)

// app/about/page.tsx
export default function AboutPage() {
  return <h1>About Us</h1>;
}

βœ… Simple, file-based routing without extra configuration


πŸ”₯ 3. Nested Routes & Layouts

Next.js 15 allows us to share layouts between multiple pages using layout.tsx.

πŸ“Œ Example: Shared Layout for Dashboard

app/
β”œβ”€β”€ dashboard/
β”‚   β”œβ”€β”€ layout.tsx  # Shared layout for dashboard
β”‚   β”œβ”€β”€ page.tsx    # "/dashboard"
β”‚   β”œβ”€β”€ settings/
β”‚   β”‚   β”œβ”€β”€ page.tsx # "/dashboard/settings"

πŸ“Œ dashboard/layout.tsx

export default function DashboardLayout({ children }: { children: React.ReactNode }) {
  return (
    <div>
      <nav>Dashboard Navbar</nav>
      <main>{children}</main>
    </div>
  );
}

πŸ“Œ dashboard/page.tsx

export default function DashboardPage() {
  return <h1>Dashboard Home</h1>;
}

πŸ“Œ dashboard/settings/page.tsx

export default function DashboardSettings() {
  return <h1>Dashboard Settings</h1>;
}

βœ… Navbar will persist across all dashboard pages
βœ… Shared layouts improve code reuse


πŸ”₯ 4. Dynamic Routes ([id])

We can create dynamic routes by wrapping a folder name in square brackets [ ].

πŸ“Œ Example: Blog Post Route (/blog/:id)

app/
β”œβ”€β”€ blog/
β”‚   β”œβ”€β”€ [id]/  # Dynamic Route
β”‚   β”‚   β”œβ”€β”€ page.tsx

πŸ“Œ blog/[id]/page.tsx

export default function BlogPost({ params }: { params: { id: string } }) {
  return <h1>Blog Post ID: {params.id}</h1>;
}

βœ… Access dynamic parameters using { params.id }
βœ… /blog/1 β†’ Shows "Blog Post ID: 1"
βœ… /blog/nextjs β†’ Shows "Blog Post ID: nextjs"


πŸ”₯ 5. Catch-All Routes ([[...slug]])

If we need a route that matches multiple segments, use [[...slug]].

πŸ“Œ Example: Catch-All Route

app/
β”œβ”€β”€ docs/
β”‚   β”œβ”€β”€ [[...slug]]/
β”‚   β”‚   β”œβ”€β”€ page.tsx

πŸ“Œ docs/[[...slug]]/page.tsx

export default function DocsPage({ params }: { params: { slug?: string[] } }) {
  return <h1>Docs: {params.slug ? params.slug.join("/") : "Home"}</h1>;
}

βœ… /docs/ β†’ "Docs: Home"
βœ… /docs/nextjs/15 β†’ "Docs: nextjs/15"


πŸ”₯ 6. Navigating Between Pages (next/link)

Use next/link for client-side navigation.

πŸ“Œ Example: Navbar with Links

import Link from "next/link";

export default function Navbar() {
  return (
    <nav>
      <Link href="/">Home</Link>
      <Link href="/about">About</Link>
      <Link href="/blog/1">Blog Post 1</Link>
    </nav>
  );
}

βœ… Fast navigation without page reloads


πŸ”₯ 7. API Routes (app/api/)

We can create API routes directly inside the app/api/ folder.

πŸ“Œ Example: API Route (/api/hello)

app/
β”œβ”€β”€ api/
β”‚   β”œβ”€β”€ hello/
β”‚   β”‚   β”œβ”€β”€ route.ts

πŸ“Œ api/hello/route.ts

export async function GET() {
  return new Response(JSON.stringify({ message: "Hello from API!" }), {
    headers: { "Content-Type": "application/json" },
  });
}

βœ… Visit /api/hello to get { "message": "Hello from API!" }


πŸ”₯ 8. Middleware (middleware.ts)

Middleware allows us to modify requests before they reach a route.

πŸ“Œ Example: Redirect /old to /new

import { NextResponse } from "next/server";
import type { NextRequest } from "next/server";

export function middleware(request: NextRequest) {
  if (request.nextUrl.pathname === "/old") {
    return NextResponse.redirect(new URL("/new", request.url));
  }
}

βœ… Used for authentication, redirects, logging, etc.


🎯 Summary

Feature App Router (Next.js 15)
Routing Type File-based routing
Nested Routes βœ… Yes
Dynamic Routes ([id]) βœ… Yes
API Routes (app/api/) βœ… Yes
Middleware βœ… Yes
Layouts βœ… Yes
Navigation (next/link) βœ… Yes

πŸš€ Final Thoughts

  • Next.js 15 automatically creates routes based on the app/ directory.
  • Layouts make it easy to reuse UI across multiple pages.
  • Dynamic routes allow flexible URL handling (/blog/:id).
  • API routes can handle backend logic without needing a separate server.
  • Client-side navigation (next/link) ensures fast performance.

πŸš€ Catch-All Segments in Next.js 15 (App Router)

Catch-all segments in Next.js 15 allow us to capture multiple URL segments in a single dynamic route. This is useful for handling flexible or deeply nested paths without defining each one manually.


πŸ”₯ 1. Syntax: Using [...] for Catch-All Segments

To create a catch-all route, we wrap a folder name inside square brackets with three dots ([...]).

πŸ“Œ Example: Catch-All Route (/docs/*)

app/
β”œβ”€β”€ docs/
β”‚   β”œβ”€β”€ [...slug]/
β”‚   β”‚   β”œβ”€β”€ page.tsx

πŸ“Œ app/docs/[...slug]/page.tsx

export default function DocsPage({ params }: { params: { slug?: string[] } }) {
  return <h1>Docs Path: {params.slug ? params.slug.join("/") : "Home"}</h1>;
}

βœ… Routes and Their Outputs

URL params.slug Output
/docs undefined Docs Path: Home
/docs/nextjs ["nextjs"] Docs Path: nextjs
/docs/nextjs/15 ["nextjs", "15"] Docs Path: nextjs/15
/docs/nextjs/15/features ["nextjs", "15", "features"] Docs Path: nextjs/15/features

πŸ”Ή params.slug will always be an array of strings, representing the URL segments.


πŸ”₯ 2. Optional Catch-All Segments ([[...slug]])

If we want the route to match even when no segments are provided, we use double square brackets ([[...slug]]).

πŸ“Œ Example: Handling /docs as well

app/
β”œβ”€β”€ docs/
β”‚   β”œβ”€β”€ [[...slug]]/
β”‚   β”‚   β”œβ”€β”€ page.tsx

πŸ“Œ app/docs/[[...slug]]/page.tsx

export default function DocsPage({ params }: { params: { slug?: string[] } }) {
  return <h1>Docs: {params.slug ? params.slug.join("/") : "Home"}</h1>;
}

βœ… Difference Between [...] and [[...]]

URL [...] (Required) [[...]] (Optional)
/docs ❌ 404 Error βœ… "Docs: Home"
/docs/nextjs βœ… "Docs: nextjs" βœ… "Docs: nextjs"

πŸ”Ή Use [[...slug]] when the route should work without extra segments.


πŸ”₯ 3. Real-World Example: Breadcrumb Navigation

We can use catch-all segments to generate breadcrumbs dynamically.

πŸ“Œ Example: Breadcrumb Component

export default function Breadcrumbs({ params }: { params: { slug?: string[] } }) {
  const path = params.slug || [];

  return (
    <nav>
      <ul>
        <li><a href="/">Home</a></li>
        {path.map((segment, index) => {
          const href = "/" + path.slice(0, index + 1).join("/");
          return <li key={href}><a href={href}>{segment}</a></li>;
        })}
      </ul>
    </nav>
  );
}

βœ… Visiting /docs/nextjs/15 shows:

Home > docs > nextjs > 15

🎯 Summary

Feature [...] (Required) [[...]] (Optional)
Captures multiple segments βœ… Yes βœ… Yes
Works without segments (/docs) ❌ No (404) βœ… Yes
Returns params.slug as an array βœ… Yes βœ… Yes

πŸ“Œ Use cases:

  • Dynamic documentation pages (/docs/[...slug])
  • E-commerce categories (/products/[...category])
  • Breadcrumb navigation
  • URL rewriting and redirection handling

πŸš€ Different Ways to Implement a notFound Error Page in Next.js 15

In Next.js 15 (App Router), we can implement custom 404 Not Found error pages in different ways, depending on the context. Let's go through each approach in detail.


πŸ”₯ 1. Global not-found.tsx for a Custom 404 Page

If a user visits a non-existing route, we can create a global not-found.tsx inside the app directory.

πŸ“Œ Example: app/not-found.tsx

export default function NotFound() {
  return (
    <div className="flex flex-col items-center justify-center h-screen">
      <h1 className="text-4xl font-bold">404 - Page Not Found</h1>
      <p className="text-lg mt-4">Sorry, we couldn’t find the page you’re looking for.</p>
      <a href="/" className="mt-6 px-4 py-2 bg-blue-600 text-white rounded">Go Home</a>
    </div>
  );
}

βœ… When is this triggered?

  • If the user visits a route that does not exist (/random-page β†’ 404).
  • Works automatically when a page is not found.

πŸ”₯ 2. Programmatically Triggering a 404 Error Inside a Page

We can use Next.js’s built-in notFound() function inside a route to conditionally trigger a 404 page.

πŸ“Œ Example: app/products/[id]/page.tsx

import { notFound } from "next/navigation";

export default function ProductPage({ params }: { params: { id: string } }) {
  const validProducts = ["101", "102", "103"]; // Fake product list
  if (!validProducts.includes(params.id)) {
    notFound(); // Triggers the 404 page
  }

  return <h1>Product ID: {params.id}</h1>;
}

βœ… When is this triggered?

  • If the user visits /products/999 (an invalid product), it will redirect them to the 404 page.

πŸ”₯ 3. Handling 404 Errors in Fetch Requests (Server Components Only)

If we are fetching data from an API, we can return notFound() when no data exists.

πŸ“Œ Example: Fetching user data from an API

import { notFound } from "next/navigation";

async function getUser(id: string) {
  const res = await fetch(`https://jsonplaceholder.typicode.com/users/${id}`);
  if (!res.ok) {
    notFound(); // Redirects to the 404 page if user is not found
  }
  return res.json();
}

export default async function UserPage({ params }: { params: { id: string } }) {
  const user = await getUser(params.id);
  return <h1>User: {user.name}</h1>;
}

βœ… When is this triggered?

  • If we visit /users/999 and the API returns a 404, it will automatically show our custom 404 page.

πŸ”₯ 4. Custom 404 Handling in Layouts (Not Recommended)

We can also handle 404 pages inside layouts by checking the route params, but this is not recommended because it might cause unwanted redirects.

πŸ“Œ Example: app/products/layout.tsx

import { notFound } from "next/navigation";

export default function ProductLayout({ children, params }: { children: React.ReactNode, params: { id: string } }) {
  const validProducts = ["101", "102", "103"];
  if (!validProducts.includes(params.id)) {
    notFound();
  }

  return <>{children}</>;
}

βœ… When is this triggered?

  • If a product is invalid, it redirects all subpages (/products/999) to 404.

🎯 Summary Table

Method Use Case Example
Global not-found.tsx Shows a global 404 page for non-existing routes app/not-found.tsx
notFound() inside a page Conditionally show 404 when data is missing app/products/[id]/page.tsx
API-based 404 Handling If an API request fails, trigger notFound() Fetching user/product data
404 in Layouts (Not Recommended) Handling 404 inside layouts for groups of pages app/products/layout.tsx

πŸš€ Which One Should You Use?

βœ… Use not-found.tsx β†’ For a global 404 page.
βœ… Use notFound() in pages β†’ When handling dynamic routes or data fetching errors.
βœ… Use API-based 404 Handling β†’ When fetching external data and the resource is missing.

πŸ”₯ usePathname Hook in Next.js 15

πŸ“Œ What is usePathname?

usePathname is a React Server Component (RSC) hook in Next.js 15 that lets us access the current URL path in a client component. It’s useful when we need to:
βœ… Show active navigation styles
βœ… Conditionally render UI based on the route
βœ… Generate breadcrumbs
βœ… Perform analytics or logging

πŸ“Œ Importing usePathname

import { usePathname } from "next/navigation";

πŸš€ 1. Basic Usage: Display Current Path

We can use usePathname to get the current URL path and display it in our component.

πŸ“Œ Example: components/CurrentPath.tsx

"use client"; // Required for usePathname

import { usePathname } from "next/navigation";

export default function CurrentPath() {
  const pathname = usePathname();

  return (
    <div>
      <h1>Current Path: {pathname}</h1>
    </div>
  );
}

βœ… Visiting /about will display:

Current Path: /about

πŸš€ 2. Highlight Active Navigation Link

We can use usePathname to apply active styles to the current page link.

πŸ“Œ Example: components/Navbar.tsx

"use client";

import { usePathname } from "next/navigation";
import Link from "next/link";

export default function Navbar() {
  const pathname = usePathname();

  return (
    <nav className="flex space-x-4">
      {["/", "/about", "/contact"].map((path) => (
        <Link
          key={path}
          href={path}
          className={`px-4 py-2 ${
            pathname === path ? "bg-blue-500 text-white" : "text-gray-700"
          }`}
        >
          {path === "/" ? "Home" : path.replace("/", "").toUpperCase()}
        </Link>
      ))}
    </nav>
  );
}

βœ… If we visit /about, the About link is highlighted.

[ Home ]  [ ABOUT ]  [ Contact ]

πŸ”Ή Only the active link gets the blue background.


πŸš€ 3. Breadcrumb Navigation Using usePathname

We can use usePathname to generate breadcrumb links dynamically.

πŸ“Œ Example: components/Breadcrumbs.tsx

"use client";

import { usePathname } from "next/navigation";
import Link from "next/link";

export default function Breadcrumbs() {
  const pathname = usePathname();
  const pathSegments = pathname.split("/").filter(Boolean);

  return (
    <nav className="mt-4">
      <ul className="flex space-x-2">
        <li><Link href="/">Home</Link></li>
        {pathSegments.map((segment, index) => {
          const href = "/" + pathSegments.slice(0, index + 1).join("/");
          return (
            <li key={href}>
              / <Link href={href} className="text-blue-500">{segment}</Link>
            </li>
          );
        })}
      </ul>
    </nav>
  );
}

βœ… Visiting /docs/nextjs/hooks generates:

Home / docs / nextjs / hooks

πŸš€ 4. Redirect Users Based on Pathname

We can use usePathname with useEffect to redirect users under certain conditions.

πŸ“Œ Example: Redirect to login if not authenticated

"use client";

import { usePathname } from "next/navigation";
import { useEffect } from "react";
import { useRouter } from "next/navigation";

export default function AuthRedirect() {
  const pathname = usePathname();
  const router = useRouter();
  const isAuthenticated = false; // Assume user is not logged in

  useEffect(() => {
    if (!isAuthenticated && pathname !== "/#") {
      router.push("/#");
    }
  }, [pathname]);

  return null;
}

βœ… If the user is not logged in and tries to visit /dashboard, they will be redirected to /#.


🎯 Summary Table

Feature Use Case Example
Get Current Path Show path in UI usePathname()
Highlight Active Link Change nav styles Navbar component
Breadcrumbs Generate links from URL /docs/nextjs/hooks β†’ docs > nextjs > hooks
Redirect Users Protect routes Redirect to /# if not authenticated

πŸš€ Final Thoughts

  • βœ… Use usePathname in client components ("use client" is required).
  • βœ… It works well for UI-based logic (menus, breadcrumbs, redirects).
  • ❌ Don’t use it inside Server Components, since it only works in client-side rendering.

πŸ“‚ Private Folders & File Colocation in Next.js 15

Next.js 15 introduces private folders and file colocation as best practices for organizing components, utilities, and other project files. These concepts help improve structure, maintainability, and scalability in Next.js projects. Let’s explore them in detail.


πŸš€ 1. What are Private Folders?

Private folders are directories that Next.js does not treat as routes. They are used to store reusable components, utilities, styles, or constants without exposing them as pages or API routes.

πŸ“Œ How do Private Folders Work?

  • Any folder prefixed with an underscore (_) is ignored by Next.js as a route.
  • Next.js will not generate a route for these folders.
  • This is useful for storing helper functions, components, or shared utilities inside the app directory.

πŸ“Œ Example: Private Folder Structure

app
│── _components/   β†’ (Reusable components, ignored as a route)
β”‚    β”œβ”€β”€ Button.tsx
β”‚    β”œβ”€β”€ Navbar.tsx
│── _utils/        β†’ (Helper functions, ignored as a route)
β”‚    β”œβ”€β”€ formatDate.ts
β”‚    β”œβ”€β”€ fetchData.ts
│── page.tsx       β†’ (Home Page)
│── about/page.tsx β†’ (About Page)

βœ… Files inside _components and _utils will not create pages or routes.

πŸ“Œ Example: Using Private Components in a Page

import Navbar from "../_components/Navbar"; // βœ… Allowed (Not a route)
import { formatDate } from "../_utils/formatDate"; // βœ… Allowed

export default function Home() {
  return (
    <div>
      <Navbar />
      <p>{formatDate(new Date())}</p>
    </div>
  );
}

πŸš€ 2. What is File Colocation?

File colocation is the practice of keeping related files together inside the same directory. This improves maintainability by grouping:
βœ… Components related to a page
βœ… Styles specific to a page
βœ… Utility functions for a page

πŸ“Œ Example: File Colocation in Next.js

app
│── blog/                   β†’ (Blog page route)
β”‚    β”œβ”€β”€ page.tsx           β†’ (Main Blog Page)
β”‚    β”œβ”€β”€ Post.tsx           β†’ (Post Component)
β”‚    β”œβ”€β”€ post.module.css    β†’ (CSS specific to blog posts)
β”‚    β”œβ”€β”€ fetchPosts.ts      β†’ (API function to fetch blog posts)

βœ… Everything related to the blog is inside blog/ instead of spreading files across different directories.

πŸ“Œ Example: Using File Colocation

import Post from "./Post"; // βœ… Colocated Component
import styles from "./post.module.css"; // βœ… Colocated Styles
import { fetchPosts } from "./fetchPosts"; // βœ… Colocated Utility

export default async function BlogPage() {
  const posts = await fetchPosts();

  return (
    <div className={styles.container}>
      {posts.map((post) => (
        <Post key={post.id} title={post.title} />
      ))}
    </div>
  );
}

πŸš€ Why Use Private Folders & File Colocation?

Feature Benefit
Private Folders (_folder) Prevents unintended routing & keeps project clean
File Colocation Improves maintainability by grouping related files together
Performance Keeps imports optimized and reduces unnecessary file lookups
Scalability Easier to manage large projects with well-structured folders

🎯 Summary

  • βœ… Private folders (_components, _utils) prevent Next.js from creating unwanted routes.
  • βœ… File colocation keeps all relevant files in one place, improving project structure.
  • βœ… Best practice: Combine both to create a well-organized Next.js project.

πŸ“‚ Route Groups in Next.js 15

πŸš€ What are Route Groups?

Route Groups in Next.js 15 allow us to organize routes without affecting the URL structure. They help in:
βœ… Structuring large projects
βœ… Grouping related pages
βœ… Improving code maintainability
βœ… Keeping URLs clean (the group name does not appear in the URL)


πŸ“Œ How to Create Route Groups?

  • Route groups are created by wrapping a folder name inside parentheses (group-name).
  • Next.js ignores the group name in the URL, but the folder structure helps organize the project.

πŸš€ 1. Basic Route Group Example

πŸ“Œ Folder Structure

app
│── (marketing)/      β†’ (Route Group for marketing pages)
β”‚    β”œβ”€β”€ about/page.tsx  β†’ (Accessible at `/about`)
β”‚    β”œβ”€β”€ contact/page.tsx β†’ (Accessible at `/contact`)
│── (dashboard)/      β†’ (Route Group for dashboard pages)
β”‚    β”œβ”€β”€ page.tsx β†’ (Accessible at `/dashboard`)
β”‚    β”œβ”€β”€ settings/page.tsx β†’ (Accessible at `/dashboard/settings`)
│── page.tsx β†’ (Home Page `/`)

βœ… Even though about and contact are inside (marketing),
they are accessible at /about and /contact, not /marketing/about.

βœ… The route group name is ignored in the URL.


πŸš€ 2. Using Route Groups for Layouts

Each route group can have its own layout to wrap pages under it.

πŸ“Œ Folder Structure

app
│── (dashboard)/ 
β”‚    β”œβ”€β”€ layout.tsx      β†’ (Layout for dashboard pages)
β”‚    β”œβ”€β”€ page.tsx        β†’ (Accessible at `/dashboard`)
β”‚    β”œβ”€β”€ settings/page.tsx  β†’ (Accessible at `/dashboard/settings`)
│── page.tsx β†’ (Home Page `/`)

πŸ“Œ dashboard/layout.tsx

export default function DashboardLayout({ children }: { children: React.ReactNode }) {
  return (
    <div className="dashboard-layout">
      <nav>Dashboard Navbar</nav>
      <main>{children}</main>
    </div>
  );
}

βœ… All pages inside (dashboard) will use this layout.
βœ… Visiting /dashboard/settings automatically includes the Dashboard layout.


πŸš€ 3. Route Groups for Authentication (Protect Routes)

We can separate protected routes (dashboard, admin panel) from public routes (home, about).

πŸ“Œ Folder Structure

app
│── (public)/ 
β”‚    β”œβ”€β”€ page.tsx  β†’ (Accessible at `/`)
β”‚    β”œβ”€β”€ about/page.tsx  β†’ (Accessible at `/about`)
│── (auth)/ 
β”‚    β”œβ”€β”€ login/page.tsx  β†’ (Accessible at `/#`)
β”‚    β”œβ”€β”€ register/page.tsx β†’ (Accessible at `/register`)
│── (dashboard)/ 
β”‚    β”œβ”€β”€ layout.tsx  β†’ (Protected Layout)
β”‚    β”œβ”€β”€ page.tsx  β†’ (Accessible at `/dashboard`)
β”‚    β”œβ”€β”€ settings/page.tsx  β†’ (Accessible at `/dashboard/settings`)

πŸ“Œ Protect Dashboard Pages in dashboard/layout.tsx

import { redirect } from "next/navigation";

export default function DashboardLayout({ children }: { children: React.ReactNode }) {
  const isAuthenticated = false; // Assume user is not logged in

  if (!isAuthenticated) {
    redirect("/#"); // Redirect to login if not authenticated
  }

  return <div className="dashboard-layout">{children}</div>;
}

βœ… If the user is not logged in, they are redirected to /#.
βœ… The public and auth pages remain accessible to everyone.


πŸš€ 4. API Routes with Route Groups

Route groups also work with API routes inside the app/api/ directory.

πŸ“Œ Folder Structure

app
│── (api)/
β”‚    β”œβ”€β”€ users/route.ts β†’ (API at `/api/users`)
β”‚    β”œβ”€β”€ posts/route.ts β†’ (API at `/api/posts`)

πŸ“Œ Example: app/(api)/users/route.ts

export async function GET() {
  return Response.json([{ id: 1, name: "John Doe" }]);
}

βœ… Accessible at /api/users
βœ… The (api) group is ignored in the URL.


🎯 Key Takeaways

Feature Benefit
Route Groups Organize files without affecting URLs
Clean URLs Folder names in ( ) are ignored in the final route
Scoped Layouts Different layouts for different route groups
Auth Separation Public, private, and API routes can be structured cleanly
API Routes Route groups also work for API endpoints

πŸš€ Final Thoughts

  • βœ… Use Route Groups to structure large Next.js apps without messing up URLs.
  • βœ… Great for organizing dashboard layouts, public/auth pages, and API routes.
  • βœ… Easy to separate concerns while keeping a clean URL structure.

πŸ“‚ Everything About layout.tsx & Layouts in Next.js 15

πŸš€ What is layout.tsx in Next.js 15?

In Next.js 15, layout.tsx is used to define persistent UI elements that wrap around multiple pages.
Layouts help us avoid code duplication and keep a consistent structure across multiple pages.


πŸ“Œ 1. How Do Layouts Work in Next.js?

  • Any layout.tsx file inside a folder automatically wraps all pages inside that folder.
  • Layouts can be nested, meaning child layouts inherit parent layouts.
  • Useful for headers, sidebars, authentication layouts, dashboards, and more.

πŸš€ 2. Basic Layout Example

πŸ“Œ Folder Structure

app
│── layout.tsx  β†’ (Global Layout for the entire app)
│── page.tsx  β†’ (Home Page)
│── about/page.tsx  β†’ (About Page)
│── dashboard/
β”‚   β”œβ”€β”€ layout.tsx  β†’ (Dashboard-specific Layout)
β”‚   β”œβ”€β”€ page.tsx  β†’ (Dashboard Home)
β”‚   β”œβ”€β”€ settings/page.tsx  β†’ (Dashboard Settings)

βœ… layout.tsx inside app/ applies to the entire app.
βœ… layout.tsx inside dashboard/ only applies to dashboard pages.


πŸš€ 3. Creating a Global Layout (app/layout.tsx)

πŸ“Œ Example: app/layout.tsx

export default function RootLayout({ children }: { children: React.ReactNode }) {
  return (
    <html lang="en">
      <body>
        <header>🌟 My Website Header 🌟</header>
        <main>{children}</main>
        <footer>Β© 2025 My Website</footer>
      </body>
    </html>
  );
}

βœ… Applies to all pages (page.tsx) inside app/.
βœ… Ensures all pages have the same header and footer.


πŸš€ 4. Nested Layouts (Scoped to a Folder)

  • A layout.tsx inside a folder only applies to pages within that folder.
  • Child layouts inherit parent layouts automatically.

πŸ“Œ Example: app/dashboard/layout.tsx

export default function DashboardLayout({ children }: { children: React.ReactNode }) {
  return (
    <div className="dashboard-container">
      <nav>πŸ“Œ Dashboard Sidebar</nav>
      <section>{children}</section>
    </div>
  );
}

βœ… This layout wraps all pages in app/dashboard/.
βœ… Visiting /dashboard or /dashboard/settings includes this layout.


πŸš€ 5. Nested Layout Inheritance

πŸ“Œ Folder Structure

app
│── layout.tsx  β†’ (Global Layout)
│── page.tsx  β†’ (Home Page)
│── dashboard/
β”‚   β”œβ”€β”€ layout.tsx  β†’ (Dashboard Layout)
β”‚   β”œβ”€β”€ page.tsx  β†’ (Dashboard Home)
β”‚   β”œβ”€β”€ settings/
β”‚       β”œβ”€β”€ layout.tsx  β†’ (Settings Layout)
β”‚       β”œβ”€β”€ page.tsx  β†’ (Settings Page)

πŸ“Œ Layouts Applied

Route Layout Applied
/ app/layout.tsx
/dashboard app/layout.tsx + app/dashboard/layout.tsx
/dashboard/settings app/layout.tsx + app/dashboard/layout.tsx + app/dashboard/settings/layout.tsx

πŸš€ 6. Passing Props to Layouts

Layouts receive a children prop, but we can also pass additional props.

πŸ“Œ Example: Theme Prop in layout.tsx

export default function RootLayout({ children }: { children: React.ReactNode }) {
  const theme = "dark"; // Example: Theme state
  
  return (
    <html lang="en">
      <body className={theme}>
        {children}
      </body>
    </html>
  );
}

βœ… Useful for themes, authentication state, and global context providers.


πŸš€ 7. Using Providers in Layouts

  • Layouts are great places to wrap our app with context providers (e.g., Theme, Auth).
  • This ensures all pages inside the layout have access to these providers.

πŸ“Œ Example: Wrapping Layout with Auth Provider

import { AuthProvider } from "@/context/AuthContext";

export default function RootLayout({ children }: { children: React.ReactNode }) {
  return (
    <html lang="en">
      <body>
        <AuthProvider>
          {children} {/* All pages inside get access to Auth */}
        </AuthProvider>
      </body>
    </html>
  );
}

βœ… All pages can now access authentication state from AuthContext.


πŸš€ 8. Handling Metadata in Layouts

Layouts can define metadata for all pages inside them using generateMetadata.

πŸ“Œ Example: Adding Metadata in layout.tsx

export const metadata = {
  title: "My Next.js App",
  description: "This is an amazing Next.js app",
};

export default function RootLayout({ children }: { children: React.ReactNode }) {
  return (
    <html lang="en">
      <body>{children}</body>
    </html>
  );
}

βœ… All pages inside this layout automatically get this metadata.


🎯 Key Takeaways

Feature Benefit
Global Layout (app/layout.tsx) Wraps the entire app (header, footer, themes)
Nested Layouts Scoped layouts for dashboards, settings, etc.
Layout Inheritance Child layouts inherit parent layouts automatically
Context Providers in Layouts Manage authentication, themes, state globally
Metadata in Layouts Define SEO-friendly metadata for all pages in a layout

πŸš€ Final Thoughts

  • βœ… layout.tsx helps us structure UI consistently across multiple pages.
  • βœ… Nested layouts allow different sections to have unique designs.
  • βœ… Great for global providers, authentication, and theming.

Absolutely! Let’s deep dive into Routing Metadata in Next.js 15, which is an essential part of modern SEO, social sharing, accessibility, and performance.


🧠 What is Routing Metadata in Next.js 15?

Metadata in Next.js 15 refers to extra information (like title, description, Open Graph data, Twitter cards, theme color, etc.) that gets injected into your HTML <head> section on a per-page or per-route basis. This is done using either a static metadata object or a dynamic generateMetadata() function.

It helps search engines, social platforms, and browsers understand and display your pages properly.


πŸ“¦ Where Do We Use Metadata?

You can define metadata in:

  • app/layout.tsx β†’ Applies to all routes.
  • app/page.tsx β†’ Applies only to a single route/page.
  • app/blog/[slug]/page.tsx β†’ Dynamically generate metadata per route.
  • Nested layouts can also define their own metadata.

✍️ Basic Example: Static Metadata

app/page.tsx

export const metadata = {
  title: "Home | My Awesome App",
  description: "This is the homepage of our cool app.",
};

export default function HomePage() {
  return <h1>Welcome to the Home Page</h1>;
}

βœ… This will inject:

<title>Home | My Awesome App</title>
<meta name="description" content="This is the homepage of our cool app." />

πŸ” Dynamic Metadata using generateMetadata()

When you need metadata that depends on dynamic data (like a blog post title from a CMS or DB), you use generateMetadata().

Example: app/blog/[slug]/page.tsx

type Props = {
  params: { slug: string }
}

export async function generateMetadata({ params }: Props) {
  const post = await getPostData(params.slug);

  return {
    title: `${post.title} | My Blog`,
    description: post.summary,
    openGraph: {
      title: post.title,
      description: post.summary,
      images: [post.coverImage],
    },
  };
}

export default function BlogPostPage({ params }: Props) {
  return <div>Blog post content for {params.slug}</div>;
}

βœ… This lets us dynamically set metadata for each blog post!


🌐 Types of Metadata You Can Define

Metadata Field Purpose
title Sets the HTML <title>
description Meta description tag
keywords SEO keywords (less used today)
robots Controls crawling/indexing
themeColor Sets browser theme color
viewport Controls mobile scaling
openGraph Metadata for social sharing (FB, LinkedIn)
twitter Twitter-specific metadata
icons App icons
appleWebApp, manifest, archives, etc. PWA-related and advanced options

🧠 Metadata Inheritance

Just like layouts, metadata declared in a parent layout is inherited by children, unless overridden.

Example:

app/layout.tsx

export const metadata = {
  title: "My App",
  description: "A universal layout for all routes",
};

Now, any page.tsx under this layout inherits this metadata unless it overrides it.


⚑ Tip: generateStaticParams + generateMetadata

These two often work together for static site generation (SSG) of dynamic routes.

export async function generateStaticParams() {
  const posts = await fetchAllPosts();
  return posts.map(post => ({ slug: post.slug }));
}

Then use generateMetadata() to define metadata for each slug!


πŸ›  Real-World Use Case Example

You're building an eCommerce product page.

app/products/[productId]/page.tsx

export async function generateMetadata({ params }) {
  const product = await fetchProductById(params.productId);
  
  return {
    title: `${product.name} - Buy Now!`,
    description: product.description,
    openGraph: {
      title: product.name,
      description: product.description,
      images: [product.imageUrl],
    },
    twitter: {
      card: "summary_large_image",
      title: product.name,
      description: product.description,
      images: [product.imageUrl],
    }
  };
}

βœ… This makes your product page SEO-friendly and social-media-ready πŸŽ‰


πŸ§ͺ Pro Tips

  • We cannot use hooks (like useState or useEffect) inside generateMetadata(), since it runs on the server.
  • Keep metadata as lightweight and static as possible unless you really need dynamic fetching.
  • Store reusable metadata templates in a utility file for DRYness.

🧭 Summary

Feature Explanation
metadata Static metadata object
generateMetadata() Dynamic metadata based on route params or fetched data
Layout metadata Inherited by child routes
Dynamic routes Supported with metadata via params
SEO & social Supports OG tags, Twitter cards, canonical tags

Here’s a complete, ready-to-use template for handling metadata in a Next.js 15 project β€” including static and dynamic metadata with Open Graph and Twitter support, ideal for SEO, PWA, and social sharing.


🧱 Project Structure

Let’s assume the following structure:

app/
β”œβ”€β”€ layout.tsx         ← Root layout (with global metadata)
β”œβ”€β”€ page.tsx           ← Homepage
β”œβ”€β”€ about/
β”‚   └── page.tsx       ← Static metadata example
β”œβ”€β”€ blog/
β”‚   └── [slug]/
β”‚       └── page.tsx   ← Dynamic metadata example

1️⃣ Root Metadata in layout.tsx

This applies globally to all routes unless overridden.

// app/layout.tsx
import "./globals.css";
import type { Metadata } from "next";

export const metadata: Metadata = {
  title: {
    default: "MyApp",
    template: "%s | MyApp",
  },
  description: "A modern web app built with Next.js 15",
  keywords: ["Next.js", "Web App", "SEO", "React"],
  authors: [{ name: "Skyy Banerjee", url: "https://skyybbanerjee.dev" }],
  icons: {
    icon: "/favicon.ico",
  },
  themeColor: "#ffffff",
  openGraph: {
    type: "website",
    url: "https://myapp.com",
    title: "MyApp",
    description: "Explore the best modern web experience",
    siteName: "MyApp",
    images: [
      {
        url: "/og-image.png",
        width: 1200,
        height: 630,
      },
    ],
  },
  twitter: {
    card: "summary_large_image",
    title: "MyApp",
    description: "Explore the best modern web experience",
    images: ["/og-image.png"],
    creator: "@skyybbanerjee",
  },
};

export default function RootLayout({ children }: { children: React.ReactNode }) {
  return (
    <html lang="en">
      <body>{children}</body>
    </html>
  );
}

2️⃣ Static Metadata Example: about/page.tsx

// app/about/page.tsx
export const metadata = {
  title: "About Us",
  description: "Learn more about our mission and team.",
};

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

3️⃣ Dynamic Metadata Example: blog/[slug]/page.tsx

// app/blog/[slug]/page.tsx

type Props = {
  params: { slug: string };
};

async function getPostData(slug: string) {
  // Simulate DB or CMS call
  return {
    title: `Blog: ${slug.replace("-", " ")}`,
    description: "This is a dynamic blog post.",
    imageUrl: `https://myapp.com/images/${slug}.jpg`,
  };
}

export async function generateMetadata({ params }: Props) {
  const post = await getPostData(params.slug);

  return {
    title: post.title,
    description: post.description,
    openGraph: {
      title: post.title,
      description: post.description,
      images: [post.imageUrl],
    },
    twitter: {
      card: "summary_large_image",
      title: post.title,
      description: post.description,
      images: [post.imageUrl],
    },
  };
}

export default function BlogPost({ params }: Props) {
  return <div>This is the blog post for: {params.slug}</div>;
}

πŸ§ͺ Bonus Tip: Set robots Metadata

To control indexing:

export const metadata = {
  robots: {
    index: true,
    follow: true,
    googleBot: {
      index: true,
      follow: true,
      noimageindex: false,
    },
  },
};

We can scale this template into anything we need! πŸš€

Absolutely Skyy! Let’s dive into the Link component in Next.js 15 β€” a core feature for client-side navigation in your app. It’s lightweight, fast, and integrated with the new App Router system.


πŸ”— What is the Link Component?

In Next.js 15, the Link component (from next/link) is used for navigating between routes. It enables client-side navigation, meaning that clicking a link doesn’t cause a full page reload β€” just the necessary route is loaded using JavaScript. This leads to faster page transitions and preserves state like scroll position (if configured).


πŸ“¦ Where is it imported from?

import Link from 'next/link';

🧱 Basic Usage

<Link href="/about">About Us</Link>

This will render:

<a href="/about">About Us</a>

βœ… React handles routing without reloading the browser.


🧠 Under the Hood

The Link component:

  • Uses the HTML <a> tag under the hood.
  • Intercepts clicks and loads pages via client-side navigation.
  • Automatically prefetches the linked page in the background (when visible in viewport).
  • Is compatible with dynamic routes, Route Groups, Catch-all routes, and Layouts in the App Router.

πŸ’‘ Features of Link

Feature Description
href Required. Route path or URL to navigate to.
prefetch Prefetch the linked page in the background. Enabled by default in production.
replace Replaces history instead of pushing (like router.replace())
scroll Scroll to top on navigation (default: true)
as Optional. Used in dynamic routes to mask the URL
Nested elements Now allowed! You can wrap complex JSX inside Link

🧩 Example: Wrapping Elements

You can now do this (allowed in App Router):

<Link href="/products/shoes">
  <div className="p-4 rounded hover:bg-gray-100">
    <h2>Shoes</h2>
    <p>See our latest shoes collection.</p>
  </div>
</Link>

βœ… This was not possible in earlier versions unless you manually wrapped it with <a>.


πŸš€ Dynamic Routes with Link

For a dynamic route like /blog/[slug]:

<Link href={`/blog/${post.slug}`}>{post.title}</Link>

You can also pass an object:

<Link href={{ pathname: '/blog/[slug]', query: { slug: post.slug } }}>
  {post.title}
</Link>

βš™οΈ replace vs push

By default, navigation pushes to browser history. If you want to replace the current route:

<Link href="/dashboard" replace>Go to Dashboard</Link>

⏳ Disable Prefetching (Optional)

By default, prefetching is enabled in production. You can disable it:

<Link href="/contact" prefetch={false}>Contact</Link>

βœ… This saves bandwidth if the route is unlikely to be visited.


πŸ” Scroll Behavior

If you don’t want to scroll to top on navigation:

<Link href="/faq" scroll={false}>FAQ</Link>

🌐 External Links

Use plain <a> tags for external links:

<a href="https://github.com/skyybbanerjee" target="_blank" rel="noopener noreferrer">
  GitHub
</a>

πŸ”’ Always use rel="noopener noreferrer" for security with target="_blank".


πŸ” Link with Active Styling (Custom Highlighting)

You can highlight the current link manually using usePathname():

"use client";
import Link from "next/link";
import { usePathname } from "next/navigation";

export default function NavLink({ href, children }: { href: string, children: React.ReactNode }) {
  const pathname = usePathname();
  const isActive = pathname === href;

  return (
    <Link href={href} className={isActive ? "text-blue-600 font-bold" : "text-gray-700"}>
      {children}
    </Link>
  );
}

🧠 Summary

Prop Description
href Path to navigate to
replace Replaces the current history entry
prefetch Enables/disables preloading the route
scroll Controls scroll-to-top behavior
as Used for masking dynamic URLs
children Can be text, JSX, or elements (like cards)

🧠 Understanding params and searchParams in Next.js 15

Next.js 15, like 13/14 in the App Router, uses file-based routing and introduces React Server Components (RSC) by default.

When a page or layout is rendered, we can access:

βœ… params

  • Comes from dynamic segments in the file system.
  • Example: In app/articles/[articleId]/page.tsx, articleId is a param.

βœ… searchParams

  • Comes from the URL query string, like ?lang=en.
  • These don’t require any special [param] file setup β€” they’re parsed automatically.

βœ… Usage in Server vs Client Components

In Server Components:

You can use them directly as props in your page or layout:

export default function Page({ params, searchParams }) {
  console.log(params.articleId);
  console.log(searchParams.lang);
}

In Client Components:

Since Next.js doesn’t allow async props by default, we wrap them in a Promise and use the use() hook to resolve them.


πŸ”Ž Let's Understand our Two Files

### βœ… 1. Home.tsx – Server Component

<Link href="/articles/breaking-news-123?lang=en">Read in ENGLISH</Link>
<Link href="/articles/breaking-news-123?lang=fr">Lire en FRANÇAIS</Link>

Here:

  • We're linking to a dynamic route like /articles/[articleId]
  • breaking-news-123 β†’ becomes a param (articleId)
  • ?lang=en β†’ becomes a search param

βœ… 2. NewsArticle.tsx – Client Component

"use client";

import { use } from "react"; // for async props

function NewsArticle({
  params,
  searchParams,
}: {
  params: Promise<{ articleId: string }>;
  searchParams: Promise<{ lang?: "en" | "es" | "fr" | "de" }>;
}) {
  const { articleId } = use(params); // resolves to { articleId: "breaking-news-123" }
  const { lang = "en" } = use(searchParams); // resolves to { lang: "fr" } etc.

We're correctly:

  • Using the use() hook to await async params and searchParams props.
  • Getting the articleId from the route: /articles/[articleId]
  • Getting the lang from query string like ?lang=en

So:

URL params.articleId searchParams.lang
/articles/breaking-news-123?lang=en "breaking-news-123" "en"
/articles/breaking-news-123?lang=fr "breaking-news-123" "fr"

πŸ“¦ Behind the Scenes: How Next.js Handles It

  • In our route folder:
    app/articles/[articleId]/page.tsx
    or
    app/articles/[articleId]/NewsArticle.tsx (if used as client comp)

Next.js automatically passes:

params = { articleId: "..." }
searchParams = { lang: "..." }

If you're using a client component here, the props are promises. That’s why you use:

const { articleId } = use(params); 
const { lang } = use(searchParams);

πŸ’‘ Bonus Tips

πŸ’¬ You could also do:

If you don’t need to support all languages:

const supportedLangs = ["en", "fr", "es", "de"];
const { lang = "en" } = use(searchParams);

if (!supportedLangs.includes(lang)) {
  // show 404 or redirect
}

βœ… Summary

Concept Meaning Where it comes from
params Values from [param] in file structure URL path
searchParams Values from ?key=value in URL query URL query string
use() hook Needed in Client Components to resolve props params and searchParams

🧭 What is Programmatic Navigation?

Instead of using <Link href="...">, programmatic navigation lets us navigate between routes using JavaScript logic, like:

  • After a form submission βœ…
  • On button click βœ…
  • Based on conditions or auth βœ…

βœ… How to Do Programmatic Navigation in Next.js 15

In the App Router, we use the useRouter() hook from next/navigation inside Client Components to navigate programmatically.

1. Import the hook

"use client"; // must be a client component

import { useRouter } from "next/navigation";

2. Use router.push() to navigate

"use client";

import { useRouter } from "next/navigation";

export default function MyComponent() {
  const router = useRouter();

  const goToLogin = () => {
    router.push("/#");
  };

  return <button onClick={goToLogin}>Go to Login</button>;
}

🧠 Methods on router

Method Description
router.push(url) Navigate to a new route (adds to history stack)
router.replace(url) Navigate without keeping the previous route in history (used for redirects)
router.refresh() Re-fetches data and re-renders current route
router.back() Same as browser back button
router.forward() Same as browser forward button

πŸ” Example: Redirect after form submission

"use client";

import { useRouter } from "next/navigation";

export default function RegisterForm() {
  const router = useRouter();

  const handleSubmit = async (e) => {
    e.preventDefault();
    // Do API call or logic
    await new Promise((r) => setTimeout(r, 1000)); // fake delay

    // Navigate to dashboard after success
    router.push("/dashboard");
  };

  return (
    <form onSubmit={handleSubmit}>
      <input type="text" placeholder="Name" />
      <button type="submit">Register</button>
    </form>
  );
}

πŸ”’ Conditional Redirect (like auth)

"use client";

import { useEffect } from "react";
import { useRouter } from "next/navigation";

export default function ProtectedPage() {
  const router = useRouter();
  const isLoggedIn = false; // assume from context/store

  useEffect(() => {
    if (!isLoggedIn) {
      router.replace("/#");
    }
  }, [isLoggedIn]);

  return <div>Welcome to Protected Page</div>;
}

πŸ’‘ Bonus: Pass searchParams dynamically

router.push(`/profile/${userId}?ref=welcome`);

🚫 Note

  • Only use useRouter() inside client components.
  • Make sure to add "use client" at the top of the file.

βœ… Summary

Feature Use
router.push() Navigate forward
router.replace() Replace history entry
router.back() Go back
router.refresh() Refresh current route

In Next.js 15 (App Router), we have two powerful file types for structuring our UI: layout.tsx and template.tsx. While they may look similar, their behavior and purpose are very different.

Let’s break this down in full detail. πŸ‘‡


πŸ—οΈ What is layout.tsx?

layout.tsx is used to wrap pages and components with a consistent UI shellβ€”for example, a navbar, footer, sidebar, etc.

βœ… Key Characteristics of layout.tsx

  • It is shared and persistent across navigation.
  • It does not remount when navigating between sibling routes.
  • It is ideal for:
    • Headers
    • Sidebars
    • Persistent navigation
    • Keeping state (like dark mode toggle, open sidebar)

🧠 Example:

// app/dashboard/layout.tsx
export default function DashboardLayout({ children }: { children: React.ReactNode }) {
  return (
    <div>
      <Sidebar />
      <main>{children}</main>
    </div>
  );
}

βœ… Behavior:

  • When we navigate from /dashboard/analytics to /dashboard/settings, the layout doesn't rerender. It persists.
  • Useful for layouts that manage state (like sidebar toggle).

πŸ†• What is template.tsx?

template.tsx is a new concept in the App Router that renders a fresh instance on every navigation to that segment.

βœ… Key Characteristics of template.tsx

  • It remounts on every navigation.
  • It’s not shared, unlike layout.tsx.
  • Use it when you want a clean slate every time the route loads.
  • Good for modals, loading animations, one-time transitions, etc.

🧠 Example:

// app/shop/@modal/template.tsx
export default function ModalTemplate({ children }: { children: React.ReactNode }) {
  return (
    <div className="modal-overlay">
      <div className="modal-content">{children}</div>
    </div>
  );
}

βœ… Behavior:

  • Every time you go from one route to another using this template.tsx, it will remount all children, giving you a fresh instance.

🧬 Comparison Table

Feature layout.tsx template.tsx
🧠 Shared Across Routes βœ… Yes ❌ No (fresh on each route)
πŸ” Remounts on Nav ❌ No βœ… Yes
πŸ’Ύ Keeps State βœ… Yes ❌ No (resets every time)
🎯 Use Case Persistent UI (navbars, sidebars, etc.) Transient UI (modals, animations, etc.)
🧩 Memory Optimization Good for keeping components alive Good for short-lived dynamic content

πŸ”₯ Real-world Use Cases

Use Case Use layout.tsx? Use template.tsx?
Navbar + Sidebar Shell βœ… Yes ❌ No
Dashboard Tabs with local state βœ… Yes ❌ No
Modals that open on different routes ❌ No βœ… Yes
Step-by-step multi-page form ❌ No βœ… Yes (reset state each time)
Transitions/Animations between views ❌ No βœ… Yes

πŸ’‘ Pro Tip:

We can use both in the same folder structure!

app/
 └── dashboard/
     β”œβ”€β”€ layout.tsx      βœ… Shared layout
     β”œβ”€β”€ template.tsx    βœ… Fresh template on each visit
     β”œβ”€β”€ page.tsx
     └── settings/
         └── page.tsx

So when you go from /dashboard β†’ /dashboard/settings, the layout.tsx stays, but the template.tsx will re-render.


βœ… Final Summary

Aspect layout.tsx template.tsx
Shared across pages? βœ… Yes ❌ No
Remounts on nav? ❌ No βœ… Yes
Keeps state? βœ… Yes ❌ No
Use for? Layout shell, persistent UI Dynamic UIs, modals, animations

In Next.js 15, error handling is built-in and improved thanks to the App Router architecture. Here’s a detailed breakdown of all the ways we can handle errors in a Next.js 15 project, both server-side and client-side.


🧭 1. Route-Level error.tsx File (App Router Specific)

βœ… What it does:

Catches rendering or async errors inside the route segment it belongs to.

βœ… Structure:

app/
β”œβ”€β”€ dashboard/
β”‚   β”œβ”€β”€ page.tsx
β”‚   β”œβ”€β”€ error.tsx πŸ‘ˆ

βœ… Example:

// app/dashboard/error.tsx
'use client';

export default function ErrorPage({ error, reset }: {
  error: Error;
  reset: () => void;
}) {
  return (
    <div>
      <h2>Something went wrong in Dashboard!</h2>
      <p>{error.message}</p>
      <button onClick={() => reset()}>Try Again</button>
    </div>
  );
}
  • error: the caught error.
  • reset(): re-attempts rendering (good for retry logic).

🌐 2. Global Error Handling with app/error.tsx

Catches unhandled errors in the whole app.

// app/error.tsx
'use client';

export default function GlobalError({ error, reset }: {
  error: Error;
  reset: () => void;
}) {
  return (
    <div>
      <h1>App crashed 😒</h1>
      <p>{error.message}</p>
      <button onClick={reset}>Retry</button>
    </div>
  );
}

πŸ” 3. try/catch Blocks in Server Components / Route Handlers

When using server functions or API routes, handle expected errors explicitly.

βœ… Example in Route Handler:

// app/api/data/route.ts
import { NextResponse } from 'next/server';

export async function GET() {
  try {
    const data = await fetchData();
    return NextResponse.json(data);
  } catch (error) {
    return NextResponse.json(
      { message: 'Something went wrong' },
      { status: 500 }
    );
  }
}

🚫 4. 404 (Not Found) Handling

A. Static NotFound Page

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

B. Programmatic notFound() function

import { notFound } from 'next/navigation';

export default function Page({ params }) {
  if (params.id !== 'valid-id') {
    notFound(); // Triggers the 404 page
  }

  return <div>Valid Page</div>;
}

πŸ”„ 5. Client Components: ErrorBoundary (React standard)

For errors in interactive client components.

βœ… Setup your own ErrorBoundary:

// components/ErrorBoundary.tsx
'use client';

import React from 'react';

class ErrorBoundary extends React.Component<
  { children: React.ReactNode },
  { hasError: boolean }
> {
  constructor(props: any) {
    super(props);
    this.state = { hasError: false };
  }

  static getDerivedStateFromError(error: Error) {
    return { hasError: true };
  }

  render() {
    if (this.state.hasError) {
      return <h2>Client UI broke πŸ˜”</h2>;
    }
    return this.props.children;
  }
}

export default ErrorBoundary;

βœ… Usage:

<ErrorBoundary>
  <SomeClientComponent />
</ErrorBoundary>

πŸ§ͺ 6. Handling Errors in Actions (app/actions.ts)

When using form actions or server actions:

'use server';

export async function submitData(formData: FormData) {
  try {
    // Do something
  } catch (err) {
    throw new Error("Submission failed!");
  }
}

This will bubble up to error.tsx or be caught in the calling component.


πŸ” 7. 404 + 500 Pages (Static Fallback for Pages Router)

For hybrid apps using both App Router and Pages Router:

pages/
β”œβ”€β”€ 404.tsx
β”œβ”€β”€ 500.tsx

βœ… 404:

export default function Custom404() {
  return <h1>Oops! This page doesn’t exist.</h1>;
}

βœ… 500:

export default function Custom500() {
  return <h1>Server-side error occurred!</h1>;
}

πŸ” BONUS: Edge Cases + Tips

Situation Use This
A component crashes (render error) error.tsx or ErrorBoundary
Dynamic route not found notFound()
Data fetch fails try/catch + error.tsx
Invalid user input throw new Error() (handled by layout/template or form UI)
Programmatic redirection redirect('/some-path')

βœ… Summary

Method Scope Usage
error.tsx Route segment Catches render errors in that route
app/error.tsx Global Catches all uncaught errors
notFound() Server components Manually trigger 404 page
ErrorBoundary Client components Catches UI errors only in client parts
try/catch API / server logic Handle and format expected errors
Custom 404/500 Pages Router Fallback UI for pages (hybrid apps)

πŸ’‘ What Are Parallel Routes?

Deep into Parallel Routes in Next.js 15:. This is a powerful advanced feature in the App Router that helps us render multiple pages or sections in parallelβ€”within the same layoutβ€”without affecting each other.

Parallel Routes allow us to display multiple route segments in the same layout at the same time, where each segment is independent, like:

  • A sidebar and a main content area
  • Multiple tabs rendered simultaneously
  • Dashboards with multiple panels

πŸ“ Folder Structure and Concept

In Next.js 15, named slots using the @ symbol are used for parallel routing.

Example folder structure:

app/
β”œβ”€β”€ layout.tsx
β”œβ”€β”€ @sidebar/
β”‚   └── page.tsx
β”œβ”€β”€ @main/
β”‚   └── page.tsx

Note: @sidebar and @main are slot names. They do not define the route, but instead define what content goes into that slot.


🧠 How It Works

In the layout file (layout.tsx), we can accept named slots as props:

// app/layout.tsx
export default function RootLayout({
  children,
  sidebar,
  main,
}: {
  children: React.ReactNode;
  sidebar: React.ReactNode;
  main: React.ReactNode;
}) {
  return (
    <div className="flex">
      <aside className="w-1/4 p-4 border-r">{sidebar}</aside>
      <main className="w-3/4 p-4">{main}</main>
    </div>
  );
}
  • sidebar: will get content from the @sidebar route.
  • main: will get content from the @main route.

So, both routes can be rendered in parallel under the same layout.


🧭 How to Navigate Between Parallel Routes

You define a URL structure like this:

/dashboard?parallelRoute=main

But usually, it’s used with segment config and navigation links, like:

<Link href="/dashboard" scroll={false}>
  Dashboard Home
</Link>

And behind the scenes, Next.js renders both @sidebar and @main in their slots, based on the file structure.


πŸ§ͺ Use Case: Dashboard with Tabs

Folder structure:

app/
β”œβ”€β”€ dashboard/
β”‚   β”œβ”€β”€ layout.tsx
β”‚   β”œβ”€β”€ @overview/
β”‚   β”‚   └── page.tsx
β”‚   β”œβ”€β”€ @settings/
β”‚   β”‚   └── page.tsx

layout.tsx

// app/dashboard/layout.tsx
export default function DashboardLayout({
  overview,
  settings,
}: {
  overview: React.ReactNode;
  settings: React.ReactNode;
}) {
  return (
    <div>
      <nav>
        <Link href="/dashboard">Overview</Link>
        <Link href="/dashboard/settings">Settings</Link>
      </nav>
      <div className="grid grid-cols-2">
        <section>{overview}</section>
        <section>{settings}</section>
      </div>
    </div>
  );
}

πŸ” Shared Layouts and Lazy Rendering

Parallel routes help us:

  • Reuse layout while keeping each section isolated
  • Avoid unmounting components during navigation
  • Improve performance by lazy loading segments independently

βš™οΈ Bonus: Default and Error UI for Parallel Routes

🟑 Default UI (default.tsx)

If a parallel route is not loaded, Next.js will look for:

app/
β”œβ”€β”€ @main/
β”‚   └── default.tsx

This acts like a fallback or placeholder UI until the actual page is rendered.


πŸ”΄ Error Handling

Each slot can have its own:

  • error.tsx
  • loading.tsx
  • not-found.tsx

So if just the sidebar crashes, only it is replaced with its error.tsx componentβ€”the rest of the layout stays intact!


βœ… Benefits of Parallel Routes

Feature Benefit
Named slots Organize layout clearly
Independent rendering Better performance, less flicker
Fallback UIs Handle partial loading gracefully
Error isolation One section error doesn't crash the whole page
Smooth UX Useful for dashboards, split screens, modals, tabs

πŸ’¬ Summary

  • Parallel Routes = multiple independent route trees rendered inside named slots (@name) in the same layout.
  • Perfect for dashboards, sidebars, tabbed interfaces, modals, etc.
  • Easy to scale, clean layout-based architecture.
  • Use default.tsx, loading.tsx, and error.tsx for graceful UX in each segment.

🧠 What Are Unmatched Routes in Next.js 15?

Unmatched Routes in Next.js 15β€”a lesser-known but super powerful feature when working with Parallel Routes. It gives us more control when a specific slot does not have a matched route.

Unmatched routes let us detect and handle the case when no route is matched for a given named slot (used in parallel routing).

Think of it like:

  • We defined a named slot (e.g., @modal)
  • But the current route does not match anything inside that slot
  • So we can render a fallback UI, show a default message, or redirect

🧭 When Do Unmatched Routes Occur?

In layouts with parallel routes like this:

app/
  └── layout.tsx
  └── @main/
       └── page.tsx
  └── @sidebar/
       └── page.tsx

If the current route matches @main, but nothing is matched in @sidebar, then the route is unmatched for @sidebar.

This is where not-found.tsx or default.tsx (for that slot) kicks in.


βœ… Real Use Case

Let’s say we have:

app/
  └── dashboard/
       └── layout.tsx
       └── @notifications/
            └── page.tsx
            └── not-found.tsx

And we try to visit:

/dashboard

If there’s no matching route for @notifications, Next.js checks:

  1. Is there a page.tsx inside @notifications? βœ…
  2. Is the URL trying to render @notifications, but no match is found? βœ…
  3. β†’ Then it will render not-found.tsx in that slot

πŸ“ Folder Setup Example

app/
  └── layout.tsx
  └── @main/
       └── page.tsx
  └── @modal/
       └── not-found.tsx

layout.tsx:

export default function RootLayout({ main, modal }: { main: React.ReactNode; modal: React.ReactNode }) {
  return (
    <div>
      <div>{main}</div>
      <div>{modal}</div>
    </div>
  );
}

not-found.tsx (for @modal):

export default function ModalNotFound() {
  return <div>No modal route matched.</div>;
}

So if the URL is: /home, and there’s nothing matched under @modal, then ModalNotFound is rendered in the modal slot.


πŸ”₯ Combine With default.tsx

You can optionally define a default.tsx too for graceful fallback:

// app/@modal/default.tsx
export default function ModalDefault() {
  return <p>No active modal</p>;
}

This will be shown only when there’s no match and no not-found.tsx.


🧩 Summary: Unmatched Routes in a Nutshell

Concept Explanation
πŸ” What is it? A route slot that didn't match any route
🧠 Used In? Parallel Routes using @slotName
πŸ“¦ Handled By not-found.tsx or default.tsx inside that slot
πŸ”„ Fallback Useful to show default UIs or errors
βœ… Benefit Prevent blank slots, show UI when something is missing

🚦 What are Intercepting Routes in Next.js?

Intercepting routes allow us to load a different route in a specific part of the pageβ€”without replacing the whole route view. Let's break down intercepting routes in Next.jsβ€”a feature introduced in Next.js 13+ with the App Router. It’s super useful when we want to show something like a modal or drawer on top of an existing page without navigating away from it.

πŸ‘‰ Think:
We're on /products and click a product to see /products/1. Instead of navigating fully to /products/1, we want a modal to open with product detailsβ€”still on /products.


βœ… Use Case Example

Let’s say we have a list of users at /users, and when we click on one, we want to show /users/123 as a modal.

Without Interception:

  • User clicks β†’ navigates to /users/123
  • Whole page reloads and changes.

With Interception:

  • User clicks β†’ modal shows on /users, but content from /users/123 is loaded inside it.

πŸ“ Folder Structure for Intercepting

Suppose you're on route /users, and want to intercept /users/[id].

app/
β”œβ”€β”€ users/
β”‚   β”œβ”€β”€ page.tsx                 # /users
β”‚   β”œβ”€β”€ [id]/
β”‚   β”‚   └── page.tsx             # /users/123 (full page)
β”‚   β”œβ”€β”€ (modals)/               # Special folder for intercept
β”‚   β”‚   └── users/
β”‚   β”‚       └── [id]/
β”‚   β”‚           └── page.tsx     # Intercepted modal content

πŸͺ„ How it Works:

  • The route (modals)/users/[id]/page.tsx intercepts /users/[id] when we’re already on /users.
  • Instead of full navigation, the intercepted page can show in a modal component in /users/page.tsx.

πŸ’‘ Key Concept

πŸ“Œ (modals) is just a parallel route segmentβ€”you can name it anything like (intercept) or (drawer)β€”it’s conventionally wrapped in ().

You then render the intercepted route with the Modal logic inside /users/page.tsx.


βš™οΈ Code Example

/users/page.tsx

import { useRouter } from 'next/navigation'
import Modal from '@/components/Modal'
import UserDetails from './(modals)/users/[id]/page'

export default function UsersPage() {
  const router = useRouter()

  const handleClick = (id: string) => {
    router.push(`/users/${id}`)
  }

  return (
    <>
      <div>
        <h1>All Users</h1>
        <button onClick={() => handleClick('123')}>View User 123</button>
      </div>

      {/* Show modal if intercepted */}
      {/* You can conditionally show based on route or state */}
      <Modal>
        <UserDetails />
      </Modal>
    </>
  )
}

✨ Summary

Feature Purpose
Intercepting Route Load route content (like modal) without full nav
Parallel Routes Needed for intercepting; wrapped in ()
Practical Use Modals, drawers, side-panels, etc.

🌐 What Are Parallel Intercepting Routes in Next.js 15?

In short:

Parallel Intercepting Routes let us show different routes side-by-side in specific UI areas (like modals, drawers, or side panels) without losing the original route context. Let’s dive into parallel intercepting routes in Next.js 15, especially as it builds on the App Router architecture that started with v13 and is now more powerful and flexible.


πŸ” Real-World Analogy

Imagine a dashboard where:

  • The main content shows the user's dashboard (/dashboard)
  • A side panel opens to show user settings (/settings) while staying on /dashboard

βœ… We want:

  • /dashboard to remain visible
  • /settings to load in a side panel
  • Browser URL to reflect /settings, but without full navigation

This is what parallel routes + intercepting enable.


🧠 Core Concepts

Concept Description
Parallel Routes Multiple UI areas (slots) that render independently
Intercepting Routes Route loaded into a slot instead of full navigation
Named Slots You define slots with keys like @modal, @drawer, etc.
(group) folders Group routes without affecting URL structure

πŸ› οΈ Folder Structure Example

Let’s build this example:

  • Main route: /dashboard
  • Intercepted side panel: /settings
  • Show /settings in a @panel slot while keeping /dashboard visible
app/
β”œβ”€β”€ layout.tsx                  # Defines all slots (@main, @panel)
β”œβ”€β”€ page.tsx                    # Default home route
β”œβ”€β”€ dashboard/
β”‚   β”œβ”€β”€ page.tsx                # /dashboard
β”œβ”€β”€ settings/
β”‚   └── page.tsx                # /settings (full route if direct nav)
β”œβ”€β”€ (panel)/
β”‚   └── settings/
β”‚       └── page.tsx            # Intercepted view for @panel

🧩 layout.tsx with Parallel Slots

export default function RootLayout({ children, panel }: {
  children: React.ReactNode
  panel: React.ReactNode
}) {
  return (
    <div className="flex">
      <main className="flex-1">
        {children} {/* Default content */}
      </main>
      <aside className="w-96 border-l">
        {panel} {/* Intercepted content */}
      </aside>
    </div>
  )
}

We’ve created two parallel routes:

  • @main β†’ shows children
  • @panel β†’ shows panel (intercepted)

Behind the scenes, Next.js maps these using file conventions like (panel) folders.


πŸ”„ How Interception Works

  • When we navigate from /dashboard to /settings, if the current route has a @panel slot defined, Next.js renders /settings into the slot.
  • If we go directly to /settings, it loads as a full page (like normal).

βœ… Benefits

  • ✨ Seamless modals/drawers without full page reloads
  • πŸ”„ Preserves scroll, state, context
  • πŸ”— Shareable URLs (since /settings is real)
  • 🧱 Clean architecture via named slots

πŸ§ͺ Optional Tip: Named Slots via Route Grouping

To declare a specific route to a slot, use the route.js config in the folder:

// app/(panel)/settings/route.js
export const route = {
  slot: 'panel'
}

This makes it explicit that this route should be loaded into the @panel slot.


🎯 Summary

Feature Use
layout.tsx with slots Define multiple UI zones like main, modal, panel, etc.
(group) folders Organize routes without URL impact
Intercepting routes Load new routes into a slot instead of full navigation
Great for Modals, side drawers, detail previews, split screens

In Next.js 15, Route Handlers are how we define custom backend logic (like APIs) within the App Router. They allow us to handle HTTP requests (GET, POST, PUT, DELETE, etc.) directly inside our app directory, similar to traditional API routes but fully aligned with the App Router’s conventions.


🧩 What are Route Handlers?

They are special files (typically named route.ts or route.js) placed inside a specific folder (usually under /app/api/...) that export functions corresponding to HTTP methods β€” like GET, POST, etc.

βœ… Route Handlers replace pages/api from the old Pages Router.


πŸ—‚ Folder Structure

app/
└── api/
    └── hello/
        └── route.ts  ← Route Handler

This defines an API endpoint at:

/api/hello

πŸ”§ Example: Simple GET Handler

app/api/hello/route.ts

export async function GET() {
  return new Response("Hello from API Route!");
}

πŸ“Œ Now hitting http://localhost:3000/api/hello with a GET request will return:

Hello from API Route!

πŸ”§ Example: Handling Different Methods (GET + POST)

export async function GET(request: Request) {
  return new Response("GET Request Success");
}

export async function POST(request: Request) {
  const data = await request.json();
  return new Response(`Received name: ${data.name}`);
}

To test:

curl -X POST http://localhost:3000/api/hello -H "Content-Type: application/json" -d '{"name":"Skyy"}'

βš™οΈ Supported HTTP Methods

You can export functions like:

  • GET
  • POST
  • PUT
  • DELETE
  • PATCH
  • HEAD
  • OPTIONS

Example:

export async function DELETE() {
  return new Response("Deleted something!");
}

πŸ“₯ Accessing Request Data

You can use the Request object to access:

βœ… Query params (in GET)

export async function GET(req: Request) {
  const url = new URL(req.url);
  const search = url.searchParams.get("name");
  return new Response(`Hi ${search}`);
}

βœ… JSON body (in POST)

export async function POST(req: Request) {
  const body = await req.json();
  return new Response(`Received ${body.name}`);
}

βœ… Dynamic Route Handlers

You can use dynamic segments like this:

/app/api/user/[id]/route.ts

export async function GET(
  request: Request,
  { params }: { params: { id: string } }
) {
  return new Response(`User ID: ${params.id}`);
}

Now accessing /api/user/123 will return:
"User ID: 123"


🧠 Middleware & Edge Support

  • Route handlers can run on Edge or Node.js (your choice).
  • To run on Edge, use:
    export const runtime = "edge";

πŸ“ Common Use Cases

  • Custom API routes (fetching/updating DB)
  • Webhooks
  • Auth endpoints
  • File uploads
  • Payment handlers (Stripe/Razorpay)
  • Server Actions support (in the future)

πŸ§ͺ Bonus: Returning JSON

export async function GET() {
  const data = { message: "Hello JSON" };
  return Response.json(data); // Shortcut for JSON responses
}

πŸ“Œ Summary

Feature Supported βœ…
GET/POST/PUT/DELETE βœ…
JSON body parsing βœ…
Query/Search Params βœ…
Dynamic Params ([id]) βœ…
Edge Runtime Option βœ…
Typed via TypeScript βœ…

πŸͺ 1. Cookies in Route Handlers

In Next.js 15, Route Handlers give us direct access to cookies and caching controls, which are super important for things like authentication, personalization, performance, and session handling. Next.js provides cookies() utility from next/headers for reading and setting cookies on the server.

πŸ”Ή Importing:

import { cookies } from "next/headers";

βœ… Reading a Cookie:

export async function GET() {
  const cookieStore = cookies();
  const token = cookieStore.get("auth-token");

  return new Response(`Cookie value: ${token?.value ?? "not found"}`);
}

βœ… Setting a Cookie:

export async function POST() {
  const cookieStore = cookies();

  cookieStore.set("auth-token", "12345", {
    httpOnly: true,
    path: "/",
    secure: true,
    maxAge: 60 * 60 * 24 * 7, // 1 week
  });

  return new Response("Cookie set!");
}

βœ… Deleting a Cookie:

export async function DELETE() {
  const cookieStore = cookies();
  cookieStore.delete("auth-token");

  return new Response("Cookie deleted!");
}

☝️ Cookies set with httpOnly: true can’t be accessed from JavaScript in the browser β€” great for securing tokens like JWTs.


⚠️ cookies() only works inside Route Handlers, Server Components, and Server Actions β€” not in Client Components.


πŸ“¦ 2. Caching in Route Handlers

Caching is used to control how responses are stored and reused by the browser or a CDN. In Route Handlers, we can manage caching behavior using Response headers.


βœ… Setting Cache Headers Manually

export async function GET() {
  const data = { name: "Skyy", role: "Dev" };

  return new Response(JSON.stringify(data), {
    headers: {
      "Content-Type": "application/json",
      "Cache-Control": "public, max-age=3600", // Cache for 1 hour
    },
  });
}

πŸ”Ή Common Cache-Control Options:

Directive Meaning
no-store Don’t store in cache at all
no-cache Must revalidate every time
public Cacheable by any cache (CDN, browser)
private Only cache in user's browser
max-age=seconds Time in seconds before stale

πŸ”Ή Example: Disable Caching (for dynamic/private data)

export async function GET() {
  return new Response("Private data", {
    headers: {
      "Cache-Control": "no-store",
    },
  });
}

🧠 Bonus: Dynamic vs Static Caching

In Next.js 15:

  • Static cache (default) is used when response can be cached and reused.
  • Dynamic responses (e.g., with cookies or user-specific data) are opted out of static cache.
  • Use:
    export const dynamic = "force-dynamic";
    to force dynamic behavior even when no dynamic data is present.

Example:

export const dynamic = "force-dynamic"; // or "force-static"

βœ… Summary

Feature Method / Tool Notes
Read Cookie cookies().get() Server only
Set Cookie cookies().set() Secure, customizable
Delete Cookie cookies().delete() Easy removal
Cache Response Response.headers Fully customizable
Control dynamic/static export const dynamic Static or dynamic behavior

πŸšͺ What is Middleware in Next.js?

In Next.js 15, Middleware is a special function that runs before a request is completed, and before rendering a route. It allows us to intercept requests, perform logic, and optionally redirect, rewrite, or modify responses.

Think of it as a gatekeeper between the user and the page.


🧠 Why Use Middleware?

Middleware is perfect for:

  • πŸ” Authentication & Authorization (e.g., redirect if not logged in)
  • 🌐 Localization (e.g., detect browser language)
  • πŸš€ A/B testing or feature flags
  • πŸ”„ Rewrites and redirects
  • πŸ§ͺ Logging, analytics, etc.

πŸ“ Where Do We Define Middleware?

In the root of the app/ or project directory, we create:

/middleware.ts

or

/middleware.js

This file runs for every request unless we filter it.


βš™οΈ Basic Structure of Middleware

// middleware.ts
import { NextResponse } from "next/server";
import type { NextRequest } from "next/server";

export function middleware(request: NextRequest) {
  // Perform something with the request

  return NextResponse.next(); // allow request
}

πŸ”„ Common Operations

βœ… 1. Redirect

export function middleware(request: NextRequest) {
  const isLoggedIn = false;

  if (!isLoggedIn && request.nextUrl.pathname.startsWith("/dashboard")) {
    return NextResponse.redirect(new URL("/#", request.url));
  }

  return NextResponse.next();
}

βœ… 2. Rewrite

export function middleware(request: NextRequest) {
  if (request.nextUrl.pathname === "/old") {
    return NextResponse.rewrite(new URL("/new", request.url));
  }

  return NextResponse.next();
}

βœ… 3. Set Headers

export function middleware(request: NextRequest) {
  const response = NextResponse.next();
  response.headers.set("x-powered-by", "Skyy");
  return response;
}

πŸ§ͺ Example: Auth Check for Private Routes

export function middleware(request: NextRequest) {
  const token = request.cookies.get("auth-token")?.value;

  const isAuthRoute = request.nextUrl.pathname.startsWith("/dashboard");

  if (isAuthRoute && !token) {
    return NextResponse.redirect(new URL("/#", request.url));
  }

  return NextResponse.next();
}

πŸ“ Matching Routes with matcher

To restrict middleware only to specific routes, use config.matcher.

export const config = {
  matcher: ["/dashboard/:path*", "/profile", "/settings"],
};

πŸ“ :path* means include all nested routes too.


πŸ“¦ NextRequest and NextResponse

  • NextRequest gives access to:

    • request.nextUrl.pathname β†’ URL info
    • request.cookies β†’ read cookies
    • request.headers β†’ get headers
  • NextResponse gives methods to:

    • .redirect()
    • .rewrite()
    • .next() (allow to continue)
    • .headers.set()

πŸ” Example Use Cases

Use Case Middleware Logic Example
Protect dashboard Check cookie or session and redirect
Auto locale redirect Check Accept-Language and rewrite
Block bots or IPs Check IP/headers and return 403
Beta route gating Check cookie/flag and redirect
Analytics logging Log request info before serving

🚨 Limitations of Middleware

  • ❌ Can’t access use client components
  • ❌ Cannot render components
  • βœ… Runs at Edge (very fast)
  • βœ… Can only use Web APIs and Next APIs

πŸ”š Summary

Feature Description
Location /middleware.ts at project root
Execution Before the route is rendered
Key Tools NextRequest, NextResponse
Common Usage Auth, redirects, rewrites, headers
Matcher Filters what routes middleware applies to

BASIC CRUD

// app/api/todos/[id]/route.ts
import { NextResponse } from "next/server";
import { todos } from "@/lib/data";

export async function GET(_: Request, { params }: { params: { id: string } }) {
  const todo = todos.find((t) => t.id === params.id);
  return todo
    ? NextResponse.json(todo)
    : NextResponse.json({ error: "Not found" }, { status: 404 });
}

export async function PUT(request: Request, { params }: { params: { id: string } }) {
  const { task, done } = await request.json();
  const index = todos.findIndex((t) => t.id === params.id);

  if (index === -1) {
    return NextResponse.json({ error: "Todo not found" }, { status: 404 });
  }

  todos[index] = { ...todos[index], task, done };
  return NextResponse.json(todos[index]);
}

export async function DELETE(_: Request, { params }: { params: { id: string } }) {
  const index = todos.findIndex((t) => t.id === params.id);
  if (index === -1) {
    return NextResponse.json({ error: "Not found" }, { status: 404 });
  }
  const deleted = todos.splice(index, 1)[0];
  return NextResponse.json(deleted);
}

πŸ” What are Headers in Route Handlers?

Let’s explore headers in Route Handlers in Next.js 15 in depth β€” how to read, set, and use them within route.ts files inside the app/api directory.

In Next.js 15 Route Handlers (App Router), headers can be used:

  • To read incoming request metadata (e.g., auth tokens, content-type, etc.)
  • To set outgoing custom response headers (e.g., caching, CORS, custom info)

πŸ“₯ Reading Request Headers

In any route.ts handler, use the Request object’s .headers property.

βœ… Example – Reading a custom or auth header

// app/api/headers-example/route.ts
import { NextResponse } from "next/server";

export async function GET(request: Request) {
  const authToken = request.headers.get("authorization");
  const userAgent = request.headers.get("user-agent");

  return NextResponse.json({
    message: "Headers received",
    authToken,
    userAgent,
  });
}

βœ… Use this when you want to validate API keys, check user-agents, etc.


πŸ“€ Setting Response Headers

Use the NextResponse object’s .headers.set() method.

βœ… Example – Set custom response headers

// app/api/set-headers/route.ts
import { NextResponse } from "next/server";

export async function GET() {
  const res = NextResponse.json({ message: "Response with custom headers" });

  res.headers.set("X-Custom-Header", "HelloFromNextJS15");
  res.headers.set("Cache-Control", "no-store");

  return res;
}

You can use this for:

  • Security headers (CSP, CORS, etc.)
  • Performance tuning (Cache-Control)
  • Metadata (debugging, tracking)

πŸ’₯ Full Example: Auth Check with Header

// app/api/secure/route.ts
import { NextResponse } from "next/server";

export async function GET(request: Request) {
  const token = request.headers.get("authorization");

  if (token !== "Bearer mysecrettoken123") {
    return NextResponse.json(
      { error: "Unauthorized" },
      { status: 401 }
    );
  }

  return NextResponse.json({ message: "Welcome, authorized user" });
}

⚠️ Case Sensitivity of Headers

  • Headers are case-insensitive.
  • Always prefer get("authorization"), not "Authorization".

πŸ§ͺ Bonus: Looping Through Headers

export async function GET(request: Request) {
  const headers = Object.fromEntries(request.headers.entries());
  return NextResponse.json({ allHeaders: headers });
}

βœ… Summary Table

Task Code Snippet
Read header request.headers.get("authorization")
Set response header res.headers.set("X-My-Header", "Value")
Multiple headers Loop: Object.fromEntries(request.headers.entries())
Security header res.headers.set("X-Frame-Options", "DENY")
Cache-Control res.headers.set("Cache-Control", "no-store")

Let’s break down the different types of HTTP headers β€” especially in the context of Next.js 15 route handlers β€” and how they’re typically used.

🧠 What Are HTTP Headers?

HTTP headers are key-value pairs sent between the client and server to provide context about the request/response.

Headers are categorized into:

βœ… 1. Request Headers

Sent from the client (browser or frontend) to the server.

Header Name Purpose
Authorization Carries access tokens or API keys.
Content-Type Describes the format of the body (application/json, multipart/form-data, etc.)
Accept Tells the server what content types the client can handle (application/json, etc.)
User-Agent Identifies the client app/browser and OS.
Cookie Sends cookies to the server.
Referer Tells the server which page the request came from.
Accept-Language Preferred language of the client (en, fr, etc.)

βœ… 2. Response Headers

Sent by the server to the client, alongside the body.

Header Name Purpose
Content-Type Tells the browser how to interpret the response (text/html, application/json, etc.)
Set-Cookie Sends cookies to be stored in the browser.
Cache-Control Caching instructions for browsers/CDNs (no-store, max-age=3600, etc.)
Location Used in redirects.
Access-Control-Allow-Origin For CORS. Specifies allowed origins.
X-Custom-* Custom headers for debugging or tracking.

βœ… 3. Entity Headers

Apply to the body/content of the request or response.

Header Name Purpose
Content-Length Size of the body.
Content-Encoding How content is encoded (e.g., gzip).
ETag Used for caching validation.
Last-Modified When the resource was last changed.

βœ… 4. Security Headers

Added to protect the app and browser.

Header Name Purpose
X-Frame-Options Prevent clickjacking (e.g., DENY, SAMEORIGIN)
Strict-Transport-Security Enforce HTTPS (max-age=...)
Content-Security-Policy Restrict resources (images, scripts, etc.)
X-XSS-Protection Browser XSS protection
Referrer-Policy Controls Referer header behavior

βœ… 5. CORS Headers

Control cross-origin requests.

Header Name Purpose
Access-Control-Allow-Origin Whitelisted origin(s)
Access-Control-Allow-Methods Allowed HTTP methods
Access-Control-Allow-Headers Allowed custom headers
Access-Control-Allow-Credentials Allow cookies/credentials

πŸ”§ How to Use These in Next.js 15

Reading Request Headers

export async function GET(request: Request) {
  const lang = request.headers.get("accept-language");
  const auth = request.headers.get("authorization");
  return Response.json({ lang, auth });
}

Setting Response Headers

import { NextResponse } from "next/server";

export async function GET() {
  const res = NextResponse.json({ status: "ok" });

  res.headers.set("Cache-Control", "no-store");
  res.headers.set("X-My-App", "Zumfass");
  res.headers.set("Content-Security-Policy", "default-src 'self'");

  return res;
}

πŸ§ͺ Bonus: Viewing Headers in DevTools

In Chrome β†’ Right-click β†’ Inspect β†’ Network tab β†’ Select a request β†’ Headers tab


πŸ“Œ Summary Table

Type Examples Usage In Next.js
Request Headers authorization, user-agent request.headers.get()
Response Headers set-cookie, cache-control response.headers.set()
Security X-Frame-Options, CSP Custom security headers
CORS access-control-allow-origin Set via middleware/handler

🧠 What Are Cookies?

πŸͺ Let's dive deep into cookies in Route Handlers in Next.js 15 β€” how they work, how to use them for authentication, preferences, sessions, and more.

Cookies are small key-value pairs stored in the browser, which can be sent with every HTTP request to the server. They're essential for:

  • User authentication (like tokens)
  • Session management
  • Storing preferences (theme, language)

πŸ“¦ cookies() API in Next.js 15

In Next.js App Router (v13+), especially in Route Handlers (route.ts, route.js), we use the cookies() API to read, set, and delete cookies.

βœ… This API is server-only β€” works in Route Handlers, Server Components, Middleware, and Layouts.


πŸ”‘ Import Path

import { cookies } from 'next/headers';

πŸ” Reading Cookies

export async function GET() {
  const cookieStore = cookies();
  const token = cookieStore.get('token'); // returns { name, value, Path, ... }

  return Response.json({
    message: 'Hello',
    token: token?.value || 'No Token',
  });
}

πŸͺ Setting Cookies

export async function POST() {
  const cookieStore = cookies();

  cookieStore.set({
    name: 'token',
    value: 'abc123',
    httpOnly: true,      // secure from JS access
    secure: true,        // only over HTTPS
    path: '/',
    maxAge: 60 * 60 * 24, // 1 day
  });

  return Response.json({ message: 'Cookie Set' });
}

βœ… You can also set it using this shorter form:

cookieStore.set('theme', 'dark');

❌ Deleting Cookies

export async function DELETE() {
  const cookieStore = cookies();
  cookieStore.delete('token');

  return Response.json({ message: 'Cookie Deleted' });
}

🧾 Full Example: Authentication with Cookies

// app/api/#/route.ts
import { cookies } from 'next/headers';

export async function POST(request: Request) {
  const { username, password } = await request.json();

  if (username === 'admin' && password === 'secret') {
    cookies().set({
      name: 'token',
      value: 'JWT-TOKEN-HERE',
      httpOnly: true,
      secure: true,
      maxAge: 60 * 60 * 24,
    });

    return Response.json({ message: 'Login successful' });
  }

  return Response.json({ message: 'Invalid credentials' }, { status: 401 });
}

πŸ“¦ Usage in Middleware (for Auth Protection)

// middleware.ts
import { NextResponse } from 'next/server';
import type { NextRequest } from 'next/server';

export function middleware(request: NextRequest) {
  const token = request.cookies.get('token');

  if (!token) {
    return NextResponse.redirect(new URL('/#', request.url));
  }

  return NextResponse.next();
}

πŸ”’ Secure Cookie Tips

Flag Purpose
httpOnly Cannot be accessed via JavaScript (document.cookie)
secure Only sent over HTTPS
sameSite Controls cross-origin sending (e.g., strict, lax, none)
maxAge How long the cookie is valid (in seconds)
path URL path scope of the cookie (/ = all)

πŸ€” Server vs Client Cookies

Feature Server (cookies()) Client (document.cookie)
Access scope Server-side only (secure) Client-side only (JS-accessible)
Best for Auth tokens, user sessions Preferences (e.g., theme)
Tamper resistance More secure (with httpOnly) Less secure

βœ… TL;DR Summary

Operation Code Example
Get Cookie cookies().get("token")
Set Cookie cookies().set("token", "value")
Delete Cookie cookies().delete("token")

🧭 What is redirect() in Next.js?

The redirect() function is a server-side utility provided by Next.js used to immediately redirect a user to another route. Let’s dive deep into the redirect() function in Next.js 15, especially within the App Router and Route Handlers. It’s a super useful utility for controlling navigation on the server.

It’s available in:

Where We Can Use It
Server Components (.tsx)
Route Handlers (route.ts)
Layouts / Pages (layout.tsx)
Middleware (middleware.ts)

πŸ“¦ How to Import

import { redirect } from 'next/navigation';

Note: This is from next/navigation, even when used in route.ts.


βœ… Basic Syntax

redirect('/#');
  • This immediately stops the current request/response lifecycle and sends a redirect to the browser.

🧠 Behavior

Feature Description
Server-Only Can’t be used in Client Components. Will throw error if attempted.
No return needed Calling redirect() throws internally to break the function execution.
Permanent/Temp? By default, uses status code 307 (temporary). Cannot be customized.
Blocking It immediately halts any further code execution.

πŸ§ͺ Example in a Route Handler

// app/api/#/route.ts
import { redirect } from 'next/navigation';
import { cookies } from 'next/headers';

export async function GET() {
  const token = cookies().get('token');

  if (!token) {
    redirect('/#'); // instantly redirects if not authenticated
  }

  return Response.json({ message: 'You are logged in' });
}

πŸ” Example in a Server Component

// app/dashboard/page.tsx
import { redirect } from 'next/navigation';
import { cookies } from 'next/headers';

export default function DashboardPage() {
  const token = cookies().get('token');

  if (!token) {
    redirect('/#');
  }

  return <h1>Welcome to Dashboard!</h1>;
}

🚫 Common Mistakes

Mistake Fix
❌ Using in client components βœ… Use useRouter().push() instead
❌ Trying to return redirect() βœ… Just call it; don’t return it
❌ Using after an async fetch βœ… Ensure condition checks before data fetch

🧭 Use With Middleware?

In middleware, we don’t use redirect() β€” instead, we use:

import { NextResponse } from 'next/server';

export function middleware(request: NextRequest) {
  return NextResponse.redirect(new URL('/#', request.url));
}

πŸ” Client-Side Alternative

In Client Components, use the useRouter hook:

'use client';

import { useRouter } from 'next/navigation';

function MyComponent() {
  const router = useRouter();

  const handleClick = () => {
    router.push('/about'); // Client-side navigation
  };

  return <button onClick={handleClick}>Go to About</button>;
}

πŸ›‘ Why redirect() Throws

Internally, redirect() uses throw to prevent rendering or returning anything else. That’s why we don’t write any code after it β€” it won’t run.

redirect('/#');
console.log("This won't log"); // Never executes!

βœ… TL;DR Cheatsheet

Action Use
Redirect in server comp redirect('/path')
Redirect in route handler redirect('/path')
Redirect in middleware NextResponse.redirect(...)
Redirect on client useRouter().push('/path')

🧠 First: Why Caching Matters

Let's go deep into caching in Next.js 15, especially with the App Router and GET() methods in Route Handlers. We'll use an example and break everything down step-by-step.

In Next.js 15, caching helps us:

  • Improve performance by reducing redundant database or API calls.
  • Speed up delivery with static or semi-static responses.
  • Balance freshness and performance with revalidation.

βœ… Our Code – Overview

// app/api/pokemon/route.ts

// Caching in Nextjs15
// Only works with GET() methods

export const dynamic = "force-static";
export const revalidate = 20; // only changed after 20 secs, but in the BG

export async function GET() {
  // This would usually come from a DB.
  const pokemonTypes = [
    { id: 1, type: "Grass-type 🌿" },
    { id: 2, type: "Fire-type πŸ”₯" },
    { id: 3, type: "Water-type πŸ’¦" },
    { id: 4, type: "Fighting-type πŸ₯Š" },
  ];

  return Response.json(pokemonTypes);
}

Let’s break it down now. πŸ”


πŸ“¦ The Key Exports

1. export const dynamic = "force-static";

This tells Next.js:

β€œTreat this route as fully static. Generate it at build time.”

Options available:

Value Meaning
"auto" Default behavior (Next decides based on code used)
"force-dynamic" Always render on the server at runtime (no caching)
"force-static" Always cache the result as static (fully cacheable, no dynamic data)

So, force-static = β€œmake this route’s response cacheable and serve the cached version.”


2. export const revalidate = 20

This enables Incremental Static Regeneration (ISR) for API Routes / Route Handlers. It means:

  • The first request will build and cache the response.
  • Subsequent requests (within 20 seconds) serve the cached version.
  • After 20 seconds, the cache is rebuilt in the background while still serving the old data.
  • Once rebuilt, new requests get fresh data.

πŸ“Œ This is sometimes referred to as stale-while-revalidate behavior.


πŸ“ So What’s Cached?

Only the GET() method response is cached. This works perfectly for:

  • Blogs
  • Static product lists
  • Public APIs
  • Anything that doesn’t change rapidly

🚫 What Doesn’t Work with Caching?

Caching won’t apply if:

  • You use cookies, headers, or dynamic content (e.g., headers(), cookies())
  • You fetch from external APIs without proper cache config
  • You're using POST, PUT, DELETE, etc. (caching only works with GET())

πŸ”₯ Advanced: Dynamic + Revalidate?

You can mix dynamic + revalidation like this:

export const dynamic = "force-dynamic"; // runtime always
export const revalidate = 10; // doesn’t matter, since it’s forced dynamic

But here, revalidate is ignored. Only useful with static or auto.


βœ… Best Practices

Scenario Recommendation
Public data that rarely changes force-static + long revalidate
Semi-frequent updates (e.g., news) auto + revalidate = X
User-authenticated or private data force-dynamic, no caching
Client-side fetching (CSR) Control cache via SWR, React Query etc.

πŸ“₯ Client-Side Consumption

Let’s say we call this from the frontend:

const res = await fetch("/api/pokemon");
const data = await res.json();

This works perfectly with cached GET() APIs. The client gets the cached version unless 20 seconds have passed and revalidation has occurred.


πŸ§ͺ Want to Try Live?

Add a timestamp to your data to verify cache refresh:

const pokemonTypes = [
  { id: 1, type: "Grass-type 🌿" },
  { id: 2, type: "Fire-type πŸ”₯" },
  { id: 3, type: "Water-type πŸ’¦" },
  { id: 4, type: "Fighting-type πŸ₯Š" },
  { id: 5, type: `Fetched at: ${new Date().toISOString()}` },
];

Now, request this endpoint. The timestamp will only change every 20 seconds, confirming the caching behavior!


πŸ”š Summary

Concept Value/Explanation
dynamic "force-static" for caching static GET responses
revalidate Time (in sec) before cache revalidates in BG
Applies to GET() methods only
Server Cache Next.js + Vercel or serverless edge caching
Not supported For POST, PUT, DELETE, cookies(), headers()

Understanding Request vs NextRequest and Response vs NextResponse is crucial for working with Route Handlers, middleware, and server-side logic in Next.js 15 (App Router). Let’s break it all down clearly and deeply. 🧠✨


🟒 Request vs NextRequest

βœ… Request (Web Standard)

This is the native Fetch API’s Request object – just like in the browser or Node.js.
It's used in Route Handlers (like GET, POST, etc.) inside app/api/*.

// app/api/hello/route.ts
export async function GET(request: Request) {
  const url = request.url;
  return Response.json({ message: "Hello World" });
}

βœ… NextRequest (Next.js Extension)

This extends the native Request with extra Next.js-specific goodies like:

  • cookies – to easily read cookies (request.cookies.get("token"))
  • nextUrl – full parsed URL with pathname, searchParams, etc.
  • geo – for geolocation (on Vercel)
  • ip – get user's IP (on Vercel)

It is used only in middleware, or sometimes in edge functions.

// middleware.ts
import { NextRequest, NextResponse } from "next/server";

export function middleware(request: NextRequest) {
  const token = request.cookies.get("token");
  const lang = request.nextUrl.searchParams.get("lang");
  return NextResponse.next();
}

🧠 Summary: When to Use?

Context Use Why?
Route Handlers Request Native, no need for Next-specific logic
Middleware NextRequest Access Next-specific features (nextUrl, cookies, IP)
Edge Functions NextRequest Same as above

πŸ”΅ Response vs NextResponse

βœ… Response (Web Standard)

This is the standard Web Response used for returning data from route handlers, like GET():

export async function GET() {
  return new Response("Hello", {
    status: 200,
    headers: {
      "Content-Type": "text/plain",
    },
  });
}

Or using the utility method:

return Response.json({ data: "value" });

Perfect for route handlers – clean, standard, and enough in most cases.


βœ… NextResponse (Next.js Extension)

This is used only in middleware and includes enhancements:

  • NextResponse.redirect(url)
  • NextResponse.rewrite(url)
  • Cookie manipulation: response.cookies.set() and get()
  • Modifying headers more easily
// middleware.ts
import { NextResponse } from "next/server";

export function middleware(request: NextRequest) {
  const response = NextResponse.next();
  response.cookies.set("visited", "true");
  return response;
}

🧠 Summary: When to Use?

Context Use Why?
Route Handlers Response Simpler, native, works well in API routes
Middleware NextResponse Needed for rewrite, redirect, set headers/cookies
Advanced headers NextResponse If modifying response during middleware phase

πŸ” Real-World Example Side-by-Side

βœ… In a Route Handler

// app/api/user/route.ts
export async function GET(request: Request) {
  return Response.json({ user: "Skyy" });
}

βœ… Works fine because we don’t need any Next.js-specific feature here.


βœ… In Middleware

// middleware.ts
import { NextRequest, NextResponse } from "next/server";

export function middleware(request: NextRequest) {
  const isLoggedIn = request.cookies.get("token");

  if (!isLoggedIn) {
    return NextResponse.redirect(new URL("/#", request.url));
  }

  return NextResponse.next();
}

Here, NextRequest gives access to cookies and NextResponse.redirect() simplifies redirection.


πŸ§ͺ BONUS TIP: Want cookies in route handlers?

Use:

import { cookies } from "next/headers";

export async function GET() {
  const cookieStore = cookies();
  const userToken = cookieStore.get("token")?.value;
  return Response.json({ token: userToken });
}

πŸ”š TL;DR Cheat Sheet

Feature Request NextRequest Response NextResponse
Based On Web API Extends Request Web API Extends Response
Available In Route Handlers Middleware Route Handlers Middleware
Can use nextUrl ❌ βœ… ❌ ❌
Can access cookies ❌ (manually) βœ… ❌ βœ…
Can redirect ❌ ❌ βœ… (manual) βœ…
Modify headers βœ… βœ… βœ… βœ… (easier)

About

All resources related to ROUTING in NexJs15+ πŸ‘¨πŸ»β€πŸ’»βš›οΈ

Topics

Resources

Stars

Watchers

Forks

Releases

No releases published

Packages

No packages published