Next.js interview questions and answers for 2025

hero image

Next.js Interview Questions for Freshers and Intermediate Levels

1.

When should you choose Next.js over a traditional React application?

Answer

You should choose Next.js over a traditional React application when you need:

  1. Better SEO – Next.js supports server-side rendering (SSR) and static site generation (SSG), ensuring search engines can index content properly.
  2. Faster Performance – Features like automatic code-splitting, image optimization, and incremental static regeneration (ISR) improve load times.
  3. Built-in Routing – Next.js provides a file-based routing system, eliminating the need for external routing libraries like React Router.
  4. API Routes – Next.js allows you to create serverless API endpoints directly within the project, reducing backend complexity.
  5. Scalability – With hybrid rendering (SSG + SSR + ISR), Next.js enables efficient content updates without rebuilding the entire app.
  6. Easy DeploymentVercel (Next.js’s native platform) offers seamless deployment with automatic optimizations.
  7. Edge Functions & Middleware – Next.js supports middleware for request handling and Edge Functions for improved performance at the network level.

Use Next.js if your project requires speed, SEO, scalability, and a streamlined development experience. However, if you only need a basic, CSR-heavy app, plain React may suffice.

2.

How does Next.js handle server-side rendering (SSR) compared to client-side rendering (CSR)?

Answer

Next.js supports Server-Side Rendering (SSR) and Client-Side Rendering (CSR), each serving different use cases.

  • SSR (Server-Side Rendering)
    • The page is generated on the server for each request using getServerSideProps().
    • Useful for dynamic data that changes frequently (e.g., personalized dashboards, real-time updates).
    • Improves SEO and first-page load time since the content is fully rendered before reaching the browser.
  • CSR (Client-Side Rendering)
    • The page loads with minimal initial content, and data fetching happens in the browser using useEffect() or API calls.
    • Suitable for single-page applications (SPAs) and interactive UIs where SEO is not a priority.
    • Reduces server load but may cause slower first loads due to additional JavaScript execution.

Key Difference:

  • SSR loads fully-rendered content upfront, improving SEO and performance for first-time visits.
  • CSR loads faster initially but fetches content dynamically, leading to potential delays in rendering.

Next.js allows developers to choose between SSR and CSR based on performance and SEO needs.

3.

What is static site generation (SSG) in Next.js, and when should it be used?

Answer

Static Site Generation (SSG) in Next.js pre-builds pages at compile time, generating static HTML files that can be served instantly. It is implemented using getStaticProps().

When to Use SSG:

  • SEO-Optimized Content – Pre-rendered pages improve search engine indexing.
  • Blogs & Marketing Pages – Content that doesn’t change often benefits from fast-loading static pages.
  • E-Commerce Product Listings – If product details don’t update frequently, SSG reduces server load.
  • Documentation & Knowledge Bases – Static content allows instant, scalable delivery.

Key Benefits:

  • Fast Performance – Pre-built pages load instantly.
  • Lower Server Load – No need to generate pages dynamically for each request.
  • Supports Incremental Static Regeneration (ISR) – Pages can be updated without a full site rebuild.

Supports Incremental Static Regeneration (ISR) – Allows individual static pages to be updated after deployment without rebuilding the entire site, making it ideal when you want the benefits of static generation but need some pages to update periodically based on content changes.

Use SSG when content can be pre-generated and doesn’t require real-time updates per request.

4.

How does Next.js improve SEO compared to a standard React application?

Answer

Next.js improves SEO by offering server-side rendering (SSR), static site generation (SSG), and optimized metadata handling, which help search engines crawl and index pages effectively.

Key SEO Advantages Over Standard React:

  1. Pre-Rendered Pages (SSR & SSG) – Unlike React’s client-side rendering (CSR), which loads content dynamically, Next.js serves fully-rendered HTML to search engines, improving indexability and page rankings.
  2. Faster Page Load Times – Next.js features like automatic code splitting, lazy loading, and image optimization enhance performance, improving Core Web Vitals, a key SEO ranking factor.
  3. Efficient Metadata Handling – The next/head component allows easy management of title tags, meta descriptions, and Open Graph tags, which are crucial for SEO and social sharing.
  4. Optimized Routing – Next.js supports clean, SEO-friendly URLs using its file-based routing system.
  5. Incremental Static Regeneration (ISR) – Allows pages to update without full site rebuilds, ensuring fresh content while keeping the benefits of static performance.

Conclusion:

Next.js significantly enhances SEO, performance, and user experience, making it a better choice than a standard React SPA for content-driven and search-engine-focused applications.

5.

What are API routes in Next.js, and how do they work?

Answer

API routes in Next.js allow you to create serverless backend endpoints within your application.

In the App Router (Next.js 13+), API routes are created inside the /app/api/ directory, using a special route.js or route.ts file for each endpoint.

In the older Pages Router, API routes are placed inside the /pages/api/ directory.

 

How They Work (App Router):

  • Each folder inside /app/api/ defines an API route (e.g., /app/api/user/route.js becomes /api/user).
  • You export HTTP method handlers like GET, POST, PUT, or DELETE directly.
  • Instead of using req and res, you work with Next.js Request and Response objects.

Example API Route (app/api/hello/route.js):

 

import { NextResponse } from "next/server";

export async function GET(request) {
return NextResponse.json({ message: "Hello from Next.js API route!" });
}

 

Note:

In older Pages Router projects (/pages/api/), you define API routes using a default export function that receives req and res, similar to Express.js.

 

Key Benefits of Next.js API Routes:

  1. Eliminates the Need for a Separate Backend – Backend logic stays within the Next.js project.
  2. Optimized for Serverless Deployments – Works seamlessly with Vercel, AWS Lambda, and other serverless platforms.
  3. Built-in Middleware Support – Can handle authentication, validation, and database interactions.

API routes are ideal for handling form submissions, authentication, database interactions, and third-party API integrations in a Next.js application.

6.

Explain the difference between getStaticProps(), getServerSideProps(), and getInitialProps().

Answer

These three functions control how data is fetched and when pages are generated in Next.js Pages Router (the traditional routing system).

In the modern App Router (Next.js 13+), server components and fetch are used instead for data fetching.

 

Pages Router Functions:

1. getStaticProps() (SSG – Static Site Generation)

  • Runs at build time and pre-generates static HTML.
  • Ideal for content that doesn’t change frequently (e.g., blogs, product pages).
  • Improves performance since pages are cached and served instantly.

Example:

 

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

 

2. getServerSideProps() (SSR – Server-Side Rendering)

  • Runs on every request, generating pages dynamically on the server.
  • Ideal for frequently changing data (e.g., user dashboards, real-time updates).
  • Ensures fresh content but may increase server load.

Example:

 

export async function getServerSideProps() {
const data = await fetch("https://api.example.com/latest-news");
return { props: { news: data } };
}

 

3. getInitialProps() (Deprecated)

  • Runs on both server and client, making it less efficient.
  • Previously used in class components and _app.js, but now considered legacy.
  • Replaced by getStaticProps and getServerSideProps for better performance.

 

Key Differences (Pages Router):

 

Function When It Runs Use Case Performance
getStaticProps Build time (SSG) Static content (blogs, docs) Fast & cached
getServerSideProps On each request (SSR) Dynamic content (auth data) Slower, real-time
getInitialProps Server & client Legacy, avoid using Less efficient

 

 

Note:

In the App Router (Next.js 13+), data fetching is handled differently using async Server Components and fetch() inside the component itself, removing the need for getStaticProps or getServerSideProps.

For modern projects, it’s recommended to use the App Router data fetching methods instead.

7.

How does file-based routing work in Next.js?

Answer

Next.js uses a file-based routing system, meaning the structure of your files inside the /pages directory determines the app’s routes automatically—no need for React Router.

Key Concepts:

  1. Basic Routes:
    • A file inside /pages corresponds to a route.
    • Example: /pages/about.js/about
  2. Dynamic Routes:
    • Use square brackets ([param]) to create dynamic segments.
    • Example: /pages/post/[id].js/post/123
  3. Nested Routes:
    • Folder structure defines nested paths.

Example:

 

/pages
/blog
index.js → /blog
[slug].js → /blog/my-post

 

  1. API Routes:
    • Inside /pages/api/, files act as API endpoints instead of pages.
    • Example: /pages/api/user.js/api/user
  2. Catch-All Routes:
    • [...] captures multiple segments dynamically.
    • Example: /pages/docs/[...slug].js/docs/a/b/c

Why Use File-Based Routing?

  • No need for extra routing libraries (like React Router).
  • Automatically optimized for performance.
  • Easier to manage as the app scales.

This system simplifies navigation and route management while ensuring efficiency and scalability.

8.

What is the difference between dynamic and static routes in Next.js?

Answer

Next.js supports static and dynamic routes based on the file-based routing system.

1. Static Routes

  • Defined using regular file names inside the /pages directory.
  • The URL structure is fixed and does not change.

Example:

 

/pages/about.js → /about
/pages/contact.js → /contact

 

  • Best for: Pages with fixed content (e.g., Home, About, Contact).

2. Dynamic Routes

  • Defined using square brackets ([param]) to create variable paths.
  • URL structure is flexible, allowing dynamic values.

Example:

 

/pages/product/[id].js → /product/123
/pages/user/[username].js → /user/john-doe

 

  • Requires fetching data (e.g., getStaticProps, getServerSideProps).
  • Best for: Pages with dynamic content (e.g., user profiles, blog posts).

Key Differences:

 

Feature Static Routes Dynamic Routes
URL Structure Fixed (/about) Flexible (/product/:id)
File Naming Regular .js file Uses [param].js
Data Fetching Not needed Required for dynamic content
Use Cases Fixed pages (About, Contact) Dynamic pages (Products, Users)

 

Conclusion:

Static routes are for fixed content, while dynamic routes allow flexible, data-driven pages. Next.js optimizes both for better performance and SEO.

9.

What is the purpose of the _app.js and _document.js files in a Next.js application?

Answer

Next.js provides two special files, _app.js and _document.js, to control the structure and behavior of your application when using the Pages Router.

In the newer App Router (Next.js 13+), these roles are handled differently.

Pages Router (pages/ Directory):

1. _app.js (Custom App Component)

  • Wraps all pages and persists layout across page changes.
  • Used for global styles, context providers, and state management.

Example:

 

function MyApp({ Component, pageProps }) {
return (
<Layout>
<Component {...pageProps} />
</Layout>
);
}
export default MyApp;

 

  • Best for:
    • Global CSS imports (import "../styles/globals.css")
    • Shared layouts (headers, footers)
    • Theme providers (e.g., Context API, Redux)

2. _document.js (Custom Document Component)

  • Controls the base HTML and <head> structure (executed only on the server).
  • Useful for injecting meta tags, fonts, and third-party scripts.

Example:

 

import { Html, Head, Main, NextScript } from "next/document";

export default function MyDocument() {
return (
<Html lang="en">
<Head>
<link rel="stylesheet" href="https://example.com/fonts.css" />
</Head>
<body>
<Main />
<NextScript />
</body>
</Html>
);
}
  • Best for:
    • Adding custom fonts and external stylesheets
    • Setting language and accessibility attributes (<Html lang="en">)
    • Preloading critical assets

 

App Router (app/ Directory in Next.js 13+):

  • In the App Router, _app.js and _document.js are replaced by the root layout.js or layout.tsx files.
  • Global styles, metadata, and layouts are now handled inside the app/layout.js file.

Example structure:

 

app/
layout.js --> Handles global structure (like _app.js + _document.js)
page.js --> Your page content

 

  • Styles defined in app/layout.js do not apply to legacy pages/* routes.

Migration Tip:

If you are migrating from the Pages Router:

  • Copy global styles and layouts from _app.js and _document.js into your app/layout.js.
  • Keep _app.js and _document.js while migrating if you still have pages in the pages/ directory.
  • Once fully migrated to the app/ directory, you can safely delete _app.js and _document.js.

 

Key Differences:

 

Feature _app.js / _document.js (Pages Router) layout.js (App Router)
Purpose Global styles, layouts, HTML structure Unified layouts and global config
Location pages/ folder app/ folder
Runs On _app.js: Server & Client, _document.js: Server only Server and Client (depending on component)
Migration Needed during partial migration Replaces them fully when migration is complete

 

Conclusion:

  • In the Pages Router, use _app.js for layouts/state and _document.js for HTML structure.
  • In the App Router, use layout.js for both purposes.
  • When migrating, keep _app.js and _document.js temporarily to avoid breaking legacy routes, and delete them after full migration.
10.

How can you add dynamic API routes in Next.js?

Answer

In Next.js App Router (Next.js 13+), dynamic API routes are created inside the /app/api/ directory.

These routes handle dynamic parameters and return JSON responses, similar to dynamic page routing.

 

Steps to Create a Dynamic API Route (App Router)

  1. Define a Dynamic API Route:
    • Use square brackets ([param]) in the folder name under /app/api/.
    • Inside the folder, create a route.js or route.ts file.
    • Example: /app/api/user/[id]/route.js → Accessible at /api/user/:id.
  2. Handle Dynamic Parameters in the Route File:
    • Extract parameters from the params argument provided by Next.js.

 

export async function GET(request, { params }) {
const { id } = params; // Extract dynamic ID from the URL

return Response.json({ message: `Fetching user with ID: ${id}` });
}

 

Advanced: Catch-All API Routes (App Router)

  • Use [...slug] in the folder name to capture multiple segments.

Example: /app/api/post/[...slug]/route.js → Handles /api/post/a/b/c.

 

export async function GET(request, { params }) {
const { slug } = params;
return Response.json({ message: `Fetching post: ${slug.join("/")}` });
}

 

Key Benefits of Dynamic API Routes in App Router:

  • Flexible: Dynamically handle URL parameters in serverless APIs.
  • Modern Structure: Organized using folders and route files (route.js), not just filenames.
  • Serverless-Ready: Works seamlessly with platforms like Vercel, AWS Lambda, and others.

 

Note:

In older Pages Router projects (/pages/api/[param].js), dynamic API routes were based on dynamic filenames.

In the App Router, dynamic routes are folder-based and defined with a route.js file.

 

Conclusion:

Dynamic API routes in the Next.js App Router allow you to build clean, scalable serverless endpoints that adapt based on URL parameters, using the new /app/api/ folder structure.

11.

How does Next.js handle CSS and global styles?

Answer

Next.js supports multiple ways to handle CSS and global styles, allowing developers to style applications efficiently.

In the App Router (Next.js 13+), global styles are handled differently compared to the old Pages Router.

 

1. Global CSS

  • Imported inside the app/layout.js (or layout.tsx) file, not in _app.js.
  • Applies styles globally across the entire application.

Example:

import '../styles/globals.css';

export default function RootLayout({ children }) {
return (
<html lang="en">
<body>{children}</body>
</html>
);
}
  • You can import multiple global styles here without restrictions (unlike the Pages Router which allowed only one global import in _app.js).

 

2. CSS Modules

  • Scoped to a specific component, preventing class name conflicts.
  • Uses the .module.css extension.

Example

 

import styles from './Button.module.css';

export default function Button() {
return <button className={styles.primary}>Click Me</button>;
}

 

CSS Modules work the same in both the App Router and Pages Router.

 

3. Styled Components & CSS-in-JS

  • You can use libraries like styled-components or Emotion for component-scoped styling.

Example with styled-components:

 

import styled from 'styled-components';

const Button = styled.button`
background: blue;
color: white;
padding: 10px;
`;

export default function MyComponent() {
return <Button>Click Me</Button>;
}

 

No changes needed for styled-components in the App Router.

 

4. Tailwind CSS & Other Frameworks

  • Next.js fully supports Tailwind CSS in the App Router.
  • Global Tailwind styles should be imported inside app/layout.js.
  • Tailwind is configured using tailwind.config.js as usual.

 

Note:

In older Pages Router projects, global CSS was imported in _app.js.

In App Router projects, you should import global styles inside layout.js.

 

Conclusion

Next.js (App Router) allows styling using global CSS (via layout.js), CSS Modules, CSS-in-JS solutions, and frameworks like Tailwind CSS.

The best approach depends on your project structure, team preferences, and scalability needs.

12.

What are environment variables in Next.js, and how do you configure them?

Answer

Environment variables in Next.js store sensitive or configurable values, such as API keys, database URLs, and feature flags, without exposing them in the source code.

How to Configure Environment Variables

  1. Create a .env.local file in the project root:
NEXT_PUBLIC_API_URL=https://api.example.com
DATABASE_SECRET=mysecretkey
  1. Access Environment Variables in Next.js
    • Server-side (only available on the server):

 

const secret = process.env.DATABASE_SECRET;

 

  • Client-side (must start with NEXT_PUBLIC_):

 

const apiUrl = process.env.NEXT_PUBLIC_API_URL;

 

Types of .env Files

  • .env.local – Used for local development (ignored by Git).
  • .env.production – Used for production environments.
  • .env.development – Used for development mode.

Key Rules

  • Client-side variables must start with NEXT_PUBLIC_.
  • Restart the server after changing environment variables.

Conclusion

Next.js environment variables help manage configurations securely by keeping sensitive data out of the frontend and version control.

13.

How can you implement image optimization in Next.js?

Answer

Next.js provides built-in image optimization using the next/image component, which improves performance by automatically resizing, compressing, and lazy-loading images.

Steps to Use Image Optimization

  1. Import and Use the next/image Component

 

import Image from 'next/image';

function MyComponent() {
return (

);
}

export default MyComponent;

 

  • The width and height props ensure proper layout.
  • Images are optimized on demand and served in modern formats like WebP.
  1. Use Remote Images (External URLs)
    • Add the domain to next.config.js:

 

module.exports = {
images: {
domains: ['example.com'],
},
};

 

Then use:

 

<Image src="https://example.com/image.jpg" width={600} height={400} alt="Remote Image" />

 

3. Enable Blur Placeholder for Lazy Loading

 

Benefits of Next.js Image Optimization

  • Automatic resizing and compression based on the device.
  • Lazy loading to improve performance.
  • Modern formats (WebP, AVIF) for better efficiency.
  • CDN caching for faster delivery.

Conclusion

Next.js**next/imagesimplifies image handling by providingautomatic optimization, lazy loading, and responsive support**, improving both performance and SEO. However, when usingexternal images, you must whitelist allowed domains in the next.config.jsfile; otherwise, Next.js will block loading those images for security reasons.

14.

What is ISR (Incremental Static Regeneration), and how does it work in Next.js?

Answer

Incremental Static Regeneration (ISR) allows Next.js to update static pages after deployment without rebuilding the entire site.

It combines the benefits of Static Site Generation (SSG) with the ability to serve fresh content dynamically.

In the App Router (Next.js 13+), ISR is handled differently compared to the old Pages Router — it uses the fetch function options directly inside server components.

 

How ISR Works in the App Router

  • Pages or components fetch data using fetch() with a revalidate option.
  • The revalidate value (in seconds) defines how often the data should be refreshed in the background.
  • When a request comes in after the revalidation time, Next.js fetches fresh data, regenerates the page, and updates the cache.

 

Example of ISR in App Router (Server Component)

 

async function PostsPage() {
const res = await fetch("https://api.example.com/posts", {
next: { revalidate: 10 }, // Regenerates every 10 seconds
});
const posts = await res.json();

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

export default PostsPage;

 

Key changes in the App Router:

  • No need for getStaticProps().
  • ISR is controlled inside fetch() using the next: { revalidate } option.
  • Works inside server components naturally.

 

When to Use ISR

  • For blogs, product listings, dashboards, and news sites where data updates periodically but doesn’t require real-time changes.
  • When avoiding full builds while still delivering SEO-friendly and fast pages.

 

Conclusion

ISR in the App Router allows server components to automatically revalidate cached data after a specified time.

It ensures content stays fresh without slowing performance or requiring full redeployments, making modern apps more scalable and dynamic.

15.

How does Next.js handle middleware, and what are its use cases?

Answer

Next.js middleware allows developers to execute custom logic before a request is processed. Middleware runs on the Edge Runtime, making it efficient for handling requests at the server level.

How Middleware Works

  • Middleware is placed in middleware.js at the root of the project.
  • It runs before rendering a page and can modify the request or response.
  • Uses the NextRequest and NextResponse objects to handle requests.

Example: Redirecting Users Based on Authentication

 

import { NextResponse } from 'next/server';

export function middleware(req) {
const token = req.cookies.get('authToken');

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

return NextResponse.next();
}

 

  • If the user is not authenticated, they are redirected to /login.
  • If authenticated, the request proceeds as usual.

Use Cases of Middleware in Next.js

  1. Authentication & Authorization – Protect routes by checking authentication tokens.
  2. Redirects & Rewrites – Modify requests dynamically based on conditions.
  3. Geo-based Content Personalization – Serve region-specific content.
  4. Rate Limiting & Security – Prevent excessive API requests or filter unwanted traffic.

Conclusion

Middleware in Next.js provides a lightweight, efficient way to intercept requests, making it useful for auth checks, redirects, and content personalization at the server level.

16.

How do you optimize performance in a Next.js application?

Answer

Optimizing performance in Next.js involves leveraging its built-in features for faster page loads, efficient data fetching, and reduced JavaScript bundle size.

Key Optimization Techniques

  1. Use Static Site Generation (SSG) & Incremental Static Regeneration (ISR)
    • Pre-render pages at build time using getStaticProps().
    • Use ISR (revalidate) to refresh data without a full rebuild.
  2. Enable Server-Side Rendering (SSR) Only When Needed
    • Use getServerSideProps() for frequently updated data to avoid unnecessary server calls.
    • Prefer SSG or ISR whenever possible to reduce server load.
  3. Optimize Images with next/image
    • Automatically resizes, compresses, and lazy-loads images.

    Example:

 

<Image src="/example.jpg" width={500} height={300} alt="Optimized Image" />

 

  1. Reduce JavaScript Bundle Size
    • Use dynamic imports with lazy loading:

 

import dynamic from 'next/dynamic';
const DynamicComponent = dynamic(() => import('../components/HeavyComponent'), { ssr: false });

 

  • Minimize dependencies and unused code.
  1. Implement Caching and CDN
    • Next.js caches static assets by default.
    • Deploy on Vercel or configure a CDN for faster global delivery.
  2. Optimize API Calls
    • Use API Routes efficiently to handle backend logic.
    • Cache API responses where possible to reduce redundant requests.
  3. Use Middleware for Efficient Request Handling
    • Implement middleware to handle authentication and redirects at the edge.

Conclusion

Next.js provides built-in performance optimizations like SSG, ISR, next/image, and caching. Properly using these features ensures faster load times, better SEO, and improved user experience.

17.

How do you implement authentication and authorization in a Next.js app?

Answer

Authentication and authorization in Next.js can be handled using API routes, middleware, and third-party authentication providers like NextAuth.js or Firebase.

 

1. Using NextAuth.js (OAuth, Credentials, JWT-based Authentication)

NextAuth.js simplifies authentication with built-in OAuth providers like Google, GitHub, and credentials-based login.

Steps to Implement NextAuth.js:

  • Install NextAuth:

 

npm install next-auth

 

  • Create an API route in the App Router (app/api/auth/[…nextauth]/route.js):

 

import NextAuth from "next-auth";
import GitHubProvider from "next-auth/providers/github";

const handler = NextAuth({
providers: [
GitHubProvider({
clientId: process.env.GITHUB_CLIENT_ID,
clientSecret: process.env.GITHUB_CLIENT_SECRET,
}),
],
secret: process.env.NEXTAUTH_SECRET,
});

export { handler as GET, handler as POST };

 

✅ In the App Router, you export both GET and POST handlers.

  • Protect pages using useSession():

 

import { useSession } from "next-auth/react";

export default function Dashboard() {
const { data: session } = useSession();

if (!session) return <p>Access Denied</p>;

return <p>Welcome, {session.user.name}</p>;
}

 

2. Custom Authentication with API Routes (App Router)

For manual authentication (e.g., database-backed login):

  • Create a login API route (app/api/login/route.js):

 

import { NextResponse } from "next/server";

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

if (username === "admin" && password === "password") {
return NextResponse.json({ token: "secure-token" });
} else {
return NextResponse.json(
{ message: "Invalid credentials" },
{ status: 401 }
);
}
}

 

✅ Notice how in the App Router, you define method-specific handlers like POST(request) instead of using req and res.

  • Store the returned token (e.g., in cookies or local storage) and validate it for protected routes.

 

3. Using Middleware for Authorization

  • Restrict access based on authentication tokens using middleware.js:

 

import { NextResponse } from "next/server";

export function middleware(request) {
const token = request.cookies.get("authToken");

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

return NextResponse.next();
}

 

✅ Middleware runs before the request is processed, allowing redirection if authentication fails.

 

Conclusion

Authentication in Next.js (App Router) can be handled via NextAuth.js (for OAuth and JWT), custom API routes (for manual authentication), and middleware (for access control), providing a secure and flexible system based on your project’s needs.

18.

Explain how you can fetch data on the client side in Next.js.

Answer

In Next.js, client-side data fetching happens in the browser after the page has loaded. This is useful for dynamic, user-specific, or frequently changing data that doesn’t need SEO optimization.

Methods for Client-Side Data Fetching

  1. Using useEffect() with fetch()
    • Best for fetching data after the component mounts.

 

import { useState, useEffect } from 'react';

function Posts() {
const [posts, setPosts] = useState([]);

useEffect(() => {
fetch('/api/posts')
.then((res) => res.json())
.then((data) => setPosts(data));
}, []);

return <div>{posts.map((post) => <p key={post.id}>{post.title}</p>)}</div>;
}

export default Posts;

 

  1. Using SWR (React Hook for Data Fetching)
    • Provides automatic caching, revalidation, and real-time updates.
    • Requires installation:

 

npm install swr

 

Example:

 

import useSWR from 'swr';

const fetcher = (url) => fetch(url).then((res) => res.json());

function Posts() {
const { data, error } = useSWR('/api/posts', fetcher);

if (error) return <p>Error loading posts</p>;
if (!data) return <p>Loading...</p>;

return <div>{data.map((post) => <p key={post.id}>{post.title}</p>)}</div>;
}

export default Posts;

 

When to Use Client-Side Fetching?

  • When SEO is not a priority (e.g., dashboards, user profiles).
  • When data is user-specific or frequently updated.
  • When you need real-time data with automatic updates (e.g., notifications, stock prices).

Conclusion

Client-side data fetching in Next.js can be done using fetch() with useEffect() for simple use cases or SWR for a more robust solution. SWR is preferred when you need automatic caching, stale-while-revalidate behavior, real-time data updates, error handling, and better performance without writing additional state management logic manually.

19.

What is the role of getLayout() in Next.js applications?

Answer

In Next.js, getLayout() was a popular pattern in the Pages Router to define custom layouts per page.

In the App Router (Next.js 13+), layouts are handled differently using nested layout.js files.

How Layouts Work in the App Router

  • Every folder inside the app/ directory can have its own layout.js file.
  • Layouts are automatically shared between pages within the same folder.
  • You don’t manually define or apply getLayout(); Next.js handles layouts natively.

Example: Defining a Layout in the App Router

 

app/
dashboard/
layout.js
page.js
settings/
layout.js
page.js

 

app/dashboard/layout.js

 

export default function DashboardLayout({ children }) {
return (
<div>
<Sidebar />
<main>{children}</main>
</div>
);
}

 

  • The DashboardLayout will automatically wrap all pages inside the dashboard/ folder.

 

When getLayout() Was Used (Pages Router)

  • Pages manually defined getLayout() to wrap a page with a custom layout.
  • It was applied inside _app.js.

Now in the App Router, this manual approach is replaced by nested layouts for better structure and built-in optimization.

 

When to Use Layouts in App Router

  • To maintain persistent UI elements like headers, sidebars, or footers across specific sections.
  • To easily nest different layouts (e.g., dashboard layout vs. public layout).
  • To avoid reloading layouts during page navigation, improving performance.

 

Conclusion

In the Pages Router, getLayout() was used to customize layouts per page manually.

In the App Router, layouts are handled automatically through nested layout.js files, providing a more scalable and consistent way to manage page structure across your application.

20.

How can you configure internationalization (i18n) in Next.js?

Answer

Next.js has built-in internationalization (i18n) routing, allowing applications to support multiple languages without additional libraries.

Steps to Configure i18n in Next.js

  1. Enable i18n in next.config.js

 

module.exports = {
i18n: {
locales: ['en', 'fr', 'de'], // Supported languages
defaultLocale: 'en', // Default language
},
};

 

  • Users are automatically redirected based on their browser’s language.
  1. Use useRouter() to Detect Locale in Components

 

import { useRouter } from 'next/router';

function HomePage() {
const { locale } = useRouter();

return <p>Current language: {locale}</p>;
}

export default HomePage;
  1. Creating Locale-Specific Pages
    • URLs are automatically prefixed with locale codes (/fr/about, /de/about).
    • You can use localized JSON files for translations.
  2. Manually Switching Locales

 

import { useRouter } from 'next/router';

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

const changeLanguage = (lang) => {
router.push(router.pathname, router.asPath, { locale: lang });
};

return (
<button onClick={() => changeLanguage('fr')}>Switch to French</button>
);
}

export default LanguageSwitcher;

 

Benefits of Next.js i18n Support

  • Automatic locale detection and routing.
  • No need for third-party i18n libraries.
  • SEO-friendly URLs for different languages.

Conclusion

Next.js makes multi-language support seamless by handling locale-based routing, translations, and redirects with minimal configuration.

21.

Explain the next/head component and when to use it.

Answer

The next/head component in Next.js allows you to modify the HTML <head> section of a page, enabling dynamic control over metadata like title, description, viewport settings, and Open Graph tags.

How to Use next/head

 

import Head from 'next/head';

function AboutPage() {
return (
<>
<Head>
<title>About Us - My Website</title>
<meta name="description" content="Learn more about our company and mission." />
<meta name="viewport" content="width=device-width, initial-scale=1" />
</Head>
<h1>About Us</h1>
</>
);
}

export default AboutPage;

 

  • The <title> tag updates dynamically for each page.
  • Meta tags improve SEO and social media previews.

When to Use next/head

  1. SEO Optimization – Set page-specific titles, descriptions, and keywords.
  2. Social Media Sharing – Define Open Graph (og:title, og:image) and Twitter meta tags.
  3. Performance Enhancements – Add preloads for fonts, scripts, or stylesheets.
  4. Favicon & Theme Colors – Customize the browser tab appearance.

Conclusion

next/head ensures each page has optimized metadata, improving SEO, user experience, and social media previews dynamically.

22.

How can you enable absolute imports and module path aliases in Next.js?

Answer

Next.js allows absolute imports and module path aliases to simplify file imports, avoiding long relative paths like ../../../components/Button.

Steps to Enable Absolute Imports & Aliases

  1. Modify jsconfig.json (for JavaScript) or tsconfig.json (for TypeScript) in the root directory:

 

{
"compilerOptions": {
"baseUrl": ".",
"paths": {
"@components/*": ["components/*"],
"@utils/*": ["utils/*"]
}
}
}

 

  • @components points to components/
  • @utils points to utils/
  1. Use Aliases in Imports

 

import Button from '@components/Button'; // Instead of '../../components/Button'
import formatDate from '@utils/formatDate'; // Instead of '../../utils/formatDate'

 

  1. Restart the Development Server

Run:

 

npm run dev

 

Benefits of Absolute Imports & Aliases

  • Improves code readability and maintainability.
  • Avoids deeply nested relative paths.
  • Easier refactoring when moving files.

Conclusion

By configuring jsconfig.json or tsconfig.json, Next.js supports cleaner, more readable imports, making development more efficient.

23.

How does Next.js handle redirects and rewrites?

Answer

Next.js provides built-in support for redirects and rewrites through the next.config.js file, allowing developers to modify request paths without extra middleware or server logic.

Redirects in Next.js

  • Redirects change the URL in the browser and send the user to a new location.
  • Defined in next.config.js:

 

module.exports = {
async redirects() {
return [
{
source: "/old-page",
destination: "/new-page",
permanent: true, // 301 Redirect
},
];
},
};

 

  • Use permanent: true for SEO-friendly 301 redirects (cached by browsers).
  • Use permanent: false for temporary 302 redirects.

Rewrites in Next.js

  • Rewrites keep the original URL visible but serve content from a different path.
  • Useful for proxying requests without changing the user’s URL.

Example:

 

module.exports = {
async rewrites() {
return [
{
source: "/blog/:slug",
destination: "https://external-api.com/posts/:slug",
},
];
},
};

 

  • The user sees /blog/post-title, but the content comes from external-api.com.

Key Differences

 

Feature Redirect Rewrite
URL Change Yes (browser sees new URL) No (URL stays the same)
Use Case Moving pages, SEO fixes Proxying, API integrations

 

Conclusion

Next.js redirects modify the URL, while rewrites serve content from a different location without changing the URL. Both are configured in next.config.js for flexible request handling.

24.

What are the differences between middleware in Next.js 12 and Next.js 13+?

Answer

Next.js 12 introduced middleware, but Next.js 13+ improved it with better performance, flexibility, and Edge Runtime enhancements.

Key Differences

 

Feature Next.js 12 Next.js 13+
Execution Runs on Node.js Optimized for Edge Runtime
Location Inside /middleware.js Same, but with better performance
Response Handling next() for forwarding requests Uses NextResponse.next()
Streaming Support No Yes (better response modification)
Performance Slower on cold starts Faster with Edge functions

 

Example Middleware in Next.js 13+

 

import { NextResponse } from 'next/server';

export function middleware(req) {
const token = req.cookies.get('authToken');

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

return NextResponse.next();
}

 

Improvements in Next.js 13+

  • Optimized for Edge Runtime (lower latency).
  • Better streaming support for faster responses.
  • More efficient redirects and rewrites.

Conclusion

Next.js 13+ enhances middleware performance with faster execution, improved Edge handling, and better streaming support, making it more efficient for real-world applications. Additionally, Next.js 13+ introduced the App Router, a major structural change that works seamlessly with middleware to handle advanced routing and layout patterns in modern applications.

25.

How does Next.js handle caching and revalidation of static pages?

Answer

Next.js optimizes performance by caching static pages and using Incremental Static Regeneration (ISR) to revalidate content without a full rebuild.

1. Caching in Next.js

  • Pre-rendered static pages are stored in a CDN cache for faster delivery.
  • Pages generated with getStaticProps() are cached and served instantly.
  • API routes and SSR pages (getServerSideProps()) are not cached since they run on every request.

2. Revalidation Using ISR

  • ISR allows automatic updates to static pages after deployment.
  • Set revalidate in getStaticProps() to refresh content at defined intervals.

Example:

 

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

return {
props: { posts: data },
revalidate: 60, // Revalidate every 60 seconds
};
}

 

  • Users get cached content until the revalidation period expires.
  • The page is updated in the background while serving the cached version.

3. Clearing the Cache Manually

  • You can trigger revalidation on demand using the res.revalidate() API inside an API route.

Example

 

export default async function handler(req, res) {
await res.revalidate('/blog');
res.json({ revalidated: true });
}

 

Conclusion

Next.js automatically caches static pages and allows incremental updates via ISR, ensuring fast performance while keeping content fresh without requiring full redeployments.

26.

How do you handle state management in Next.js applications?

Answer

Next.js, like React, supports multiple state management approaches depending on the complexity and scope of the application.

1. Local State (useState & useReducer)

  • Used for component-specific state (e.g., form inputs, modals).

Example:

 

import { useState } from 'react';

function Counter() {
const [count, setCount] = useState(0);
return <button onClick={() => setCount(count + 1)}>Count: {count}</button>;
}

export default Counter;

 

2. Context API (Global State)

  • Best for lightweight global state management (e.g., user authentication).

Example:

 

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

const ThemeContext = createContext();

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

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

 

3. Third-Party State Management (Redux, Zustand, Recoil)

  • Redux Toolkit – Best for large-scale apps with complex state.

 

import { configureStore, createSlice } from '@reduxjs/toolkit';

const counterSlice = createSlice({
name: 'counter',
initialState: { value: 0 },
reducers: {
increment: (state) => { state.value += 1; },
},
});

export const { increment } = counterSlice.actions;
export const store = configureStore({ reducer: { counter: counterSlice.reducer } });

 

  • Zustand – A simpler alternative for global state management.

 

import create from 'zustand';

const useStore = create((set) => ({
count: 0,
increase: () => set((state) => ({ count: state.count + 1 })),
}));

 

Choosing the Right Approach

 

Approach Use Case
useState / useReducer Local state within components
Context API Lightweight global state (e.g., theme, auth)
Redux Toolkit Complex, large-scale applications
Zustand / Recoil Simple, scalable global state

 

Conclusion

Next.js supports React’s built-in state management, Context API for global state, and external libraries like Redux and Zustand for handling complex state efficiently.

Next.js Interview Questions for Experienced Levels

1.

How does Next.js optimize application performance beyond automatic code splitting?

Answer

Next.js provides several built-in optimizations to enhance performance beyond automatic code splitting, ensuring faster load times and better scalability.

1. Static Generation & Incremental Static Regeneration (ISR)

  • In the Pages Router, getStaticProps() pre-builds pages at compile time for fast loading.
  • In the App Router, data fetching with fetch() and next: { revalidate } inside server components achieves the same optimization.
  • ISR allows updating static pages without a full rebuild, keeping content fresh.

2. Optimized Image Loading (next/image)

  • Automatic image resizing, lazy loading, and WebP support reduce bandwidth usage.

Example:

 

<Image src="/example.jpg" width={500} height={300} alt="Optimized Image" />

 

3. Server-Side Rendering (SSR) Only When Needed

  • In the Pages Router, getServerSideProps() is used for dynamic data fetching.
  • In the App Router, you can fetch data inside server components, and responses are streamed automatically or cached based on configuration.
  • Edge Functions allow faster server-side execution closer to users.

4. Middleware for Efficient Request Handling

  • Intercepts requests before they hit the backend, reducing load and improving response times.
  • Example use case: authentication checks, A/B testing.

5. Caching and CDN Optimization

  • Next.js caches static assets and API responses to reduce redundant network requests.
  • Deploying on Vercel or a CDN ensures global performance improvements.

6. Prefetching and Lazy Loading

  • Prefetching automatically loads pages before user interaction for faster navigation.
  • Dynamic imports (next/dynamic) reduce initial load by loading components on demand.

 

import dynamic from 'next/dynamic';
const HeavyComponent = dynamic(() => import('../components/HeavyComponent'), { ssr: false });

 

Note: getStaticProps() and getServerSideProps() are used in the Pages Router. In the App Router, data fetching happens directly inside server components using fetch() with caching and revalidation strategies.

Conclusion

Next.js enhances performance with SSG, ISR, optimized images, edge functions, caching, and prefetching, ensuring fast and scalable applications.

2.

What are the key differences between React Server Components (RSC) and traditional SSR in Next.js?

Answer

Next.js supports both React Server Components (RSC) and Traditional Server-Side Rendering (SSR), but they serve different purposes in rendering and data fetching.

1. Execution Location

  • RSC: Runs entirely on the server without sending JavaScript to the client.
  • SSR: Runs on the server but sends the rendered HTML and hydration script to the client.

2. Performance and Client Load

  • RSC: Lighter and faster because it does not send unnecessary JavaScript.
  • SSR: Can lead to higher client-side JavaScript execution due to hydration.

3. Data Fetching

  • RSC (App Router): Fetches data directly on the server inside server components, improving performance and security without needing special data-fetching functions.
  • SSR (Pages Router): Uses getServerSideProps(), meaning data is fetched on every request and passed into the page component, which increases server load.

4. When to Use Each

 

Feature React Server Components (RSC) Traditional SSR
Best For Large, interactive UIs with minimal client JS Pages needing frequent real-time updates
Client JS None (only HTML & styles sent) Sends full React components with hydration
Data Fetching Runs directly on the server Uses getServerSideProps() per request

 

Note:

  • React Server Components (RSC) are used in the App Router (/app/ directory) starting from Next.js 13+.
  • Traditional SSR (getServerSideProps()) is a Pages Router (/pages/ directory) feature in earlier versions and can still be used if needed.

Conclusion

React Server Components improve performance by reducing JavaScript bundle size and shifting more work to the server, while SSR remains useful for dynamic content that changes per request. Next.js leverages both to optimize applications based on use case.

3.

How does Next.js handle hydration, and what are the potential pitfalls?

Answer

Hydration in Next.js is the process of attaching client-side React interactivity to pre-rendered HTML from Server-Side Rendering (SSR) or Static Site Generation (SSG). It ensures that React components become interactive once the page loads.

How Next.js Handles Hydration

  1. Pre-renders HTML using SSR or SSG.
  2. Sends the JavaScript bundle to the client.
  3. Reconstructs the component tree in the browser and attaches event listeners.

Potential Pitfalls of Hydration

  1. Hydration Mismatch (Client-Server Inconsistency)
    • Happens when the server-rendered HTML differs from the client-rendered output.
    • Example issue: Using useEffect() to fetch data that modifies the UI post-render.
    • Solution (App Router): Fetch data directly in server components to ensure consistent server-side rendering.
    • Solution (Pages Router): Use getStaticProps() or getServerSideProps() for consistent data during pre-rendering.
  2. Heavy JavaScript Execution
    • If the JavaScript bundle is too large, hydration can be slow.
    • Solution: Use code splitting (next/dynamic) to load components lazily.

 

import dynamic from 'next/dynamic';
const HeavyComponent = dynamic(() => import('../components/HeavyComponent'), { ssr: false });

 

  1. Blocking the Main Thread
    • Large amounts of JavaScript execution during hydration can block the UI.
    • Solution: Use React Server Components (RSC) to reduce client-side processing.

Note:

  • In the Pages Router, getStaticProps() and getServerSideProps() were used to fetch consistent data during pre-rendering.
  • In the App Router, server components fetch data directly using fetch() at the server level before hydration.

Conclusion

Next.js optimizes hydration by pre-rendering pages, but pitfalls like mismatches, slow JavaScript execution, and UI blocking must be managed using server-side data fetching, lazy loading, and React Server Components.

4.

What is the role of Suspense in Next.js, and how does it improve data fetching?

Answer

Suspense in Next.js is a React feature that allows components to wait for data to load before rendering, improving user experience by managing loading states efficiently.

How Suspense Works in Next.js

  • It pauses rendering until the data is available.
  • Displays a fallback UI while waiting for content.
  • Optimized for React Server Components (RSC) and asynchronous data fetching.

Example: Using Suspense for Data Fetching

import { Suspense } from "react";
import ProductList from "./ProductList";

export default function HomePage() {
return (
<Suspense fallback={<p>Loading products...</p>}>
<ProductList />
</Suspense>
);
}
  • The fallback prop shows “Loading products…” while ProductList fetches data.

How Suspense Improves Data Fetching

  1. Optimized Server-Side Rendering (SSR)
    • Suspense works with React Server Components (RSC) to load data before sending HTML to the client, reducing flickering.
  2. Reduces Client-Side JavaScript Execution
    • Fetching data server-side instead of inside useEffect() minimizes JavaScript processing on the client.
  3. Improves Performance with Streaming
    • Suspense allows progressive loading, where parts of a page load as data becomes available, reducing perceived wait times.

Note:

  • Suspense for data fetching is fully supported in the App Router (Next.js 13+).
  • In the Pages Router, Suspense was only supported for client-side components and not for data fetching.

Conclusion

Suspense in Next.js enhances data fetching by delaying rendering until data is ready, reducing client-side processing, and enabling faster, more interactive pages with progressive loading.

5.

How does Next.js handle caching strategies for both static and dynamic content?

Answer

Next.js optimizes performance using built-in caching strategies for static and dynamic content, ensuring faster page loads and reduced server load.

1. Static Content Caching (SSG & ISR)

  • Static Site Generation (SSG): Pages are built ahead of time and served from the cache.
  • Incremental Static Regeneration (ISR): Allows automatic cache updates without rebuilding the entire site.

Example (App Router server component):

 

async function PostsPage() {
const res = await fetch("https://api.example.com/posts", {
next: { revalidate: 60 }, // Revalidate every 60 seconds
});
const data = await res.json();

return (
<div>{data.map(post => <p key={post.id}>{post.title}</p>)}</div>
);
}

export default PostsPage;

 

2. Dynamic Content Caching (SSR & API Routes)

  • Server-Side Rendering (SSR) pages are not cached and are regenerated on each request.
  • API Routes can use HTTP cache headers to control caching.
  • Example of API response caching:

 

export default function handler(req, res) {
res.setHeader("Cache-Control", "s-maxage=300, stale-while-revalidate=60");
res.json({ message: "This response is cached for 5 minutes" });
}

 

  • s-maxage=300: Cache for 5 minutes.
  • stale-while-revalidate=60: Serve stale content while fetching a fresh version.

3. Edge and CDN Caching

  • Deploying on Vercel or Cloudflare automatically caches static content.
  • Dynamic responses can be cached at the edge for faster global delivery.

Note:

  • In the Pages Router, static caching and ISR were configured using getStaticProps() with a revalidate field.
  • In the App Router, caching is handled inside server components using fetch() with the next: { revalidate } option.

Conclusion

Next.js optimizes caching by serving static pages instantly (SSG, ISR), controlling API response caching, and leveraging CDN caching for performance. Proper caching reduces server load and improves scalability.

6.

How can you optimize API routes in Next.js for high-performance applications?

Answer

Optimizing Next.js API routes ensures faster response times, reduced server load, and better scalability.

 

1. Use Cache-Control Headers

  • Reduce unnecessary API calls by caching responses.

Example (App Router API route):

 

import { NextResponse } from "next/server";

export async function GET() {
const response = NextResponse.json(
{ message: "This response is cached for 5 minutes" },
{ status: 200 }
);

response.headers.set("Cache-Control", "s-maxage=300, stale-while-revalidate=60");

return response;
}

 

2. Optimize Database Queries

  • Use indexes in databases like PostgreSQL or MongoDB.
  • Fetch only required fields to reduce payload size.

Example (MongoDB query optimization):

 

const users = await db.collection("users").find({}, { projection: { password: 0 } }).toArray();

 

3. Implement Rate Limiting

  • Prevent API abuse using middleware like express-rate-limit or Next.js Middleware.

Example using App Router Middleware:

 

import { NextResponse } from 'next/server';

let requestCount = 0;

export function middleware(request) {
requestCount++;

if (requestCount > 100) {
return NextResponse.json(
{ error: "Rate limit exceeded" },
{ status: 429 }
);
}

return NextResponse.next();
}

 

4. Use Streaming for Large Responses

  • Send data in chunks instead of waiting for the full response.

Example (App Router API route):

 

export async function GET() {
const encoder = new TextEncoder();
const stream = new ReadableStream({
start(controller) {
controller.enqueue(encoder.encode("First chunk..."));
setTimeout(() => {
controller.enqueue(encoder.encode("Final chunk"));
controller.close();
}, 1000);
}
});

return new Response(stream);
}

 

5. Move Heavy Computation to Background Jobs

  • Offload processing to serverless functions or background workers instead of blocking API responses.

 

Conclusion

Optimizing API routes in Next.js involves caching, query optimization, rate limiting, streaming, and offloading heavy tasks, ensuring high performance and scalability.

 

Note:

  • In the Pages Router, API routes used req and res objects inside pages/api/.
  • In the App Router, API routes are organized in app/api/ folders with method-specific exports like GET(), POST() inside route.js files.
7.

Explain how to handle request validation and error handling in Next.js API routes.

Answer

Next.js API routes need proper validation to ensure data integrity and error handling to improve reliability.

 

1. Request Validation

  • Use Zod or Yup to validate incoming request data.

Example using Zod (App Router style):

 

import { z } from "zod";
import { NextResponse } from "next/server";

const schema = z.object({
name: z.string().min(3),
email: z.string().email(),
});

export async function POST(request) {
const body = await request.json();
const validation = schema.safeParse(body);

if (!validation.success) {
return NextResponse.json(
{ error: validation.error.errors },
{ status: 400 }
);
}

return NextResponse.json({ message: "Valid request" }, { status: 200 });
}

 

2. Error Handling

  • Use try-catch to handle errors gracefully.
  • Return appropriate HTTP status codes (400, 401, 500).

Example (App Router style):

 

import { NextResponse } from "next/server";

export async function GET() {
try {
const res = await fetch("https://api.example.com/data");
const data = await res.json();

return NextResponse.json(data, { status: 200 });
} catch (error) {
return NextResponse.json(
{ error: error.message || "Internal Server Error" },
{ status: 500 }
);
}
}

 

3. Centralized Error Handling (Middleware Approach)

  • Use Next.js Middleware to handle errors globally.

Example (App Router Middleware):

 

import { NextResponse } from "next/server";

export function middleware(request) {
const authHeader = request.headers.get("Authorization");

if (!authHeader) {
return NextResponse.json(
{ error: "Unauthorized" },
{ status: 401 }
);
}

return NextResponse.next();
}

 

Conclusion

Handling request validation and errors in Next.js API routes ensures data consistency, security, and better user experience.

Using Zod for validation, try-catch for error handling, and middleware for global handling makes APIs more robust in both Pages Router and App Router.

 

Note:

  • In the Pages Router, API routes used a handler(req, res) pattern inside pages/api/.
  • In the App Router, API routes are defined with method-specific exports like GET(request) or POST(request) inside route.js files.
8.

What are the best practices for integrating GraphQL with Next.js?

Answer

Integrating GraphQL with Next.js allows efficient data fetching and API management while maintaining high performance and scalability.

 

1. Choose a GraphQL Client

  • Apollo Client – Feature-rich, caching, and state management.
  • urql – Lightweight and optimized for performance.
  • Relay – Best for large-scale apps with complex data needs.

 

2. Setting Up Apollo Client in Next.js (App Router)

  • Install dependencies:

 

npm install @apollo/client graphql

 

  • Create an Apollo Client instance:

 

// lib/apolloClient.js
import { ApolloClient, InMemoryCache, HttpLink } from "@apollo/client";

const client = new ApolloClient({
uri: "https://api.example.com/graphql",
cache: new InMemoryCache(),
});

export default client;

 

  • Create an Apollo Provider wrapper:

 

// app/providers.jsx
import { ApolloProvider } from "@apollo/client";
import client from "../lib/apolloClient";

export function Providers({ children }) {
return {children};
}

 

  • Use it inside your app/layout.js:
import { Providers } from "./providers";

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

3. Fetching Data in Next.js

  • Using useQuery (Client-Side Fetching)

 

import { gql, useQuery } from "@apollo/client";

const GET_POSTS = gql`
query {
posts {
id
title
}
}
`;

export default function Posts() {
const { data, loading, error } = useQuery(GET_POSTS);

if (loading) return <p>Loading...</p>;
if (error) return <p>Error: {error.message}</p>;

return <ul>{data.posts.map((post) => <li key={post.id}>{post.title}</li>)}</ul>;
}

 

  • Fetching GraphQL Data Inside a Server Component

 

// app/posts/page.jsx
import client from "@/lib/apolloClient";
import { gql } from "@apollo/client";

const GET_POSTS = gql`
query {
posts {
id
title
}
}
`;

export default async function PostsPage() {
const { data } = await client.query({ query: GET_POSTS });

return (
<ul>
{data.posts.map((post) => (
<li key={post.id}>{post.title}</li>
))}
</ul>
);
}

 

✅ In the App Router, you can directly fetch data inside a server component without getServerSideProps().

 

4. Optimize Performance

  • Use Caching (InMemoryCache) to reduce redundant requests.
  • Avoid Overfetching by selecting only required fields in queries.
  • Use Server Components for SEO optimization and faster performance.

 

Conclusion

Integrating GraphQL with Next.js requires choosing the right client, optimizing queries with caching, and leveraging server components for better performance.

Using Apollo Client with server-side and client-side components ensures scalable, high-performance data fetching.

 

Note:

  • In the Pages Router, GraphQL data was often fetched using getServerSideProps() or getStaticProps() inside page components.
  • In the App Router, data fetching happens directly inside server components for better performance and scalability.
9.

How do you implement rate limiting in Next.js API routes?

Answer

Rate limiting in Next.js API routes prevents excessive requests, protecting the server from abuse and improving performance.

 

1. Using a Simple In-Memory Rate Limiter

  • Tracks request counts using a JavaScript object (works best for single-instance deployments)

 

import { NextResponse } from "next/server";

const rateLimit = {};
const WINDOW_MS = 60 * 1000; // 1 minute
const MAX_REQUESTS = 5;

export async function GET(request) {
const ip = request.headers.get("x-forwarded-for") || "unknown";

if (!rateLimit[ip]) {
rateLimit[ip] = { count: 0, startTime: Date.now() };
}

const currentTime = Date.now();

if (currentTime - rateLimit[ip].startTime > WINDOW_MS) {
rateLimit[ip] = { count: 1, startTime: currentTime };
} else {
rateLimit[ip].count++;
}

if (rateLimit[ip].count > MAX_REQUESTS) {
return NextResponse.json(
{ error: "Too many requests. Please try again later." },
{ status: 429 }
);
}

return NextResponse.json({ message: "Request successful" });
}

 

Limitations:

  • Works only in single-server setups (not distributed).
  • Memory usage increases with unique IPs.

 

2. Using Redis for Scalable Rate Limiting

  • Redis is ideal for distributed applications.
  • Install ioredis:

 

npm install ioredis

 

  • Implement rate limiting using Redis:

 

import { NextResponse } from "next/server";
import Redis from "ioredis";

const redis = new Redis(process.env.REDIS_URL);
const WINDOW_SEC = 60;
const MAX_REQUESTS = 5;

export async function GET(request) {
const ip = request.headers.get("x-forwarded-for") || "unknown";
const requests = await redis.incr(ip);

if (requests === 1) {
await redis.expire(ip, WINDOW_SEC);
}

if (requests > MAX_REQUESTS) {
return NextResponse.json(
{ error: "Too many requests. Try again later." },
{ status: 429 }
);
}

return NextResponse.json(
{ message: "Request successful" },
{ status: 200 }
);
}

 

Advantages of Redis:

  • Works in serverless and multi-instance environments.
  • Supports automatic expiry to free up memory.

 

3. Using Next.js Middleware for Global Rate Limiting

  • Middleware can handle rate limiting before the request reaches the API route.

 

import { NextResponse } from "next/server";

const requests = new Map();
const WINDOW_MS = 60000;
const MAX_REQUESTS = 5;

export function middleware(request) {
const ip = request.ip || request.headers.get("x-forwarded-for") || "unknown";
const now = Date.now();

const userData = requests.get(ip);

if (!userData) {
requests.set(ip, { count: 1, startTime: now });
} else {
if (now - userData.startTime < WINDOW_MS) {
userData.count++;
if (userData.count > MAX_REQUESTS) {
return NextResponse.json(
{ error: "Too many requests" },
{ status: 429 }
);
}
} else {
requests.set(ip, { count: 1, startTime: now });
}
}

return NextResponse.next();
}

 

Conclusion

Rate limiting in Next.js can be implemented in-memory for simple cases, with Redis for scalability, or using middleware for pre-filtering requests.

For production, Redis-based solutions are preferred to handle high-traffic applications effectively.

 

Note:

  • In the Pages Router, API routes used a handler(req, res) pattern inside pages/api/.
  • In the App Router, API routes are defined with method-specific exports like GET(request) or POST(request) inside route.js files.
10.

How does streaming work with getServerSideProps() and API routes in Next.js?

Answer

Streaming in Next.js allows progressive data rendering, improving performance by sending chunks of data before the full response is ready. This reduces perceived wait times and speeds up page loads.

 

1. Streaming with React Server Components and Suspense

  • In the App Router, pages can stream data automatically by using React Server Components and Suspense.
  • You don’t need getServerSideProps() anymore.
  • Example using Suspense:

 

import React, { Suspense } from 'react';

// This component might fetch data from your API endpoint
async function fetchStreamedData() {
const response = await fetch('/api/stream');
const reader = response.body.getReader();
let text = '';
while (true) {
const { done, value } = await reader.read();
if (done) break;
text += new TextDecoder().decode(value);
}
return text;
}

function StreamedComponent({ data }) {
return <pre>{data}</pre>;
}

function App() {
const dataPromise = fetchStreamedData();

return (
<Suspense fallback={<div>Loading streamed data...</div>}>
<StreamedComponent data={dataPromise} />
</Suspense>
);
}

export default App;

 

  • This ensures the page renders immediately with placeholders and loads real data progressively.

 

2. Streaming in API Routes

  • In the App Router, API responses can be sent in chunks using ReadableStream.

Example:

 

export async function GET() {
const encoder = new TextEncoder();
const stream = new ReadableStream({
start(controller) {
controller.enqueue(encoder.encode("First chunk...\n"));
setTimeout(() => {
controller.enqueue(encoder.encode("Second chunk...\n"));
controller.enqueue(encoder.encode("Final chunk."));
controller.close();
}, 2000);
},
});

return new Response(stream, {
headers: { "Content-Type": "text/plain" },
});
}

 

  • The client receives data progressively rather than waiting for the full response.

 

Benefits of Streaming in Next.js

  • Faster perceived load times by showing content early.
  • Reduces time to first byte (TTFB) for better user experience.
  • Efficient data fetching by avoiding blocking the UI.

 

Conclusion

Next.js streams data progressively using React Server Components with Suspense for pages and ReadableStream for API routes, improving performance and reducing wait times.

 

Note:

  • In the Pages Router, getServerSideProps() was used to fetch all data before rendering.
  • In the App Router, data is streamed automatically inside React Server Components, without needing special data-fetching functions.
11.

How do you implement user authentication efficiently using Next.js Middleware?

Answer

Next.js Middleware allows authentication checks before a request reaches a page or API route, improving security and performance by handling auth logic at the Edge or server-side.

1. Protecting Routes with Middleware

  • Redirects unauthenticated users to the login page before rendering the protected page.

Example:

 

import { NextResponse } from "next/server";

export function middleware(req) {
const token = req.cookies.get("authToken"); // Get token from cookies

if (!token) {
return NextResponse.redirect(new URL("/login", req.url));
}

return NextResponse.next();
}

export const config = {
matcher: ["/dashboard/:path*"], // Protect all /dashboard routes
};

 

  • How It Works:
    • If no token is found, the user is redirected to /login.
    • If authenticated, the request proceeds normally.

2. Verifying Tokens (JWT Authentication)

  • Middleware can decode and verify JWT tokens before allowing access.

Example using jsonwebtoken:

 

import { NextResponse } from "next/server";
import jwt from "jsonwebtoken";

export function middleware(req) {
const token = req.cookies.get("authToken");

try {
jwt.verify(token, process.env.JWT_SECRET); // Verify JWT
return NextResponse.next();
} catch (error) {
return NextResponse.redirect(new URL("/login", req.url));
}
}

export const config = {
matcher: ["/protected/:path*"], // Protect specific routes
};

 

3. Benefits of Using Middleware for Authentication

  • Runs before page loads → Prevents unnecessary rendering.
  • Works at the Edge → Faster request processing.
  • Reduces API calls → No need for client-side auth checks on every page load.

Conclusion

Using Next.js Middleware for authentication ensures efficient, server-side access control by validating tokens before rendering protected pages, leading to better security and performance. As a side note, developers can also use NextAuth.js to handle authentication automatically through configuration, offering built-in support for sessions, providers, and middleware.

12.

What are the trade-offs between running middleware at the Edge versus running it server-side?

Answer

Next.js allows middleware to run at the Edge (Edge Functions) or on the server (Node.js runtime). Each approach has advantages and trade-offs depending on performance, security, and scalability needs.

1. Edge Middleware

Pros:

  • Lower Latency – Runs closer to the user, reducing response times.
  • Scalability – Handles global traffic efficiently with Edge networks (e.g., Vercel, Cloudflare).
  • Prevents Unnecessary Requests – Can block or modify requests before they reach the origin server.

Cons:

  • Limited APIs – Cannot use full Node.js features (e.g., file system, some database connections).
  • Cold Starts in Some Providers – May experience slight delays in serverless environments.
  • Data Fetching Limitations – Cannot make direct API calls before forwarding requests.

2. Server-Side Middleware (Node.js Runtime)

Pros:

  • Full Node.js Support – Can access databases, file system, and third-party APIs.
  • Better for Heavy Processing – Handles complex logic before rendering pages.
  • More Flexible Request Handling – Can modify request headers, cookies, and body content deeply.

Cons:

  • Higher Latency – Requests travel to a centralized server instead of being handled at the Edge.
  • Increased Load on Backend – Every request reaches the origin server, increasing costs.
  • Scaling Challenges – Requires additional infrastructure to handle large traffic.

Choosing the Right Approach

 

Feature Edge Middleware Server-Side Middleware
Performance Faster (low latency) Slower (higher latency)
Scalability High (Edge network) Lower (centralized server)
Access to Node.js APIs Limited Full
Data Fetching Restricted Full API access
Best For Authentication, geo-based personalization, redirects Complex logic, database interactions

 

Conclusion

  • Use Edge Middleware for fast authentication, geo-based content, and redirects.
  • Use Server-Side Middleware when full Node.js capabilities (e.g., database queries) are required.
  • In many cases, a hybrid approach combining both is the best solution.
13.

How can you use Next.js Middleware for geo-based content delivery?

Answer

Next.js Middleware allows geo-based content delivery by detecting a user’s location from the request headers and serving region-specific content before the page renders.

1. Detecting User Location in Middleware

  • Next.js automatically provides req.geo (on platforms like Vercel) to access country, region, and city.

Example:

 

import { NextResponse } from "next/server";

export function middleware(req) {
const country = req.geo?.country || "US"; // Default to US if no data
const url = req.nextUrl.clone();

if (country === "FR") {
url.pathname = "/fr"; // Redirect French users
} else if (country === "DE") {
url.pathname = "/de"; // Redirect German users
}

return NextResponse.rewrite(url); // Rewrite without changing the URL
}

export const config = {
matcher: ["/"], // Apply middleware to the homepage
};

 

  • What This Does:
    • Detects the user’s country.
    • Rewrites the request to the appropriate localized version without changing the URL.

2. Serving Dynamic Content Based on Location

  • Instead of redirects, you can modify the response content dynamically.

Example:

 

export function middleware(req) {
const country = req.geo?.country || "US";

const response = NextResponse.next();
response.headers.set("X-Country", country); // Pass location to frontend
return response;
}

 

  • The frontend can then use this header to display localized content.

3. Benefits of Geo-Based Middleware

  • Faster than client-side detection (avoids additional API calls).
  • No extra page reloads (rewrites instead of redirects).
  • Efficient Edge Processing – Works at the Edge network, reducing latency.

Conclusion

Next.js Middleware enables fast, location-based content delivery by detecting a user’s geo-location at the Edge and dynamically serving localized content or redirects before rendering.

14.

What are the limitations of Edge Functions in Next.js compared to traditional SSR?

Answer

Edge Functions in Next.js run closer to users for lower latency but have some limitations compared to traditional Server-Side Rendering (SSR) running on a Node.js server.

1. Limited Access to Node.js APIs

  • Edge Functions run in a lightweight runtime, meaning:
    • No access to the file system (fs module).
    • No built-in support for server-side WebSockets.
    • Limited compatibility with some npm packages.
  • SSR on Node.js supports full Node.js features, including file operations and long-running tasks.

2. Restrictions on Database Connections

  • Edge Functions cannot maintain persistent database connections (e.g., PostgreSQL, MySQL) due to their stateless nature.
  • They work best with serverless databases (e.g., DynamoDB, FaunaDB, Planetscale) or HTTP-based database queries (e.g., Prisma Data Proxy).
  • SSR on Node.js can maintain direct database connections efficiently.

3. Execution Time Limits

  • Edge Functions are designed for fast, lightweight execution and have strict time limits (often <5 seconds).
  • SSR on Node.js can handle longer-running processes, making it better for heavy computations.

4. No Built-in Streaming Support

  • Edge Functions do not yet fully support React Streaming (getServerSideProps() with partial rendering).
  • SSR on Node.js allows streaming responses, progressively loading content for better performance.

5. Deployment Considerations

  • Edge Functions require Edge-compatible platforms (e.g., Vercel, Cloudflare Workers).
  • SSR on Node.js can be deployed on any server (AWS, DigitalOcean, traditional VPS).

Choosing Between Edge Functions and SSR

 

Feature Edge Functions Traditional SSR (Node.js)
Latency Faster (runs closer to users) Slower (centralized server)
Node.js APIs Limited Full access
Database Support Works best with serverless DBs Supports persistent DB connections
Execution Time Short (<5s) Longer allowed
Streaming Limited support Fully supported

 

Conclusion

Edge Functions are best for authentication, A/B testing, geo-based content, and caching, while SSR on Node.js is better for database-heavy apps, complex computations, and streaming responses. The right choice depends on application needs and infrastructure.

15.

How can you optimize security using Next.js Middleware?

Answer

Next.js Middleware helps improve security by filtering requests before they reach the application, preventing unauthorized access, injection attacks, and other threats.

1. Implement Authentication and Authorization

  • Middleware can verify JWT tokens or session cookies before allowing access.

Example:

 

import { NextResponse } from "next/server";
import jwt from "jsonwebtoken";

export function middleware(req) {
const token = req.cookies.get("authToken");

try {
jwt.verify(token, process.env.JWT_SECRET);
return NextResponse.next();
} catch (error) {
return NextResponse.redirect(new URL("/login", req.url));
}
}

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

 

  • Ensures only authenticated users access protected routes.

2. Prevent API Abuse with Rate Limiting

  • Middleware can throttle requests to prevent DDoS attacks.

Example:

 

const requests = new Map();

export function middleware(req) {
const ip = req.ip;
const now = Date.now();

if (!requests.has(ip)) requests.set(ip, { count: 1, startTime: now });
else {
const userData = requests.get(ip);
if (now - userData.startTime < 60000) {
userData.count++;
if (userData.count > 5) return new Response("Too many requests", { status: 429 });
} else {
requests.set(ip, { count: 1, startTime: now });
}
}

return NextResponse.next();
}

 

  • Limits users to 5 requests per minute.

3. Secure Headers to Prevent Attacks

  • Middleware can enforce CSP, XSS protection, and clickjacking prevention.

Example:

 

export function middleware(req) {
const response = NextResponse.next();
response.headers.set("Content-Security-Policy", "default-src 'self'");
response.headers.set("X-Frame-Options", "DENY");
response.headers.set("X-XSS-Protection", "1; mode=block");
return response;
}

 

  • Protects against cross-site scripting (XSS) and clickjacking.

4. Block Unwanted Traffic (Geo-Blocking & IP Filtering)

  • Restrict access based on IP address or location.

Example:

 

export function middleware(req) {
const country = req.geo?.country;
if (country === "CN") {
return new Response("Access Denied", { status: 403 });
}
return NextResponse.next();
}

 

  • Blocks access from certain regions.

Conclusion

Next.js Middleware enhances security by authenticating users, limiting API abuse, enforcing security headers, and blocking malicious traffic before requests reach the application.

16.

How does Next.js handle nested dynamic routes efficiently?

Answer

Next.js automatically manages nested dynamic routes using its file-based routing system, allowing scalable and efficient URL structures without extra configurations.

 

1. Defining Nested Dynamic Routes (App Router)

  • Use square brackets ([param]) to create dynamic segments inside the /app directory.

Example structure:

/app
/blog
/[category]
/[post]
page.js → /blog/:category/:post
  • This structure allows URLs like:
    • /blog/technology/nextjs-performance
    • /blog/design/ui-ux-trends

 

2. Accessing Dynamic Parameters

  • In the App Router, dynamic route parameters are passed automatically via the params prop in your page.js component.

Example:

 

export default function BlogPostPage({ params }) {
const { category, post } = params;

return <h1>Category: {category} - Post: {post}</h1>;
}

 

  • No need for useRouter() or getStaticProps().
  • Optimizations:
    • Static pages are automatically cached at build time unless dynamic rendering is explicitly configured.

 

3. Catch-All Routes for Scalability

  • Use [...slug] to create catch-all dynamic routes for variable depth URLs (e.g., /blog/category/post/sub-post).
  • Folder structure:
export default function BlogPostPage({ params }) {
const { category, post } = params;

return <h1>Category: {category} - Post: {post}</h1>;
}

Example page component:

 

export default function SlugPage({ params }) {
return <p>Path: {params.slug.join(" / ")}</p>;
}

 

  • This supports URLs like /blog/tech/nextjs/performance without needing to predefine every segment.

 

Conclusion

Next.js efficiently handles nested dynamic routes in the App Router using folder-based routing, automatic params injection, and catch-all segments, ensuring scalability and high performance without extra configuration.

 

Note:

  • In the Pages Router, nested dynamic routes used pages/, getStaticPaths(), and getStaticProps().
  • In the App Router, dynamic parameters are accessed automatically through the params prop inside server components.
17.

How do you prefetch routes in Next.js to improve navigation speed?

Answer

Next.js automatically prefetches routes linked with the next/link component, improving navigation speed by loading pages in the background before the user clicks a link.

 

1. Default Route Prefetching with next/link (App Router)

  • In the App Router, using next/link automatically prefetches pages when links appear in the viewport.

Example:

import Link from "next/link";

export default function HomePage() {
return (

Go to About

);
}
  • No need for an <a> tag inside Link.
  • When /about is visible, Next.js preloads its JavaScript and assets automatically.

 

2. Manually Enabling or Disabling Prefetching

  • You can control preloading behavior with the prefetch prop.

Example (disable prefetching):

 

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

 

  • Disabling prefetching can save bandwidth for rarely visited pages.

 

3. Prefetching Programmatically Using useRouter

  • In the App Router, use useRouter from next/navigation to programmatically prefetch routes.

Example (prefetch on hover):

 

import { useRouter } from "next/navigation";

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

return (
<button onMouseEnter={() => router.prefetch("/dashboard")}>
Dashboard (Hover to Prefetch)
</button>
);
}

 

  • This approach helps prefetch only when needed, like on user interaction.

 

4. Benefits of Route Prefetching in Next.js

  • Reduces perceived load time – Pages load almost instantly after clicking.
  • Optimized bandwidth usage – Prefetching happens only for visible links.
  • Works seamlessly with Static Generation (SSG) and ISR – Prefetched pages are served instantly.

 

Conclusion

Next.js automatically prefetches routes linked with next/link, and manual prefetching using useRouter improves performance by reducing navigation delays while optimizing resource usage.

 

Note:

  • In the Pages Router, Link required a child <a> tag and next/router was used for prefetching.
  • In the App Router, Link directly renders links, and useRouter is imported from next/navigation for programmatic prefetching.
18.

How can you implement custom route handlers in Next.js?

Answer

Custom route handlers in Next.js allow you to define server-side logic for requests directly inside your app.

In the App Router (Next.js 13+), this is done inside the app/api/ directory.

 

1. Creating API Route Handlers with the App Router (app/api/)

  • API routes are now created under the app/api directory with file-based routing.

Example (app/api/user/route.js):

 

// app/api/user/route.js

import { NextResponse } from "next/server";

export async function GET() {
return NextResponse.json({ message: "User data" }, { status: 200 });
}

export async function POST(request) {
const data = await request.json();
return NextResponse.json({ received: data }, { status: 201 });
}

 

  • Each HTTP method (GET, POST, PUT, DELETE) is handled by exported functions.
  • Handlers run on the Edge Runtime by default for faster performance.

Use Cases:

  • Building RESTful APIs inside your Next.js application.
  • Handling form submissions, user authentication, and external API calls.

 

2. Legacy Support: pages/api/ (Pages Router)

  • In the Pages Router, custom APIs were created inside pages/api/.

Example (pages/api/hello.js):

 

export default function handler(req, res) {
if (req.method === "GET") {
res.status(200).json({ message: "Hello from Next.js API route!" });
} else {
res.status(405).json({ error: "Method Not Allowed" });
}
}

 

  • Note:This method still works but is considered legacy. New apps should prefer app/api/.

 

3. Middleware-Based Custom Route Handling

  • Middleware allows you to intercept requests globally before they reach a route.

Example (middleware.js):

 

import { NextResponse } from "next/server";

export function middleware(request) {
const authToken = request.cookies.get("authToken");

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

return NextResponse.next();
}

export const config = {
matcher: ["/api/:path*"], // Apply middleware to all API routes
};

 

  • Middleware is useful for authentication, rate limiting, geo-blocking, and logging.

 

Conclusion

In Next.js with the App Router, custom route handlers are created inside the app/api/ directory using exported HTTP functions.

Middleware can also be used to preprocess or block requests before they hit the route handlers.

The Pages Router (pages/api/) method is still available for legacy support but should be avoided in new projects.

 

Note:

  • In the Pages Router, API routes were created inside pages/api/.
  • In the App Router, API routes live in app/api/ and use server or edge handlers directly.
19.

What are the potential challenges of deep linking in a Next.js application?

Answer

Deep linking allows users to navigate directly to a specific page within a Next.js app, but it comes with challenges related to dynamic routing, authentication, and SEO.

1. Handling Dynamic Routes (App Router)

  • Challenge: If a deep link points to a dynamic route (e.g., /post/:id), the page may fail if the required data isn’t available.

For static pages (e.g., using generateStaticParams()), only pre-rendered paths are available. If a user navigates directly to a deep link that wasn’t included in the pre-generated list, they’ll hit a 404.

For server-rendered or incrementally generated pages, the app can fetch data on the fly using fetch in page.js with caching strategies or by setting the route segment to dynamic = “force-dynamic”. This ensures the route can resolve dynamically at request time.

  • Solution: Use generateStaticParams() to pre-generate paths or enable dynamic rendering with dynamic = “force-dynamic” in route segments.

Example (dynamic route handler):

 

// app/blog/[slug]/page.js
export async function generateStaticParams() {
const posts = await fetchPosts();
return posts.map((post) => ({ slug: post.slug }));
}

export default async function BlogPost({ params }) {
const post = await fetchPost(params.slug);
return <div>{post.title}</div>;
}

 

2. Authentication and Protected Routes

  • Challenge: Users accessing deep links may not be authenticated, leading to unauthorized access or redirects.
  • Solution: Protect routes at the layout or with Middleware in the App Router to check authentication before rendering.

 

3. SEO and Page Previews

  • Challenge: Search engines and social media previews may not properly render deep links if client-side fetching is required.
  • Solution: Use Server Components and dynamic metadata (generateMetadata()) to ensure proper SEO and page previews.

Example:

 

export async function generateMetadata({ params }) {
const post = await fetchPost(params.slug);
return {
title: post.title,
description: post.excerpt,
};
}

 

4. Client-Side Navigation vs. Direct Access

  • Challenge: Some pages rely on client-side state, which is lost on deep linking (e.g., form progress).
  • Solution: Persist important state using URL query parameters, cookies, or local storage.

 

5. CDN and Caching Issues

  • Challenge: Deep links may load outdated content if the CDN caches old pages.
  • Solution: Use Incremental Static Regeneration (ISR) with revalidate options in the App Router to refresh content automatically.

Example:

 

export const revalidate = 60; // Revalidate every 60 seconds

 

Conclusion

Deep linking in Next.js (App Router) requires careful handling of dynamic routes, authentication at the layout or middleware level, server-rendered metadata for SEO, and smart caching to ensure seamless navigation and fresh content.

 

Note:

  • In the Pages Router, deep linking challenges were handled with getStaticPaths() and getServerSideProps().
  • In the App Router, use generateStaticParams(), dynamic rendering options, and server components to address the same concerns.
20.

What are the best practices for handling global state in a Next.js application?

Answer

Best Practices for Handling Global State in a Next.js Application

These solutions are not specific to Next.js—they are general React.js patterns that can be applied within a Next.js app.

Managing global state in a Next.js app efficiently ensures smooth performance, scalability, and maintainability. The best approach depends on the application’s complexity and data persistence needs.

1. Context API for Lightweight State Management

  • Ideal for small-scale global state (e.g., theme, authentication).

Example:

 

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

const ThemeContext = createContext();

export function ThemeProvider({ children }) {
const [theme, setTheme] = useState("light");
return (

{children}

);
}

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

 

  • Best For: Simple global state that doesn’t require frequent updates.

2. Redux Toolkit for Large-Scale Applications

  • Best when dealing with complex state across multiple components.

Example setup:

 

import { configureStore, createSlice } from "@reduxjs/toolkit";

const counterSlice = createSlice({
name: "counter",
initialState: { value: 0 },
reducers: { increment: (state) => { state.value += 1; } },
});

export const { increment } = counterSlice.actions;
export const store = configureStore({ reducer: { counter: counterSlice.reducer } });

 

  • Best For: Large applications needing centralized state management.

3. Zustand for Minimalist State Management

  • A lighter alternative to Redux, offering better performance.
  • Example:

 

import create from "zustand";

const useStore = create((set) => ({
count: 0,
increase: () => set((state) => ({ count: state.count + 1 })),
}));

 

  • Best For: Applications needing efficient, simple state updates.

4. Persisting State with Local Storage or Cookies

  • Use local storage for non-sensitive UI preferences.
  • Use cookies for authentication or user sessions (server-side accessible).

Conclusion

For small applications, Context API or Zustand works well. For larger apps, Redux Toolkit ensures structured state management. Choosing the right method depends on state complexity and performance needs.

21.

How do you optimize server-side state hydration in Next.js?

Answer

Server-side state hydration in Next.js ensures that server-fetched data is correctly transferred to the client while maintaining performance and avoiding hydration mismatches.

1. Use Efficient Server-Side Data Fetching

  • In the App Router, fetch data directly inside server components using async functions and fetch().
  • In the Pages Router, use getServerSideProps() to fetch data server-side.

Example (Pages Router using getServerSideProps()):

 

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

 

  • Benefit: Reduces initial page load time by sending only required data.

2. Avoid Client-Server Mismatches

  • Ensure server-rendered and client-rendered data match to prevent hydration errors.

Example of a common issue (Pages Router):

 

export async function getServerSideProps() {
return { props: { timestamp: Date.now() } }; // Causes mismatch
}

 

  • Fix: Generate timestamps only on the client using useEffect().

3. Use Lazy Loading for Large Data

  • Load heavy components or additional data after hydration to avoid blocking the main thread.

Example:

 

import { useEffect, useState } from "react";

function Page({ initialData }) {
const [extraData, setExtraData] = useState(null);

useEffect(() => {
fetch("/api/extra")
.then((res) => res.json())
.then(setExtraData);
}, []);

return <div>{extraData ? extraData.value : initialData}</div>;
}

 

4. Minimize JSON Stringification Overhead

  • Problem: Deeply nested objects increase the cost of JSON.stringify() during server-to-client transfer.
  • Solution: Normalize or flatten data structures to make serialization faster and lighter.

5. Use React Server Components for Partial Hydration

  • In the App Router, React Server Components allow parts of the UI to load without sending unnecessary JavaScript to the client, improving overall performance.

Conclusion

Optimizing server-side state hydration in Next.js involves fetching minimal necessary data, preventing client-server mismatches, lazy loading additional data, minimizing serialization overhead, and using React Server Components where possible for better performance.

22.

How can you persist state across page navigations in a Next.js app?

Answer

In Next.js, navigating between pages typically resets local state, but you can persist state using various strategies based on the use case.

1. Using URL Query Parameters

  • Store state in the URL so it persists across navigations.

Example:

 

import { useRouter } from "next/router";

function Filters({ filters }) {
const router = useRouter();

const updateFilter = (newFilter) => {
router.push({ pathname: "/products", query: { category: newFilter } }, undefined, { shallow: true });
};

return <button onClick={() => updateFilter("electronics")}>Set Filter</button>;
}

export default Filters;

 

  • Best For: Search filters, pagination, and user preferences.

2. Using React Context API

  • Store global state using Context, which persists across page navigations.

Example:

 

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

const CartContext = createContext();

export function CartProvider({ children }) {
const [cart, setCart] = useState([]);
return {children};
}

export function useCart() {
return useContext(CartContext);
}

 

  • Wrap _app.js with <CartProvider> to persist cart data.
  • Best For: Shopping carts, authentication state.

3. Using Local Storage or Session Storage

  • Save state in the browser to restore it after page reloads.

Example:

 

import { useState, useEffect } from "react";

function usePersistentState(key, initialValue) {
const [state, setState] = useState(() => {
try {
const storedValue = localStorage.getItem(key);
return storedValue ? JSON.parse(storedValue) : initialValue;
} catch {
return initialValue;
}
});

useEffect(() => {
try {
localStorage.setItem(key, JSON.stringify(state));
} catch {
// Handle storage write errors silently
}
}, [key, state]);

return [state, setState];
}

export default usePersistentState;

 

  • Best For: Remembering theme preferences, form drafts.

4. Using Redux or Zustand for Global State

  • State management libraries like Redux Toolkit or Zustand persist data across pages.
  • Best For: Large-scale applications with complex state.

Conclusion

Persisting state across navigations can be done using URL parameters, Context API, local storage, or state management libraries, depending on the data’s importance and longevity.

23.

What are the performance trade-offs between client-side and server-side state management in Next.js?

Answer

Next.js allows both client-side and server-side state management, each with trade-offs depending on performance, SEO, and scalability needs.

1. Client-Side State Management

  • Uses React’s useState, Context API, Redux, or Zustand.
  • Pros:
    • Faster interactions since state is managed in memory.
    • Reduced server load as data is handled on the client.
    • Works well for UI state, form inputs, and local user settings.
  • Cons:
    • Poor SEO as content is rendered on the client.
    • State resets on refresh unless persisted in local storage.
    • Can cause slower first render if heavy computations are needed.

2. Server-Side State Management

  • Uses getServerSideProps() – in pages router, API routes, or external databases.
  • Pros:
    • SEO-friendly as pages are pre-rendered with data.
    • Consistent data fetched per request, ensuring accuracy.
    • Offloads heavy state processing from the client.
  • Cons:
    • Higher latency due to server requests on every page load.
    • Increased server load with high-traffic applications.
    • More complex caching strategies are needed for performance.

Choosing the Right Approach

 

Feature Client-Side State Server-Side State
Performance Faster interactions Slower initial load
SEO Not SEO-friendly Fully SEO-friendly
Data Freshness Stale until refreshed Always up-to-date
Best For UI state, themes, local data Dynamic data, authentication

 

Conclusion

Use client-side state for fast UI interactions, and server-side state when data must be fresh, SEO-friendly, and shared across requests. A hybrid approach is best for performance optimization.

24.

How do you handle load balancing when deploying a Next.js application on AWS or DigitalOcean?

Answer

Load balancing ensures a Next.js application scales efficiently by distributing traffic across multiple servers, improving performance and reliability.

1. Using AWS Load Balancer with EC2 Instances

  • Deploy multiple EC2 instances running Next.js behind an Application Load Balancer (ALB).
  • ALB handles automatic traffic distribution and SSL termination.
  • Steps:
    • Deploy Next.js on multiple EC2 instances.
    • Attach instances to an Auto Scaling Group (ASG).
    • Configure ALB to route requests across instances.

2. Load Balancing in AWS with ECS (Containerized Deployment)

  • Use AWS Elastic Container Service (ECS) with Fargate for serverless container management.
  • Steps:
    • Build and push Next.js app as a Docker image.
    • Deploy to ECS using a Task Definition.
    • Use AWS ALB to distribute traffic to running containers.

3. Load Balancing on DigitalOcean with Droplets

  • Deploy multiple Droplets and place them behind a DigitalOcean Load Balancer.
  • Steps:
    • Deploy Next.js on multiple Droplets.
    • Add all instances to a DigitalOcean Load Balancer.
    • Enable health checks to remove unhealthy instances.

4. Using Serverless and Edge Deployments (No Load Balancer Needed)

  • Deploy on AWS Lambda (via Vercel or SST) or Cloudflare Workers to handle scaling without a load balancer.
  • Ideal for serverless and Edge-based applications.

5. Optimizing Database Connections

  • Use Amazon RDS or DigitalOcean Managed Databases with read replicas for scaling database queries.

Conclusion

For AWS, use ALB with EC2 or ECS (Fargate). For DigitalOcean, use Droplets with a Load Balancer. If possible, go serverless (Vercel, AWS Lambda, Cloudflare Workers) to avoid load balancing complexity altogether.

25.

What are the key differences between self-hosting Next.js and deploying it on Vercel?

Answer

Key Differences Between Self-Hosting Next.js and Deploying on Vercel

Next.js can be self-hosted on your own server or deployed to Vercel, the platform built specifically for Next.js applications. Each approach has trade-offs based on performance, scalability, and control.

1. Deployment and Hosting

  • Vercel: Fully managed deployment with automatic scaling, serverless functions, and Edge optimizations.
  • Self-Hosting: Requires setting up a server (AWS, DigitalOcean, or VPS) with manual configuration.

2. Performance and Scalability

  • Vercel: Optimized for serverless functions and Edge computing, ensuring low-latency responses.
  • Self-Hosting: Requires load balancing and auto-scaling (e.g., Kubernetes, Nginx, or AWS ALB).

3. Server-Side Features

  • Vercel: No persistent backend; API routes run as serverless functions with execution limits.
  • Self-Hosting: Full access to long-lived processes, WebSockets, and database connections.

4. Cost Considerations

  • Vercel: Free for small projects, but paid plans apply for high traffic and serverless function limits.
  • Self-Hosting: More predictable pricing, but requires managing infrastructure and scaling.

5. Flexibility and Customization

  • Vercel: Best for serverless and static sites, but limited control over backend infrastructure.
  • Self-Hosting: Allows custom caching, database optimizations, and fine-tuned performance tweaks.

Choosing the Right Option

 

Feature Vercel Self-Hosting
Ease of Deployment Simple (CI/CD built-in) Requires manual setup
Scalability Auto-scales via serverless Needs load balancing
Backend Flexibility Limited (serverless) Full control (long-lived processes)
Best For Startups, fast deployments, serverless apps Enterprise, custom backends, large-scale apps

 

Conclusion

Vercel is best for hassle-free, serverless deployments with automatic scaling, while self-hosting provides full control over infrastructure but requires more setup and maintenance.

26.

How can you use WebSockets in a Next.js API route for real-time applications?

Answer

Next.js API routes run as serverless functions by default, which do not support persistent WebSocket connections. To use WebSockets, you need to self-host Next.js or use a WebSocket server (e.g., Socket.io, WebSocket API).

1. Using WebSockets with a Custom Express Server

  • Next.js must run in custom server mode (not serverless).
  • In this setup, the Express server handles both Next.js page routing and WebSocket connections.

 

Example with Socket.io:

 

const { createServer } = require("http");
const { Server } = require("socket.io");
const next = require("next");

const app = next({ dev: process.env.NODE_ENV !== "production" });
const handle = app.getRequestHandler();

app.prepare().then(() => {
const server = createServer((req, res) => handle(req, res)); // Integrates Next.js page handling
const io = new Server(server);

io.on("connection", (socket) => {
console.log("User connected:", socket.id);

socket.on("message", (data) => {
io.emit("message", data);
});

socket.on("disconnect", () => {
console.log("User disconnected");
});
});

server.listen(3000, () => console.log("Server running on port 3000"));
});

 

  • In this setup, the Express server replaces the default Next.js API routes, so all routing and real-time logic is handled manually.
  • Best For: Self-hosted deployments (e.g., AWS, DigitalOcean) where fine-grained control is needed.

 

2. Using an External WebSocket Service (e.g., Pusher, Firebase, Supabase)

  • Works with serverless Next.js deployments (Vercel, Cloudflare).

Example with Pusher:

 

import Pusher from "pusher";

const pusher = new Pusher({
appId: process.env.PUSHER_APP_ID,
key: process.env.PUSHER_KEY,
secret: process.env.PUSHER_SECRET,
cluster: "us2",
useTLS: true,
});

export default async function handler(req, res) {
if (req.method === "POST") {
await pusher.trigger("chat", "message", { text: req.body.text });
res.status(200).json({ success: true });
} else {
res.status(405).json({ error: "Method Not Allowed" });
}
}

 

  • Best For: Serverless environments (Vercel, Firebase, AWS Lambda).

Conclusion

For self-hosted Next.js, use Socket.io with an Express server. For serverless deployments, use an external WebSocket provider like Pusher or Firebase.

27.

How do you monitor and debug performance bottlenecks in a Next.js production environment?

Answer

To ensure a fast and scalable Next.js application, monitoring and debugging performance bottlenecks is crucial. This involves tracking server response times, analyzing rendering performance, and optimizing API calls.

1. Use Vercel Analytics or Next.js Built-in Metrics

  • Vercel Analytics provides real-time performance monitoring.
  • Enable Next.js performance insights by logging render times:

 

export function reportWebVitals(metric) {
console.log(metric);
}

 

  • Best for: Tracking load times, hydration delays, and route changes.

2. Monitor API Response Times

  • Use APM tools like Datadog, New Relic, or OpenTelemetry to track slow API endpoints.
  • Log response times in API routes:

 

export default async function handler(req, res) {
const start = Date.now();
const data = await fetch("https://api.example.com/data").then((r) => r.json());
console.log(`API response time: ${Date.now() - start}ms`);
res.status(200).json(data);
}

 

  • Best for: Identifying slow API responses.

3. Enable Logging and Error Tracking

  • Use Sentry or LogRocket for error monitoring and performance tracking.
  • Install Sentry:

 

npm install @sentry/nextjs

 

  • Configure _app.js:

 

import * as Sentry from "@sentry/nextjs";

Sentry.init({ dsn: process.env.SENTRY_DSN });

 

4. Optimize Database Queries and Caching

  • Use query optimization, indexing, and caching to improve performance.
  • Add Cache-Control headers for API responses:

 

res.setHeader("Cache-Control", "s-maxage=600, stale-while-revalidate=30");

 

5. Analyze JavaScript Bundle Size

  • Use next build to analyze the bundle:

 

next build && next analyze

 

  • Optimize by removing unused dependencies and lazy-loading heavy components.

Conclusion

Monitoring Next.js performance requires Vercel Analytics, API logging, error tracking tools (Sentry), database optimization, and bundle size analysis to identify and fix bottlenecks efficiently.

Next.js Coding Interview Questions

1.

Implement a Next.js app with three pages: Home, About, and Contact using next/link(App Router).

Answer

1. Create a New Next.js App

 

npx create-next-app@latest nextjs-app-router-app
cd nextjs-app-router-app
npm install

 

When prompted, select “Use App Router”.

 

2. Create the Pages (Home, About, Contact)

Inside the app/ directory, create these folders and files:

 

/app
/about
page.js
/contact
page.js
page.js

 

app/page.js (Home Page)

 

import Link from "next/link";

export default function Home() {
return (
<div>
<h1>Home Page</h1>
<nav>
<Link href="/about">About</Link> | <Link href="/contact">Contact</Link>
</nav>
</div>
);
}

 

app/about/page.js (About Page)

 

import Link from "next/link";

export default function About() {
return (
<div>
<h1>About Page</h1>
<nav>
<Link href="/">Home</Link> | <Link href="/contact">Contact</Link>
</nav>
</div>
);
}

 

app/contact/page.js (Contact Page)

 

import Link from "next/link";

export default function Contact() {
return (
<div>
<h1>Contact Page</h1>
<nav>
<Link href="/">Home</Link> | <Link href="/about">About</Link>
</nav>
</div>
);
}

 

3. Start the Next.js App

 

npm run dev

 

Visit:

  • http://localhost:3000/ → Home Page
  • http://localhost:3000/about → About Page
  • http://localhost:3000/contact → Contact Page

 

Conclusion

This creates a basic Next.js App Router project with three pages and fast client-side navigation using next/link.

2.

Implement a dynamic route that displays blog posts based on id (/blog/[id]) (App Router).

Answer

1. Create the Project Structure

In the app/ directory:

 

/app
/blog
/[id]
page.js
page.js
layout.js
page.js

 

2. Implement the Dynamic Blog Post Page (app/blog/[id]/page.js)

 

import { notFound } from "next/navigation";

// Mocked data
const posts = [
{ id: "1", title: "Post 1", content: "This is the content of post 1." },
{ id: "2", title: "Post 2", content: "This is the content of post 2." },
{ id: "3", title: "Post 3", content: "This is the content of post 3." },
];

export async function generateStaticParams() {
return posts.map((post) => ({
id: post.id,
}));
}

export default async function BlogPost({ params }) {
const post = posts.find((p) => p.id === params.id);

if (!post) {
notFound(); // App Router-specific 404 handling
}

return (
<div>
<h1>{post.title}</h1>
<p>{post.content}</p>
</div>
);
}

 

3. Create a Blog Listing Page (app/blog/page.js)

 

import Link from "next/link";

export default function Blog() {
const posts = [{ id: "1", title: "Post 1" }, { id: "2", title: "Post 2" }, { id: "3", title: "Post 3" }];

return (
<div>
<h1>Blog</h1>
<ul>
{posts.map((post) => (
<li key={post.id}>
<Link href={`/blog/${post.id}`}>{post.title}</Link>
</li>
))}
</ul>
</div>
);
}

 

4. Run the Application

 

npm run dev

 

  • Visit http://localhost:3000/blog → See blog posts list.
  • Click on “Post 1” → Navigates to /blog/1, showing the post details.

 

Conclusion

This creates a dynamic blog route in Next.js App Router using generateStaticParams() for static generation at build time. Replace the mock data with a real API call if needed.

3.

Implement nested routing to display user profiles under /users/[id]/profile(App Router).

Answer

1. Create the Nested Route Structure

In the app/ directory:

 

/app
/users
/[id]
/profile
page.js
page.js
layout.js
page.js

 

2. Implement the User Profile Page (app/users/[id]/profile/page.js)

 

export default async function UserProfile({ params }) {
// Simulate fetching user data
const user = {
id: params.id,
name: `User ${params.id}`,
email: `user${params.id}@example.com`,
bio: "This is a sample user profile.",
};

return (
<div>
<h1>{user.name}'s Profile</h1>
<p>Email: {user.email}</p>
<p>Bio: {user.bio}</p>
</div>
);
}

 

3. Create the Users Listing Page (app/users/page.js)

 

import Link from "next/link";

export default function Users() {
const users = [
{ id: "1", name: "Alice" },
{ id: "2", name: "Bob" },
{ id: "3", name: "Charlie" },
];

return (
<div>
<h1>Users List</h1>
<ul>
{users.map((user) => (
<li key={user.id}>
<Link href={`/users/${user.id}/profile`}>{user.name}'s Profile</Link>
</li>
))}
</ul>
</div>
);
}

 

4. Run the Application

 

npm run dev

 

  • Visit http://localhost:3000/users → See a list of users.
  • Click on “Alice’s Profile” → Redirects to /users/1/profile, showing Alice’s details.

 

Conclusion

This setup implements nested dynamic routing in Next.js App Router using folders and Server Components, creating clean URLs like /users/[id]/profile for user profiles.

4.

Fetch and display a list of products from an API using (App Router).

Answer

1. Implement the Products Page (app/products/page.js)

 

export default async function ProductsPage() {
const response = await fetch("https://fakestoreapi.com/products", {
next: { revalidate: 60 }, // Enable ISR (Incremental Static Regeneration)
});
const products = await response.json();

return (
<div>
<h1>Products</h1>
<ul>
{products.map((product) => (
<li key={product.id}>
<h3>{product.title}</h3>
<p>Price: ${product.price}</p>
<img src={product.image} alt={product.title} width="100" />
</li>
))}
</ul>
</div>
);
}

 

  • Note: next: { revalidate: 60 } in the fetch request achieves ISR (revalidates every 60 seconds) in the App Router.

 

2. Run the Application

 

npm run dev

 

  • Visit http://localhost:3000/products to see the server-rendered product list.

 

3. How It Works in App Router

  • Data fetching happens inside Server Components (no getStaticProps() needed).
  • Automatic static optimization + ISR using revalidate in fetch options.

 

Conclusion

This implementation fetches product data inside a Server Component in the App Router and uses Incremental Static Regeneration for fast performance and SEO benefits.

5.

Fetch and display user details from an API using (App Router).

Answer

1. Create the User Page (app/user/page.js)

 

export default async function UserPage() {
const response = await fetch("https://jsonplaceholder.typicode.com/users/1", {
cache: "no-store", // Disable caching to fetch fresh data on every request
});
const user = await response.json();

return (
<div>
<h1>User Details</h1>
<p><strong>Name:</strong> {user.name}</p>
<p><strong>Email:</strong> {user.email}</p>
<p><strong>Phone:</strong> {user.phone}</p>
<p><strong>Company:</strong> {user.company.name}</p>
<p><strong>Website:</strong> {user.website}</p>
</div>
);
}

 

  • Important: cache: "no-store" ensures fresh data is fetched on every request (equivalent to SSR behavior).

 

2. Run the Application

 

npm run dev

 

  • Visit http://localhost:3000/user to see live user details fetched dynamically.

 

3. How It Works in App Router

  • No need for getServerSideProps().
  • Server Components fetch and render data at request time.
  • Fresh data without rebuilding or revalidating manually.

 

Conclusion

This setup uses Server Components and fetch with no-store caching to simulate Server-Side Rendering in the Next.js App Router, providing dynamic, always-fresh content.

6.

Implement Incremental Static Regeneration (ISR) in the App Router to refresh news articles every 60 seconds

Answer

1. Create the News Page (app/news/page.js)

 

export default async function NewsPage() {
const response = await fetch("https://jsonplaceholder.typicode.com/posts?_limit=5", {
next: { revalidate: 60 }, // Enable ISR: revalidate every 60 seconds
});
const newsArticles = await response.json();

return (
<div>
<h1>Latest News</h1>
<ul>
{newsArticles.map((article) => (
<li key={article.id}>
<h3>{article.title}</h3>
<p>{article.body}</p>
</li>
))}
</ul>
<p><em>Data refreshes every 60 seconds.</em></p>
</div>
);
}

 

2. Run the Application

 

npm run dev

 

  • Visit http://localhost:3000/news.
  • The page will regenerate with fresh data every 60 seconds in the background.

 

3. How ISR Works in the App Router

  • No getStaticProps() anymore.
  • fetch(..., { next: { revalidate: 60 } }) enables Incremental Static Regeneration.
  • Next.js serves cached HTML, and revalidates it transparently after 60 seconds when a new request comes in.

 

Conclusion

In the App Router, ISR is handled by adding revalidate inside the fetch call, making it simpler and more flexible than the old Pages Router approach.

7.

Fetch and display client-side data using useEffect() and fetch()(App Router Version).

Answer

1. Implement the Client-Side Data Fetching Page (app/client-data/page.jsx)

 

'use client'; // Mark as Client Component

import { useEffect, useState } from "react";

export default function ClientData() {
const [posts, setPosts] = useState([]);
const [loading, setLoading] = useState(true);

useEffect(() => {
async function fetchData() {
try {
const response = await fetch("https://jsonplaceholder.typicode.com/posts?_limit=5");
const data = await response.json();
setPosts(data);
} catch (error) {
console.error("Error fetching data:", error);
} finally {
setLoading(false);
}
}
fetchData();
}, []);

return (
<div>
<h1>Client-Side Data Fetching</h1>
{loading ? <p>Loading...</p> : (
<ul>
{posts.map((post) => (
<li key={post.id}>
<h3>{post.title}</h3>
<p>{post.body}</p>
</li>
))}
</ul>
)}
</div>
);
}

 

2. Run the Application

 

npm run dev

 

  • Visit http://localhost:3000/client-data → See the posts fetched dynamically on the client side.

 

3. Key Points for App Router

  • Add 'use client'; at the very top → Marks it as a Client Component.
  • Otherwise, the behavior (useEffect + fetch) stays the same.

 

Conclusion

In the App Router, client-side data fetching is still valid but needs 'use client' at the top of the file or component.

This ensures dynamic, real-time content without server-side prefetching, but it’s less SEO-friendly compared to server fetching.

8.

Create a Next.js API route (/api/hello) that returns a JSON message (App Router Version).

Answer

1. Create the API Route (app/api/hello/route.js)

Inside the app/api/hello/ directory, create a file named route.js:

 

import { NextResponse } from "next/server";

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

 

2. Run the Application

Start the Next.js development server:

 

npm run dev

 

  • Visit http://localhost:3000/api/hello → You should see:

 

{
"message": "Hello from Next.js API!"
}

 

3. How This Works in App Router

  • You define HTTP methods (GET, POST, etc.) as exported functions.
  • Use Response objects (Web API standard), not res.status().json() like before.
  • This aligns with modern serverless API standards.

 

4. Expanding the API Route (Optional)

You can also handle POST requests in the App Router:

 

import { NextResponse } from "next/server";

export async function POST(request) {
const body = await request.json();

return NextResponse.json(
{ message: `Received POST with data: ${body.name}` },
{ status: 201 }
);
}

 

  • Now your API supports both GET and POST!

 

Conclusion

In the App Router, API routes are created in app/api/ using exported HTTP methods like GET and POST.

They follow standard Web APIs and are easier to optimize for serverless environments and Edge functions.

9.

Implement an API route that handles user authentication using JWT (App Router Version)

Answer

1. Install Dependency

 

npm install jsonwebtoken

 

2. Create the Login API Route (app/api/auth/login/route.js)

 

import { NextResponse } from "next/server";
import jwt from "jsonwebtoken";

const SECRET_KEY = process.env.JWT_SECRET || "your-secret-key";

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

// Mock user data
const user = { id: 1, username: "admin", password: "password123" };

if (username === user.username && password === user.password) {
const token = jwt.sign({ userId: user.id, username: user.username }, SECRET_KEY, { expiresIn: "1h" });

return NextResponse.json({ token }, { status: 200 });
} else {
return NextResponse.json({ error: "Invalid credentials" }, { status: 401 });
}
}

 

3. Create the Auth Verification Route (app/api/auth/me/route.js)

 

 

import { NextResponse } from "next/server";
import jwt from "jsonwebtoken";

const SECRET_KEY = process.env.JWT_SECRET || "your-secret-key";

export async function GET(request) {
const authHeader = request.headers.get("authorization");
const token = authHeader?.split(" ")[1];

if (!token) {
return NextResponse.json({ error: "Token missing" }, { status: 401 });
}

try {
const decoded = jwt.verify(token, SECRET_KEY);
return NextResponse.json({ user: decoded }, { status: 200 });
} catch (error) {
return NextResponse.json({ error: "Invalid token" }, { status: 401 });
}
}

 

4. Testing the API

  • Login: POST /api/auth/login with JSON { "username": "admin", "password": "password123" }
  • Verify Auth: GET /api/auth/me with header Authorization: Bearer <token>

 

Conclusion

In the App Router, API routes live under app/api/ and you must use exported HTTP functions (POST, GET) instead of Express-style handlers.

10.

Build an API route that fetches data from a third-party API and caches it using Cache-Control(App Router Version).

Answer

1. Create the API Route (app/api/products/route.js)

 

import { NextResponse } from "next/server";

export async function GET() {
try {
const response = await fetch("https://fakestoreapi.com/products");
const products = await response.json();

return NextResponse.json(products, {
status: 200,
headers: {
"Cache-Control": "s-maxage=300, stale-while-revalidate=60",
},
});
} catch (error) {
return NextResponse.json(
{ error: "Failed to fetch data" },
{ status: 500 }
);
}
}

 

2. How Caching Works Here

  • s-maxage=300 → Caches the response at the CDN (e.g., Vercel Edge Network) for 5 minutes.
  • stale-while-revalidate=60 → Allows serving stale data for 1 minute while refreshing the cache in the background.

 

3. Testing

Visit:

 

http://localhost:3000/api/products

 

You should get product data and see cache headers in the response.

Check with:

 

curl -I http://localhost:3000/api/products

 

Result:

 

Cache-Control: s-maxage=300, stale-while-revalidate=60

 

Conclusion

Using the App Router approach with GET functions makes your API routes Edge-compatible, easier to cache, and more future-proof.

This example fetches third-party data and uses caching to optimize performance and reduce API server load.

11.

Create an API route that processes form submissions and stores them in a database (App Router Version).

Answer

1. Create the API Route (app/api/form/route.js)

 

import { NextResponse } from "next/server";
import connectDB from "@/lib/mongodb";
import Form from "@/lib/models/Form";

export async function POST(req) {
await connectDB();

try {
const { name, email, message } = await req.json();

if (!name || !email || !message) {
return NextResponse.json({ error: "All fields are required" }, { status: 400 });
}

const formEntry = new Form({ name, email, message });
await formEntry.save();

return NextResponse.json({ message: "Form submitted successfully" }, { status: 201 });
} catch (error) {
return NextResponse.json({ error: "Database error" }, { status: 500 });
}
}

 

2. Adjust the FormComponent

No changes are needed to the form itself. It will still send a POST request to /api/form.

Form submission code stays the same:

 

// app/api/form/route.js
import { NextResponse } from "next/server";

export async function POST(request) {
const form = await request.json();

// Your form processing logic here

return NextResponse.json({ message: "Form submitted successfully!" }, { status: 201 });
}

 

3. How It Works

  • The App Router API endpoint (app/api/form/route.js) listens for POST requests.
  • The request body is read using await req.json().
  • Form data is saved to MongoDB using Mongoose.
  • The server responds with either a success message or an error message.

 

Conclusion

By migrating the API route to the App Router format, you ensure the code is future-proof, edge-compatible, and aligned with Next.js best practices.

This example still handles form submission and database storage, but using the newer structure.

12.

Implement a global theme switcher using React Context API.

Answer

This task involves creating a global theme switcher using React Context API, allowing users to toggle between light and dark themes across all pages.

 

1. Create a Theme Context (context/ThemeContext.js)

Create a context/ folder and add a ThemeContext.js file.

Project Structure

 

/context
ThemeContext.js
/pages
_app.js
index.js
/components
ThemeToggle.js
/styles
globals.css

 

context/ThemeContext.js

 

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

const ThemeContext = createContext();

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

// Load theme from localStorage (if exists)
useEffect(() => {
const savedTheme = localStorage.getItem("theme") || "light";
setTheme(savedTheme);
}, []);

// Toggle theme and save to localStorage
const toggleTheme = () => {
const newTheme = theme === "light" ? "dark" : "light";
setTheme(newTheme);
localStorage.setItem("theme", newTheme);
};

return (
<ThemeContext.Provider value={{ theme, toggleTheme }}>
<div className={theme}>{children}</div>
</ThemeContext.Provider>
);
}

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

 

2. Wrap _app.js with the Theme Provider

Modify _app.js to wrap the entire app with ThemeProvider.

pages/_app.js

 

import { ThemeProvider } from "../context/ThemeContext";
import "../styles/globals.css";

export default function MyApp({ Component, pageProps }) {
return (
<ThemeProvider>
<Component {...pageProps} />
</ThemeProvider>
);
}

 

3. Create a Theme Toggle Button (components/ThemeToggle.js)

This button will switch between light and dark themes.

components/ThemeToggle.js

 

import { useTheme } from "../context/ThemeContext";

export default function ThemeToggle() {
const { theme, toggleTheme } = useTheme();

return (
<button onClick={toggleTheme} style={{ margin: "10px", padding: "5px 10px" }}>
Switch to {theme === "light" ? "Dark" : "Light"} Mode
</button>
);
}

 

4. Update the Home Page (pages/index.js)

Add the theme toggle button to the homepage.

pages/index.js

 

import ThemeToggle from "../components/ThemeToggle";

export default function Home() {
return (
<div>
<h1>Next.js Theme Switcher</h1>
<ThemeToggle />
<p>Click the button above to switch themes.</p>
</div>
);
}

 

5. Define Theme Styles in Global CSS (styles/globals.css)

Add styles for light and dark themes.

styles/globals.css

 

body {
transition: background 0.3s, color 0.3s;
}

.light {
background-color: white;
color: black;
}

.dark {
background-color: #1a1a1a;
color: white;
}

 

6. Run the Application

Start the Next.js server:

 

npm run dev

 

  • Visit http://localhost:3000/ → Click the theme switch button to toggle between light and dark modes.

 

Conclusion

This implementation uses React Context API to manage the global theme state, persists user preference in localStorage, and applies light/dark mode styles dynamically.

13.

Use Redux Toolkit to manage a shopping cart state across multiple pages.

Answer

1. Move Pages to app/

Project structure:

 

/app
/products
page.js
/cart
page.js
layout.js
/components
Navbar.js
/store.js

 

2. Create the Redux Store (store.js)

 

// store.js
import { configureStore, createSlice } from "@reduxjs/toolkit";

// Cart slice
const cartSlice = createSlice({
name: "cart",
initialState: {
items: [],
},
reducers: {
addItem: (state, action) => {
const existing = state.items.find((item) => item.id === action.payload.id);
if (existing) {
existing.quantity += 1;
} else {
state.items.push({ ...action.payload, quantity: 1 });
}
},
removeItem: (state, action) => {
state.items = state.items.filter((item) => item.id !== action.payload);
},
clearCart: (state) => {
state.items = [];
},
},
});

export const { addItem, removeItem, clearCart } = cartSlice.actions;

export const store = configureStore({
reducer: {
cart: cartSlice.reducer,
},
});

 

3. Wrap Redux Store in app/layout.js

 

// app/layout.js
import { Provider } from "react-redux";
import { store } from "../store";

export const metadata = {
title: "Shopping App",
description: "Simple shopping cart with Redux Toolkit",
};

export default function RootLayout({ children }) {
return (
<html lang="en">
<body>
<Provider store={store}>{children}</Provider>
</body>
</html>
);
}

 

4. Update Pages to app/products/page.js and app/cart/page.js

app/products/page.js

 

"use client";

import Navbar from "@/components/Navbar";
import { useDispatch } from "react-redux";
import { addItem } from "@/store";

const products = [
{ id: 1, name: "Laptop", price: 1200 },
{ id: 2, name: "Phone", price: 800 },
{ id: 3, name: "Headphones", price: 150 },
];

export default function ProductsPage() {
const dispatch = useDispatch();

return (
<div>
<Navbar />
<h1>Products</h1>
<ul>
{products.map((product) => (
<li key={product.id}>
<h3>{product.name} - ${product.price}</h3>
<button onClick={() => dispatch(addItem(product))}>Add to Cart</button>
</li>
))}
</ul>
</div>
);
}

 

app/cart/page.js

 

"use client";

import Navbar from "@/components/Navbar";
import { useSelector, useDispatch } from "react-redux";
import { removeItem, clearCart } from "@/store";

export default function CartPage() {
const cart = useSelector((state) => state.cart.items);
const dispatch = useDispatch();

return (
<div>
<Navbar />
<h1>Shopping Cart</h1>
{cart.length === 0 ? (
<p>Your cart is empty.</p>
) : (
<ul>
{cart.map((item) => (
<li key={item.id}>
<h3>{item.name} - ${item.price} x {item.quantity}</h3>
<button onClick={() => dispatch(removeItem(item.id))}>Remove</button>
</li>
))}
</ul>
)}
{cart.length > 0 && <button onClick={() => dispatch(clearCart())}>Clear Cart</button>}
</div>
);
}

 

5. Use the Same Navbar.js

No changes needed, just use App Router paths for imports (e.g., @/components/Navbar).

 

6. Run the App

 

npm run dev

 

Visit:

  • http://localhost:3000/products
  • http://localhost:3000/cart
14.

Implement Zustand for managing UI state (e.g., modal visibility).

Answer

This task involves using Zustand to manage global UI state, specifically toggling a modal dialog across multiple pages.

 

1. Move Pages to app/

Structure:

 

/app
/about
page.js
page.js
layout.js
/components
Modal.js
ModalToggle.js
/store
modalStore.js

 

2. Wrap Zustand in app/layout.js

You don’t need extra wrappers for Zustand (it works outside Context), but layout ensures consistency:

 

import "./globals.css"; // Optional styling
import Modal from "@/components/Modal";

export const metadata = {
title: "Zustand Modal Example",
description: "Next.js App Router + Zustand Modal Example",
};

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

 

  • Modal is placed outside the page but inside the layout, so it is available globally across pages.

 

3. Pages Use Only the Toggle

app/page.js

 

import ModalToggle from "@/components/ModalToggle";

export default function HomePage() {
return (
<div>
<h1>Home Page</h1>
<ModalToggle />
</div>
);
}

 

app/about/page.js

 

import ModalToggle from "@/components/ModalToggle";

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

 

4. No change needed for store/modalStore.js and components/Modal.js, components/ModalToggle.js.

Create Zustand Store and Components

✅ These files are used to control modal visibility globally across pages. Let’s include their implementations:

store/modalStore.js

 

// store/modalStore.js
import { create } from "zustand";

const useModalStore = create((set) => ({
isOpen: false,
openModal: () => set({ isOpen: true }),
closeModal: () => set({ isOpen: false }),
}));

export default useModalStore;

 

components/Modal.js

// components/Modal.js
"use client";

import useModalStore from "../store/modalStore";

export default function Modal() {
const { isOpen, closeModal } = useModalStore();

if (!isOpen) return null;

return (
<div style={styles.overlay}>
<div style={styles.modal}>
<h2>Modal Title</h2>
<p>This is a modal dialog.</p>
<button onClick={closeModal}>Close</button>
</div>
</div>
);
}

const styles = {
overlay: {
position: "fixed",
top: 0,
left: 0,
width: "100%",
height: "100%",
backgroundColor: "rgba(0,0,0,0.5)",
display: "flex",
justifyContent: "center",
alignItems: "center",
},
modal: {
background: "white",
padding: "20px",
borderRadius: "8px",
textAlign: "center",
},
};

components/ModalToggle.js

 

// components/ModalToggle.js
"use client";

import useModalStore from "../store/modalStore";

export default function ModalToggle() {
const { openModal } = useModalStore();

return <button onClick={openModal}>Open Modal</button>;
}

✅ Zustand is App Router–safe natively.

 

5. Run the App

 

npm run dev

 

  • Visit /
  • Visit /about
  • Open the modal anywhere → Zustand keeps the state global.
15.

Secure a Next.js page by implementing middleware-based authentication.

Answer

1. Project Structure

 

/app
/login
page.js
/dashboard
page.js
/middleware.js

 

2. middleware.js

Middleware code stays almost the same — just change matcher if you want:

 

import { NextResponse } from "next/server";
import jwt from "jsonwebtoken";

const SECRET_KEY = process.env.JWT_SECRET || "your-secret-key";

export function middleware(req) {
const token = req.cookies.get("authToken");

if (!token) {
return NextResponse.redirect(new URL("/login", req.url));
}

try {
jwt.verify(token, SECRET_KEY);
return NextResponse.next();
} catch (error) {
return NextResponse.redirect(new URL("/login", req.url));
}
}

export const config = {
matcher: ["/dashboard"], // Protect the /dashboard route
};

 

3. app/login/page.js

 

// app/api/auth/route.js
import { NextResponse } from "next/server";

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

if (username === "admin" && password === "password") {
return NextResponse.json({ token: "secure-token" }, { status: 200 });
}

return NextResponse.json({ message: "Invalid credentials" }, { status: 401 });
}

// the component
"use client";

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

export default function LoginPage() {
const [form, setForm] = useState({ username: "", password: "" });
const [error, setError] = useState(null);
const router = useRouter();

const handleChange = (e) => {
setForm({ ...form, [e.target.name]: e.target.value });
};

const handleSubmit = async (e) => {
e.preventDefault();
setError(null);

const response = await fetch("/api/auth", {
method: "POST",
headers: { "Content-Type": "application/json" },
body: JSON.stringify(form),
});

if (response.ok) {
router.push("/dashboard");
} else {
setError("Invalid credentials");
}
};

return (
<div>
<h1>Login</h1>
{error && <p style={{ color: "red" }}>{error}</p>}
<form onSubmit={handleSubmit}>
<input type="text" name="username" placeholder="Username" onChange={handleChange} required />
<input type="password" name="password" placeholder="Password" onChange={handleChange} required />
<button type="submit">Login</button>
</form>
</div>
);
}

 

4. app/dashboard/page.js

 

export default function DashboardPage() {
return (
<div>
<h1>Welcome to Dashboard</h1>
<p>Only authenticated users can access this page.</p>
</div>
);
}

 

5. pages/api/auth.js

✅ API routes still live under pages/api/ even when using the App Router. No change needed.

 

Summary for this task:

  • Middleware works the same in both Pages Router and App Router.
  • Pages like /login and /dashboard should move under app/ to match Next.js 13+ best practices.
16.

Implement Google OAuth authentication using NextAuth.js.

Answer

This task involves setting up Google OAuth authentication in Next.js using NextAuth.js, allowing users to log in with their Google accounts.

 

1. Install NextAuth.js

Run the following command to install NextAuth:

 

npm install next-auth

 

2. Implement a NextAuth API Route (pages/api/auth/[...nextauth].js)

NextAuth handles authentication through an API route at /api/auth/.

pages/api/auth/[...nextauth].js

 

import NextAuth from "next-auth";
import GoogleProvider from "next-auth/providers/google";

export default NextAuth({
providers: [
GoogleProvider({
clientId: process.env.GOOGLE_CLIENT_ID,
clientSecret: process.env.GOOGLE_CLIENT_SECRET,
}),
],
secret: process.env.NEXTAUTH_SECRET,
callbacks: {
async session({ session, token }) {
session.user.id = token.sub;
return session;
},
},
});

 

How It Works:

  • Configures Google as an authentication provider.
  • Uses environment variables for Google Client ID and Secret.
  • Stores user session data.

3. Get Google OAuth Credentials

  1. Go to Google Cloud Console.
  2. Create a new project.
  3. Navigate to APIs & ServicesCredentials.
  4. Click Create CredentialsOAuth Client ID.
  5. Under Application type, choose Web application.
  6. Add the following Authorized Redirect URIs:

 

http://localhost:3000/api/auth/callback/google

 

  1. Copy the Client ID and Client Secret.

 

4. Set Up Environment Variables (.env.local)

Create a .env.local file in the project root and add:

 

GOOGLE_CLIENT_ID=your-google-client-id
GOOGLE_CLIENT_SECRET=your-google-client-secret
NEXTAUTH_SECRET=your-random-secret-key
NEXTAUTH_URL=http://localhost:3000

 

Restart the server after adding environment variables.

 

5. Implement a Login Page (pages/login.js)

This page allows users to log in and log out.

pages/login.js

 

import { signIn, signOut, useSession } from "next-auth/react";

export default function Login() {
const { data: session } = useSession();

return (
<div>
{session ? (
<>
<h1>Welcome, {session.user.name}!</h1>
<img src={session.user.image} alt="User Avatar" width="50" />
<p>Email: {session.user.email}</p>
<button onClick={() => signOut()}>Sign Out</button>
</>
) : (
<>
<h1>Please Log In</h1>
<button onClick={() => signIn("google")}>Sign in with Google</button>
</>
)}
</div>
);
}

 

How It Works:

  • If the user is logged in, it displays their name, email, and profile picture.
  • If the user is not logged in, it shows a Sign in with Google button.

 

6. Protect a Page Using Authentication (pages/protected.js)

This page is only accessible if the user is authenticated.

pages/protected.js

 

import { useSession } from "next-auth/react";
import { useRouter } from "next/router";
import { useEffect } from "react";

export default function ProtectedPage() {
const { data: session, status } = useSession();
const router = useRouter();

useEffect(() => {
if (status === "unauthenticated") {
router.push("/login");
}
}, [status, router]);

if (status === "loading") {
return <p>Loading...</p>;
}

return (
<div>
<h1>Protected Page</h1>
<p>Welcome, {session?.user?.name}! You have access to this page.</p>
</div>
);
}

 

How It Works:

  • Redirects unauthenticated users to the login page.
  • Displays content only if the user is logged in.

 

7. Wrap the App with NextAuth Provider (pages/_app.js)

Modify _app.js to wrap the entire app with the NextAuth provider.

pages/_app.js

 

import { SessionProvider } from "next-auth/react";

export default function MyApp({ Component, pageProps }) {
return (
<SessionProvider session={pageProps.session}>
<Component {...pageProps} />
</SessionProvider>
);
}

 

Why?

  • Ensures that authentication state persists across pages.

 

8. Run the Application

Start the Next.js server:

 

npm run dev

 

  • Visit http://localhost:3000/login → Click Sign in with Google.
  • Log in with your Google account.
  • Visit http://localhost:3000/protected → You should be authenticated.

 

Conclusion

This implementation integrates Google OAuth authentication in Next.js using NextAuth.js, enabling secure user login and protected routes with minimal setup.

17.

Optimize images using next/image and enable lazy loading.

Answer

This task involves optimizing images in Next.js using the next/image component, which provides automatic lazy loading, resizing, and format conversion (WebP, AVIF) for improved performance.

 

1. Use next/image in a Component (components/OptimizedImage.js)

Create a reusable optimized image component.

components/OptimizedImage.js

 

import Image from "next/image";

export default function OptimizedImage() {
return (
<div>
<h2>Optimized Image Example</h2>
<Image
src="https://via.placeholder.com/800x500"
alt="Optimized Example"
width={800}
height={500}
priority={false}
/>
</div>
);
}

 

Why Use next/image Instead of <img>?

  • Lazy loading by default (loads images only when visible).
  • Automatic format conversion (e.g., WebP, AVIF).
  • Optimized sizes for different screen resolutions.

 

2. Use the Optimized Image Component in a Page (pages/index.js)

Modify the home page to include the optimized image.

pages/index.js

 

import OptimizedImage from "../components/OptimizedImage";

export default function Home() {
return (
<div>
<h1>Next.js Image Optimization</h1>
<OptimizedImage />
</div>
);
}

 

3. Enable Remote Image Domains (If Using External Images)

If your images are hosted on a remote domain, Next.js blocks them by default.

To allow external images, update next.config.js:

next.config.js

 

module.exports = {
images: {
domains: ["via.placeholder.com"], // Add allowed image domains
},
};

 

Restart the server for changes to take effect:

 

npm run dev

 

4. Enable Image Lazy Loading (Default in next/image)

Lazy loading is enabled by default in Next.js. However, you can control it using the priority prop:

 

<Image
src="https://via.placeholder.com/800x500"
alt="Example"
width={800}
height={500}
priority={false} // Enable lazy loading
/>

 

priority={true} loads the image immediately (e.g., for above-the-fold images).

 

5. Use Different Layout Options for Better Performance

Next.js provides different layout modes for responsive images:

 

<Image
src="https://via.placeholder.com/800x500"
alt="Responsive Example"
layout="intrinsic" // Keeps aspect ratio
width={800}
height={500}
/>

 

 

Layout Mode Behavior
intrinsic Keeps original aspect ratio
responsive Adapts width based on screen size
fill Fills parent container (useful for backgrounds)

 

6. Run the Application

Start the Next.js development server:

npm run dev

 

  • Visit http://localhost:3000/ → The image loads efficiently with lazy loading enabled.

 

Conclusion

This implementation optimizes images using next/image, enabling lazy loading, responsive sizes, and automatic format conversion, ensuring better performance and faster page loads.

18.

Implement route prefetching for faster navigation between pages.

Answer

This task involves using Next.js route prefetching to improve navigation speed by preloading pages in the background before the user clicks a link.

 

1. Use next/link for Automatic Prefetching

Next.js automatically prefetches pages linked with next/link when they appear in the viewport.

components/Navbar.js

 

import Link from "next/link";

export default function Navbar() {
return (
<nav>
<ul>
<li><Link href="/" prefetch={true}>Home</Link></li>
<li><Link href="/about" prefetch={true}>About</Link></li>
<li><Link href="/contact" prefetch={true}>Contact</Link></li>
</ul>
</nav>
);
}

 

How It Works:

  • When a Link is in the viewport, Next.js preloads the target page.
  • Prefetching happens only in production mode.

 

2. Disable Prefetching for Less Important Pages

If prefetching isn’t needed for certain links, disable it using prefetch={false}.

 

<Link href="/privacy-policy" prefetch={false}>Privacy Policy</Link>

 

Why?

  • Prevents unnecessary bandwidth usage for rarely visited pages.

 

3. Manually Prefetch Routes Using next/router

You can manually prefetch routes when a user hovers over a button.

components/PrefetchButton.js

 

import { useRouter } from "next/router";

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

return (
<button
onMouseEnter={() => router.prefetch("/services")}
onClick={() => router.push("/services")}
>
Go to Services (Prefetched)
</button>
);
}

 

How It Works:

  • Prefetches /services when the user hovers over the button.
  • Navigating is instant since the page is already loaded.

 

4. Test Route Prefetching

Start the Next.js development server:

 

npm run dev

 

  • Open DevToolsNetwork TabDisable Cache.
  • Hover over a prefetched link.
  • Observe that Next.js preloads the page before clicking.

 

5. Optimized Prefetching Strategy

 

Scenario Prefetch Strategy
Common pages (Home, About) Keep prefetch={true} (default)
Rarely visited pages (Privacy Policy) Disable prefetching
Pages accessed via user action (e.g., dropdown) 🎯 Manually prefetch on hover

 

 

Conclusion

This implementation optimizes Next.js route prefetching, ensuring instant navigation for common pages while preventing unnecessary data fetching.

19.

Reduce JavaScript bundle size by dynamically importing a large component using next/dynamic().

Answer

This task involves using Next.js dynamic imports (next/dynamic) to lazy load large components, reducing initial JavaScript bundle size and improving performance.

 

1. Install next/dynamic (Included in Next.js by Default)

No installation required—next/dynamic is built into Next.js.

 

2. Create a Large Component (components/HeavyComponent.js)

This represents a large component that should only load when needed.

components/HeavyComponent.js

 

export default function HeavyComponent() {
return (
<div>
<h2>Heavy Component Loaded</h2>
<p>This component is dynamically imported to reduce the initial JavaScript bundle size.</p>
</div>
);
}

 

Why Lazy Load?

  • Avoids loading unnecessary JavaScript upfront.
  • Improves page load times and First Contentful Paint (FCP).

 

3. Use Dynamic Import for Lazy Loading in a Page (pages/index.js)

Modify the home page to dynamically import HeavyComponent.

pages/index.js

 

import dynamic from "next/dynamic";
import { useState } from "react";

// Dynamically import the component (not loaded initially)
const HeavyComponent = dynamic(() => import("../components/HeavyComponent"), {
loading: () => <p>Loading...</p>, // Optional: Show a fallback while loading
ssr: false, // Ensure this component is only loaded on the client
});

export default function Home() {
const [showComponent, setShowComponent] = useState(false);

return (
<div>
<h1>Next.js Dynamic Import Example</h1>
<button onClick={() => setShowComponent(true)}>Load Heavy Component</button>
{showComponent && <HeavyComponent />}
</div>
);
}

 

How This Works:

  • dynamic(() => import("...")) → Loads the component only when needed.
  • ssr: false → Ensures the component is not preloaded during SSR.
  • loading: () => <p>Loading...</p> → Displays a placeholder while loading.

 

4. Run the Application and Analyze Bundle Size

Start the Next.js server:

 

npm run dev

 

  • Visit http://localhost:3000/.
  • Open DevTools → Network Tab → JavaScript.
  • Click “Load Heavy Component” and observe that the component loads dynamically only when clicked.

 

5. Verify Bundle Size Reduction

Run the Next.js build analysis to check bundle optimization:

 

npm run build && next analyze

 

  • Observe that HeavyComponent.js is not included in the initial bundle.

 

Conclusion

This implementation reduces the JavaScript bundle size by lazy loading large components using next/dynamic(), ensuring better page load performance while keeping essential scripts lightweight.

20.

Implement geo-based content delivery using Next.js Middleware (e.g., detect user location and serve region-specific content).

Answer

This task involves using Next.js Middleware to detect the user’s location based on request headers and serve region-specific content.

 

1. Create the Middleware File (middleware.js)

Next.js Middleware runs before the request reaches the page, allowing us to detect the user’s location and modify the response.

Project Structure

 

/middleware.js
/pages
index.js
us.js
fr.js
de.js

 

middleware.js

 

import { NextResponse } from "next/server";

export function middleware(req) {
const country = req.geo?.country || "US"; // Default to US if no data
const url = req.nextUrl.clone();

if (country === "FR") {
url.pathname = "/fr"; // Redirect French users
} else if (country === "DE") {
url.pathname = "/de"; // Redirect German users
} else {
url.pathname = "/us"; // Default to US page
}

return NextResponse.rewrite(url); // Rewrite without changing the visible URL
}

// Apply middleware to all requests
export const config = {
matcher: ["/"],
};

 

How It Works:

  • Detects user location from req.geo.country (available on Vercel or Cloudflare).
  • Redirects users based on their country.
  • Uses NextResponse.rewrite(), so the URL stays the same, but the page content changes.

 

2. Create Region-Specific Pages

Each page serves localized content based on the detected region.

pages/us.js (For US Users)

 

export default function USPage() {
return (
<div>
<h1>Welcome, US User!</h1>
<p>This content is tailored for users in the United States.</p>
</div>
);
}

 

pages/fr.js (For French Users)

 

export default function FRPage() {
return (
<div>
<h1>Bienvenue, utilisateur français !</h1>
<p>Ce contenu est destiné aux utilisateurs en France.</p>
</div>
);
}

 

pages/de.js (For German Users)

 

export default function DEPage() {
return (
<div>
<h1>Willkommen, deutscher Benutzer!</h1>
<p>Dieser Inhalt ist für Benutzer in Deutschland bestimmt.</p>
</div>
);
}

 

Why This Works:

  • Users visiting / are automatically rewritten to their region-specific page.
  • The URL remains /, but different content is displayed.

 

3. Deploy to Vercel (Required for Geo-Detection)

Since req.geo only works on Edge Networks like Vercel, deploy the app:

 

vercel deploy

 

Vercel Auto-Detects User Location

  • In Vercel logs, you’ll see req.geo.country returning real user locations.

 

4. Run the Application Locally (Without Geo-Detection)

Since req.geo is not available locally, manually test it by visiting:

  • http://localhost:3000/us → US content
  • http://localhost:3000/fr → French content
  • http://localhost:3000/de → German content

 

Conclusion

This implementation detects user location using Next.js Middleware and serves region-specific content dynamically based on their country.

Perfect for localization, region-based promotions, or GDPR compliance.

21.

Use next/head to set dynamic meta tags for a blog post page.

Answer

This task involves using next/head to dynamically set SEO-friendly meta tags for a blog post page, improving search engine visibility and social media sharing.

 

1. Use next/head in a Dynamic Blog Page (pages/blog/[id].js)

This page dynamically fetches blog posts and sets unique meta tags based on the post content.

Project Structure

 

/pages
/blog
[id].js
index.js
/components
SEO.js

 

pages/blog/[id].js

 

import Head from "next/head";
import { useRouter } from "next/router";

// Simulated blog data (replace with a real API call)
const blogPosts = [
{ id: "1", title: "Next.js SEO Guide", description: "Learn how to optimize Next.js for SEO.", author: "John Doe" },
{ id: "2", title: "Server-Side Rendering vs. Static Generation", description: "Understand SSR and SSG differences.", author: "Jane Smith" },
];

export async function getStaticPaths() {
const paths = blogPosts.map((post) => ({ params: { id: post.id } }));
return { paths, fallback: false };
}

export async function getStaticProps({ params }) {
const post = blogPosts.find((p) => p.id === params.id);
return { props: { post } };
}

export default function BlogPost({ post }) {
const router = useRouter();
const url = `https://myblog.com${router.asPath}`;

return (
<div>
<Head>
<title>{post.title} | My Blog</title>
<meta name="description" content={post.description} />
<meta name="author" content={post.author} />
<meta property="og:title" content={post.title} />
<meta property="og:description" content={post.description} />
<meta property="og:url" content={url} />
<meta property="og:type" content="article" />
<meta name="twitter:card" content="summary_large_image" />
</Head>
<h1>{post.title}</h1>
<p>{post.description}</p>
<p><strong>Author:</strong> {post.author}</p>
</div>
);
}

 

2. Create a Blog Listing Page (pages/blog/index.js)

 

import Link from "next/link";

const blogPosts = [
{ id: "1", title: "Next.js SEO Guide" },
{ id: "2", title: "Server-Side Rendering vs. Static Generation" },
];

export default function Blog() {
return (
<div>
<h1>Blog Posts</h1>
<ul>
{blogPosts.map((post) => (
<li key={post.id}>
<Link href={`/blog/${post.id}`}>{post.title}</Link>
</li>
))}
</ul>
</div>
);
}

 

Users can click a blog post to navigate to /blog/[id].

 

3. Run the Application

Start the Next.js development server:

 

npm run dev

 

  • Visit http://localhost:3000/blog → See the list of blog posts.
  • Click a post → Dynamic meta tags update for each post.

 

4. Verify Meta Tags (SEO & Social Media Preview)

  • Open the Page Source (Ctrl + U) → Check <title> and <meta> tags.
  • Use Facebook Sharing Debugger or Twitter Card Validator to test social previews.

Conclusion

This implementation uses next/head to dynamically set SEO-friendly meta tags for blog posts, improving search rankings and social media previews.

 

Important Note:

This example uses the Pages Router (pages/ directory) and next/head.

If you are using the App Router (app/ directory), you should use the generateMetadata() function inside app/blog/[id]/page.js instead of next/head to set dynamic meta tags.

Example with App Router:

 

// app/blog/[id]/page.js
export async function generateMetadata({ params }) {
const post = await fetchPost(params.id);
return {
title: `${post.title} | My Blog`,
description: post.description,
openGraph: {
title: post.title,
description: post.description,
url: `https://myblog.com/blog/${params.id}`,
type: "article",
},
twitter: {
card: "summary_large_image",
},
};
}

export default function BlogPost({ params }) {
// Render the blog post content here
}
22.

Implement Open Graph meta tags to enhance social media previews.

Answer

This task involves using Open Graph (OG) meta tags to improve social media previews when sharing a Next.js page on platforms like Facebook, Twitter, and LinkedIn.

 

1. Create an SEO Component (components/SEO.js)

Create a reusable component to dynamically set Open Graph meta tags.

Project Structure

 

/components
SEO.js
/pages
index.js
blog.js

 

components/SEO.js

 

import Head from "next/head";
import { useRouter } from "next/router";

export default function SEO({ title, description, image }) {
const router = useRouter();
const siteUrl = "https://mywebsite.com"; // Replace with your domain
const pageUrl = `${siteUrl}${router.asPath}`;
const ogImage = image || `${siteUrl}/default-image.jpg`; // Fallback image if none provided

return (
<Head>
{/* Standard Meta Tags */}
<title>{title} | My Website</title>
<meta name="description" content={description} />

{/* Open Graph Meta Tags for Facebook, LinkedIn */}
<meta property="og:title" content={title} />
<meta property="og:description" content={description} />
<meta property="og:image" content={ogImage} />
<meta property="og:url" content={pageUrl} />
<meta property="og:type" content="website" />

{/* Twitter Card Meta Tags */}
<meta name="twitter:card" content="summary_large_image" />
<meta name="twitter:title" content={title} />
<meta name="twitter:description" content={description} />
<meta name="twitter:image" content={ogImage} />
</Head>
);
}

 

2. Use the SEO Component on Pages (pages/index.js)

Add the SEO component to a page to dynamically inject meta tags.

pages/index.js

 

import SEO from "../components/SEO";

export default function Home() {
return (
<div>
<SEO
title="Next.js Open Graph Example"
description="Learn how to implement Open Graph meta tags in Next.js for better social media previews."
image="https://via.placeholder.com/1200x630" // Replace with your real OG image
/>
<h1>Welcome to My Website</h1>
<p>This page is optimized for social media sharing.</p>
</div>
);
}

 

What Happens?

  • The Open Graph tags (og:title, og:description, og:image) and Twitter tags are set dynamically.
  • When users share your page, social media platforms generate the correct rich preview.

3. Test Your Open Graph Tags

  • View Page Source (Ctrl + U) → Look for <meta> tags.
  • Facebook Debugger: Facebook Sharing Debugger
  • Twitter Card Validator: Twitter Card Validator

You may need to “Scrape Again” in Facebook Debugger after updates.

 

Conclusion

This implementation uses Open Graph meta tags to enhance social media previews in Next.js, helping to boost engagement, shares, and visibility when your links are shared.

 

Important Note:

This example uses the Pages Router (pages/ directory) with next/head.

If you are using the App Router (app/ directory), you should use the generateMetadata() function instead to set Open Graph and Twitter meta tags dynamically.

Example with App Router:

 

// app/page.js or app/blog/page.js
export const metadata = {
title: "Next.js Open Graph Example | My Website",
description: "Learn how to implement Open Graph meta tags in Next.js for better social media previews.",
openGraph: {
title: "Next.js Open Graph Example",
description: "Learn how to implement Open Graph meta tags in Next.js for better social media previews.",
images: ["https://via.placeholder.com/1200x630"],
url: "https://mywebsite.com",
type: "website",
},
twitter: {
card: "summary_large_image",
title: "Next.js Open Graph Example",
description: "Learn how to implement Open Graph meta tags in Next.js for better social media previews.",
images: ["https://via.placeholder.com/1200x630"],
},
};
23.

Connect a Next.js app to MongoDB using Mongoose and display user data.

Answer

This task involves:

  • Connecting Next.js to MongoDB using Mongoose.
  • Fetching user data from MongoDB via an API route.
  • Displaying users on a Next.js page.

 

1. Install Mongoose

 

npm install mongoose dotenv

 

2. Set Up MongoDB Connection (lib/mongodb.js)

Create a lib/ folder and add the MongoDB connection utility.

Project Structure

 

/lib
mongodb.js
models
User.js
/pages/api
users.js
/pages
users.js

 

lib/mongodb.js

 

import mongoose from "mongoose";

const MONGODB_URI = process.env.MONGODB_URI;

if (!MONGODB_URI) {
throw new Error("Please define the MONGODB_URI environment variable");
}

let cached = global.mongoose;

if (!cached) {
cached = global.mongoose = { conn: null, promise: null };
}

async function connectDB() {
if (cached.conn) return cached.conn;

if (!cached.promise) {
cached.promise = mongoose.connect(MONGODB_URI, {
useNewUrlParser: true,
useUnifiedTopology: true,
});
}

cached.conn = await cached.promise;
return cached.conn;
}

export default connectDB;

 

Why?

  • Avoids multiple database connections during hot reload in development.
  • Caches the database connection.

 

3. Create a Mongoose User Model (lib/models/User.js)

lib/models/User.js

 

import mongoose from "mongoose";

const UserSchema = new mongoose.Schema({
name: { type: String, required: true },
email: { type: String, required: true, unique: true },
});

export default mongoose.models.User || mongoose.model("User", UserSchema);

 

What This Does:

  • Defines a User with name and email.
  • Ensures email uniqueness.

 

4. Create an API Route to Fetch Users (pages/api/users.js)

pages/api/users.js

 

import connectDB from "../../lib/mongodb";
import User from "../../lib/models/User";

export default async function handler(req, res) {
await connectDB();

if (req.method === "GET") {
try {
const users = await User.find({});
return res.status(200).json(users);
} catch (error) {
return res.status(500).json({ error: "Failed to fetch users" });
}
} else {
return res.status(405).json({ error: "Method Not Allowed" });
}
}

 

What Happens:

  • Connects to MongoDB.
  • Fetches all users and returns them as JSON.

 

5. Create a Next.js Page to Display Users (pages/users.js)

pages/users.js

 

import { useState, useEffect } from "react";

export default function UsersPage() {
const [users, setUsers] = useState([]);

useEffect(() => {
async function fetchUsers() {
const response = await fetch("/api/users");
const data = await response.json();
setUsers(data);
}
fetchUsers();
}, []);

return (
<div>
<h1>Users List</h1>
{users.length === 0 ? (
<p>No users found</p>
) : (
<ul>
{users.map((user) => (
<li key={user._id}>
<strong>{user.name}</strong> - {user.email}
</li>
))}
</ul>
)}
</div>
);
}

 

What Happens:

  • Client-side fetch from /api/users.
  • Displays the list of users.

 

6. Set Up Environment Variables (.env.local)

Create a .env.local file:

 

MONGODB_URI=mongodb+srv://your-username:your-password@cluster.mongodb.net/your-database

 

Then restart your development server:

 

npm run dev

 

7. (Optional) Add Test Users to MongoDB

You can add test users with a script.

scripts/addUsers.js

 

import connectDB from "../lib/mongodb";
import User from "../lib/models/User";

async function addUsers() {
await connectDB();
await User.create([
{ name: "John Doe", email: "john@example.com" },
{ name: "Jane Smith", email: "jane@example.com" },
]);
console.log("Users added!");
process.exit();
}

addUsers();

 

Run the script:

 

node scripts/addUsers.js

 

8. Run the Application

 

npm run dev

 

Visit:

  • http://localhost:3000/users → See the user list loaded from MongoDB.

 

Conclusion

This implementation connects a Next.js app to MongoDB with Mongoose, fetches user data via an API route, and displays it client-side, completing a full-stack integration.

24.

Use Prisma ORM to fetch and display a list of products from a PostgreSQL database.

Answer

This task involves:

  • Setting up Prisma ORM for database interaction.
  • Connecting Next.js to PostgreSQL using Prisma.
  • Fetching and displaying products from the database.

 

1. Install Prisma and PostgreSQL Client

 

npm install @prisma/client
npx prisma init

 

✅ This creates a prisma/ folder and a .env file.

 

2. Configure PostgreSQL Connection (.env)

Update your .env file:

 

DATABASE_URL=postgresql://username:password@localhost:5432/mydatabase

 

Replace username, password, and mydatabase with your actual PostgreSQL credentials.

 

3. Define the Product Model (prisma/schema.prisma)

Edit prisma/schema.prisma:

 

generator client {
provider = "prisma-client-js"
}

datasource db {
provider = "postgresql"
url = env("DATABASE_URL")
}

model Product {
id Int @id @default(autoincrement())
name String
price Float
description String
createdAt DateTime @default(now())
}

 

Defines a Product model with id, name, price, description, and createdAt.

 

4. Migrate the Database

Run:

 

npx prisma migrate dev --name init

 

✅ This creates the products table in your PostgreSQL database.

 

5. Seed the Database with Test Products (prisma/seed.js)

prisma/seed.js

 

import { PrismaClient } from "@prisma/client";

const prisma = new PrismaClient();

async function main() {
await prisma.product.createMany({
data: [
{ name: "Laptop", price: 1200.99, description: "High-performance laptop" },
{ name: "Smartphone", price: 699.49, description: "Latest smartphone model" },
{ name: "Headphones", price: 149.99, description: "Noise-canceling headphones" },
],
});
console.log("Database seeded!");
}

main()
.catch((e) => console.error(e))
.finally(() => prisma.$disconnect());

 

Then run:

 

node prisma/seed.js

 

✅ Adds sample products to your database.

 

6. Create an API Route to Fetch Products (pages/api/products.js)

pages/api/products.js

 

import { PrismaClient } from "@prisma/client";

const prisma = new PrismaClient();

export default async function handler(req, res) {
if (req.method !== "GET") {
return res.status(405).json({ error: "Method Not Allowed" });
}

try {
const products = await prisma.product.findMany();
return res.status(200).json(products);
} catch (error) {
return res.status(500).json({ error: "Failed to fetch products" });
}
}

 

What Happens:

  • Fetches all products from PostgreSQL.
  • Returns them as JSON.

 

7. Display Products on a Next.js Page (pages/products.js)

pages/products.js

 

import { useEffect, useState } from "react";

export default function ProductsPage() {
const [products, setProducts] = useState([]);

useEffect(() => {
async function fetchProducts() {
const response = await fetch("/api/products");
const data = await response.json();
setProducts(data);
}
fetchProducts();
}, []);

return (
<div>
<h1>Products</h1>
{products.length === 0 ? (
<p>No products found</p>
) : (
<ul>
{products.map((product) => (
<li key={product.id}>
<h3>{product.name} - ${product.price.toFixed(2)}</h3>
<p>{product.description}</p>
</li>
))}
</ul>
)}
</div>
);
}

 

What Happens:

  • Client-side fetch from /api/products.
  • Displays a list of product names, prices, and descriptions.

 

8. Run the Application

 

npm run dev

 

Visit:

  • http://localhost:3000/products → Displays your products from PostgreSQL.

 

Conclusion

This implementation connects a Next.js app to PostgreSQL using Prisma ORM, fetches products via an API route, and displays them on a page, completing a full database-to-frontend flow.

25.

Implement a real-time chat app using WebSockets (Socket.io) in a Next.js API route.

Answer

1. Install Dependencies

 

npm install socket.io socket.io-client

 

2. Create the WebSocket Server (app/api/socket/route.js)

The App Router supports file-based routing under app/api, so we define the Socket.io server here.

 

// app/api/socket/route.js
import { Server } from "socket.io";

let io;

export function GET(req) {
if (!io) {
io = new Server({
path: "/api/socket",
cors: {
origin: "*",
methods: ["GET", "POST"],
},
});

io.on("connection", (socket) => {
console.log("User connected:", socket.id);

socket.on("chatMessage", (msg) => {
io.emit("chatMessage", msg);
});

socket.on("disconnect", () => {
console.log("User disconnected:", socket.id);
});
});

global.io = io;
}

return new Response("Socket server is running");
}

 

Note: This is suitable only for custom server or development; App Router doesn’t support full WebSocket lifecycle in production on Vercel. For production use, deploy on custom Node.js server or use external providers.

 

3. Create ChatBox Component

// components/ChatBox.js
"use client";

import { useState, useEffect } from "react";
import io from "socket.io-client";

let socket;

export default function ChatBox() {
const [message, setMessage] = useState("");
const [messages, setMessages] = useState([]);

useEffect(() => {
socket = io({
path: "/api/socket",
});

socket.on("chatMessage", (msg) => {
setMessages((prev) => [...prev, msg]);
});

return () => {
if (socket) socket.disconnect();
};
}, []);

const sendMessage = () => {
if (message.trim()) {
socket.emit("chatMessage", message);
setMessage("");
}
};

return (
<div>
<h2>Chat Room</h2>
<div style={{ height: 200, overflowY: "auto", border: "1px solid #ccc", padding: 10 }}>
{messages.map((msg, i) => (
<p key={i}>{msg}</p>
))}
</div>
<input
type="text"
value={message}
onChange={(e) => setMessage(e.target.value)}
placeholder="Type a message..."
/>
<button onClick={sendMessage}>Send</button>
</div>
);
}

 

4. Create the Chat Page (app/chat/page.js)

// app/chat/page.js
import dynamic from "next/dynamic";

const ChatBox = dynamic(() => import("../../components/ChatBox"), { ssr: false });

export default function ChatPage() {
return (
<div>
<h1>Real-Time Chat</h1>
<ChatBox />
</div>
);
}

5. Run the App

 

npm run dev

 

Open http://localhost:3000/chat in two tabs and chat live.

 

Conclusion

This setup uses the App Router and Socket.io to build a real-time chat app. In production, for persistent WebSocket support, consider running your app on a custom server (e.g., Node.js + Express) or using services like Pusher or Ably.

Next.js Developer hiring resources
Hire Next.js Developers
Hire fast and on budget—place a request, interview 1-3 curated developers, and get the best one onboarded by next Friday. Full-time or part-time, with optimal overlap.
Hire now
Q&A about hiring Next.js Developers
Want to know more about hiring Next.js Developers? Lemon.io got you covered
Read Q&A
Next.js Developer Job Description Template
Attract top Next.js developers with a clear, compelling job description. Use our expert template to save time and get high-quality applicants fast.
Check the Job Description

Hire remote Next.js developers

Developers who got their wings at:
Testimonials
star star star star star
Gotta drop in here for some Kudos. I’m 2 weeks into working with a super legit dev on a critical project, and he’s meeting every expectation so far 👏
avatar
Francis Harrington
Founder at ProCloud Consulting, US
star star star star star
I recommend Lemon to anyone looking for top-quality engineering talent. We previously worked with TopTal and many others, but Lemon gives us consistently incredible candidates.
avatar
Allie Fleder
Co-Founder & COO at SimplyWise, US
star star star star star
I've worked with some incredible devs in my career, but the experience I am having with my dev through Lemon.io is so 🔥. I feel invincible as a founder. So thankful to you and the team!
avatar
Michele Serro
Founder of Doorsteps.co.uk, UK

Simplify your hiring process with remote Next.js developers

Popular Next.js Development questions

What is the difference between Next.js and Vue.js?

Next.js is a React framework that couples server-side rendering with static generation in a unified framework. On the other hand, Vue.js is a separate JavaScript framework dedicated to building a flexible and reactive front-end user interface. Next.js finds its best utility in complex web applications, to be highly performant, whereas Vue.js works smoothly in creating dynamic front-end interfaces with relative simplicity.

What language does Next.js use?

Next.js primarily uses JavaScript but also supports TypeScript, a statically typed superset of JavaScript.

Can Next.js be used as a Back-end?

Yes, Next.js can be used as a Back-end. It natively supports API routes allowing one to create server-side endpoints within it.

Does Next.js replace Node.js?

Next.js does not replace Node.js but complements it. Next.js is a React framework for building web applications and often calculates on Node.js for the ease of server-side functionality. Whereas Node.js is for doing server-side work, some of the features of Next.js carry server-side rendering and static page generation.

What problem does Next.js solve?

Next.js makes it easier to build React applications by adding server-side rendering, static site generation, and API routes out of the box, hence making web apps faster and more SEO-friendly. It handles hard work concerning pre-rendering, routing, and performance optimization to ease the development process so developers can easily create dynamic, scalable, production-ready applications.

image

Ready-to-interview vetted Next.js developers are waiting for your request