Next.js is a powerful React framework built by Vercel that extends the capabilities of React by offering server-side rendering, static site generation, and many other performance and development enhancements out of the box.
Whether you're building a blog, an e-commerce site, or a large-scale web app, Next.js provides the tools and structure to build production-grade React applications efficiently.
⚙️ What Makes Next.js Different?
While Create React App (CRA) is a great starting point for single-page apps, it lacks many features needed in production - such as SEO support, routing, API handling, and performance optimizations.
Next.js fills these gaps by offering:
- Hybrid rendering (SSG, SSR, ISR)
- Built-in routing system
- Automatic code splitting
- Serverless API routes
- Built-in support for TypeScript, ESLint, Tailwind, etc.
- First-class deployment on Vercel
📈 Additional Benefits
- Image Optimization: Serve optimized images out of the box with next/image.
- Built-in Middleware: Run logic before rendering pages.
- API Routes: Quickly build backend services without leaving the Next.js app.
- Edge and Serverless Functions: Deploy serverless APIs and middleware at the edge to get ultra-low latency.
- Thanks to these features, Next.js enables faster development cycles, better performance, and simpler scaling for both startups and enterprises.
⚔️ CRA vs Next.js – Feature Comparison
Server-Side Rendering (SSR)
- CRA: 🚫 Not supported
- Next.js: ✅ Built-in (
getServerSideProps
)
Static Site Generation (SSG)
- CRA: 🚫 Not supported
- Next.js: ✅ Fully supported (
getStaticProps
)
SEO Optimization
- CRA: 🚫 Limited
- Next.js: ✅ Excellent (pre-rendered HTML)
Routing
-
CRA: 🚫 Requires additional libraries (e.g., React Router)
While React Router is the most common solution for routing in CRA, other alternatives like Reach Router or custom implementations can also be used. -
Next.js: ✅ File-based routing system (
pages/
directory)
API Routes
-
CRA: 🚫 Requires an external backend
CRA does not include built-in API routes, but it can be paired with solutions like Firebase, or an Express server can be integrated to handle backend logic. -
Next.js: ✅ Built-in API endpoints (
pages/api
)
TypeScript Support
-
CRA: 🟡 Manual setup required
CRA supports TypeScript, but you need to configure it during project creation or manually add it later. -
Next.js: ✅ Native support out of the box
Deployment Ready
-
CRA: 🟡 Needs extra configuration
CRA requires additional setup for deployment, such as configuring a server or using platforms like Netlify or Firebase Hosting. -
Next.js: ✅ Ready-to-go with Vercel
Next.js is optimized for deployment on Vercel, with features like preview URLs and automatic CI/CD.
🆕 App Router vs Page Router
With Next.js 13, a new routing system called the App Router was introduced.
It exists alongside the classic Page Router (based on the /pages
directory) and offers a more modern and flexible way to structure your applications.
🔑 Key Differences
-
Structure:
- Page Router: Based on the
/pages
folder - App Router: Based on the
/app
folder
- Page Router: Based on the
-
Data Fetching:
- Page Router:
getStaticProps
,getServerSideProps
- App Router:
fetch()
directly inside components or using async functions
- Page Router:
-
Layouts:
- Page Router: 🚫 Not natively supported
- App Router: ✅ Native support for shared layouts
-
Server Components:
- Page Router: 🚫 Not supported
- App Router: ✅ Fully supported
-
Streaming:
- Page Router: 🚫 Not supported
- App Router: ✅ Built-in streaming support
-
SEO Metadata:
- Page Router: Using
<Head>
fromnext/head
- App Router: Using a dedicated
head.tsx
file per route
- Page Router: Using
🗂 App Router: A New Structure
The App Router uses the app
directory to define routes.
Here's a simple example of a typical structure:
/app
├── layout.tsx // Shared layout across all pages
├── page.tsx // Main page (route `/`)
├── about
│ ├── page.tsx // `/about` route
│ ├── head.tsx // SEO metadata for `/about`
├── blog
│ ├── [slug]
│ │ ├── page.tsx // Dynamic route `/blog/:slug`
🛤 Example Usage in the App Router
Dynamic Route Example:
// app/blog/[slug]/page.tsx
export default function BlogPostPage({ params }: { params: { slug: string } }) {
return <h1>Blog Post: {params.slug}</h1>;
}
Catch-All Route Example:
// app/blog/[...slug]/page.tsx
export default function BlogCategoryPage({
params,
}: {
params: { slug: string[] };
}) {
return <h1>Category: {params.slug.join(' / ')}</h1>;
}
🛠 Key Features of the App Router
1. Server Components by Default
With the App Router, React components can be rendered on the server by default, improving performance and reducing the client-side bundle size.
// Example of a Server Component
export default async function Page() {
const data = await fetch('https://api.example.com/data').then((res) =>
res.json()
);
return <div>{data.title}</div>;
}
2. Shared Layouts
The App Router natively supports defining layouts that persist across multiple pages.
This makes it easier to manage repeating structures like headers, sidebars, and footers.
// app/layout.tsx
export default function RootLayout({
children,
}: {
children: React.ReactNode;
}) {
return (
<html lang="en">
<body>
<header>My Header</header>
<main>{children}</main>
</body>
</html>
);
}
3. Streaming and Suspense
The App Router supports streaming and progressive rendering using React Suspense.
This enables faster page loads and a better user experience.
Example:
import { Suspense } from 'react';
export default function Page() {
return (
<div>
<h1>My Page</h1>
<Suspense fallback={<p>Loading...</p>}>
<MyComponent />
</Suspense>
</div>
);
}
4. Simplified SEO Metadata
🛠 Basic SEO
With the App Router, SEO metadata is now defined in a dedicated head.tsx
file within each route. This makes SEO management cleaner, more consistent, and more flexible.
Next.js 13 also introduces enhanced SEO capabilities in Server Components, allowing fine-grained control over the <head>
for each route.
Example:
// app/about/head.tsx
export default function Head() {
return (
<>
<title>About Us - MyApp</title>
<meta name="description" content="Learn more about us." />
<meta property="og:title" content="About Us - MyApp" />
<meta property="og:description" content="Learn more about us." />
<meta property="og:image" content="/images/og-image.jpg" />
</>
);
}
🎯 Advanced SEO with metadata and generateMetadata in Next.js
🔵 Static Metadata
Static metadata is defined when your SEO information does not change dynamically.
Use the metadata export inside your page.tsx file.
Example:
// app/about/page.tsx
export const metadata = {
title: 'About Us - MyApp',
description: 'Learn more about our company and mission.',
};
export default function AboutPage() {
return <h1>About Us</h1>;
}
How it works:
- Next.js reads the static metadata object at build time.
- It generates the appropriate
<title>
and<meta>
tags in the HTML, ensuring fast SEO indexing.
🟠 Dynamic Metadata with generateMetadata
When your metadata depends on dynamic data (like a blog post title, a product name, etc.), use the generateMetadata function.
Example:
// app/blog/[slug]/page.tsx
import { fetchPost } from '@/lib/api';
export async function generateMetadata({
params,
}: {
params: { slug: string };
}) {
const post = await fetchPost(params.slug);
return {
title: post.title,
description: post.excerpt,
};
}
export default function BlogPostPage({ params }: { params: { slug: string } }) {
return <h1>{params.slug}</h1>;
}
How it works:
generateMetadata is an async function that runs on the server. It allows you to fetch data (e.g., from a database or an API) and generate SEO metadata at runtime before serving the page.
🌟 Key Benefits of Using metadata and generateMetadata
-
Centralized SEO Management: Metadata is defined close to the page/component it belongs to, making maintenance easier.
-
Full Type Safety: Both metadata and generateMetadata are fully typed with TypeScript, reducing errors.
-
Improved Performance: Static metadata is embedded at build time, while dynamic metadata is generated server-side without impacting client performance.
-
Better SEO Indexing: Pages always have complete and accurate metadata, which improves discoverability and ranking.
-
Flexibility: Whether you need simple static titles or complex dynamic meta tags, you can handle both natively with Next.js 13.
Comparing head.tsx, metadata, and generateMetadata
- head.tsx
Use case: Best suited for managing SEO metadata directly in each page or route.
When to use: Static content or when you need precise control over each page’s <head>
tag.
Benefits: Highly flexible, allows integration of Open Graph tags, Twitter Cards, and other meta tags for social sharing. Easy to maintain on a per-route basis.
- metadata
Use case: For simple, static metadata definitions.
When to use: Ideal for pages where the metadata doesn't change dynamically (e.g., static informational pages like "About Us").
Benefits: Minimal setup, evaluates at build time, fast indexing, and easy integration.
- generateMetadata
Use case: For pages where the metadata needs to be dynamically generated based on data, such as blog posts or product pages.
When to use: Use when metadata depends on data fetched server-side, like post titles, descriptions, or dynamic product details.
Benefits: Fully dynamic, ensures SEO tags are always up to date, fetches data before the page is rendered, improving SEO performance for dynamic content.
🧭 Choosing Between App Router and Page Router
It's important to note that both the App Router and Page Router are fully functional in Next.js.
The App Router introduces new features like Server Components, shared layouts, and streaming, making it ideal for new projects or those looking to leverage the latest advancements in React and Next.js.
However, the Page Router remains a great choice for:
- Existing projects: If you're already using the
/pages
directory, you can continue without any changes. - Simplicity: The Page Router is familiar and straightforward, especially for developers transitioning from older versions of Next.js or React projects.
⚡ Quick Recap
-
App Router:
Ideal for new projects or when you want to take advantage of the latest features like Server Components, shared layouts, and streaming. -
Page Router:
Recommended for existing projects or if you prefer a simpler, more traditional React file-based approach.
Both systems are supported in Next.js, so you can choose the one that best fits your project's needs.
🌐 Internationalization (i18n)
Next.js natively supports internationalization (i18n), allowing you to easily create multilingual applications.
🔧 Configuration
Add the configuration in next.config.js
:
// next.config.js
module.exports = {
i18n: {
locales: ['en', 'fr', 'es'], // Supported languages
defaultLocale: 'en', // Default language
domains: [
{
domain: 'example.com',
defaultLocale: 'en',
},
{
domain: 'example.fr',
defaultLocale: 'fr',
},
{
domain: 'example.es',
defaultLocale: 'es',
},
],
},
};
The domains
configuration is optional and is used when you want to serve different languages on specific subdomains or domains. For example:
example.com
→ Englishexample.fr
→ Frenchexample.es
→ Spanish
🛤 Example Usage
Routes are automatically generated for each language:
/about
→/en/about
/about
→/fr/about
/about
→/es/about
If you use the domains
configuration, the URLs will be mapped to their respective domains:
https://example.com/about
→ Englishhttps://example.fr/about
→ Frenchhttps://example.es/about
→ Spanish
🌟 Benefits of i18n in Next.js
- Automatic Routing: Next.js automatically generates routes for each language.
- SEO-Friendly: Language-specific URLs improve SEO for multilingual applications.
- Flexible Configuration: Supports subdomains, domains, or path-based routing for languages.
By adding the domains
configuration, you can handle more complex multilingual setups, making your application more robust and user-friendly.
🧩 Dynamic Imports
Dynamic imports allow you to load heavy or rarely used components only when needed, improving initial load times.
More Advanced Example:
// Load with a custom loading component
const DynamicComponentWithCustomLoading = dynamic(
() => import('../components/HeavyComponent'),
{
loading: () => <p>Loading component...</p>,
ssr: false, // Optionally disable server-side rendering
}
);
export default function Page() {
return (
<div>
<h1>Dynamic Import Example</h1>
<DynamicComponentWithCustomLoading />
</div>
);
}
📦 Key Benefits
-
Reduced Bundle Size: Load code only when needed.
-
Faster Time-to-Interactive: Prioritize essential content.
-
Custom Loading States: Provide better UX during component loading.
-
Dynamic imports are especially useful for modals, heavy charts, maps, or any feature that is not required immediately on page load.
🔒 Authentication with NextAuth.js
Next.js integrates seamlessly with libraries like NextAuth.js for handling authentication.
📚 Key Features
-
Supports multiple providers (OAuth, Email, Credentials, etc.)
-
Serverless ready: works well with Vercel, AWS, etc.
-
Built-in session management
-
Secure by default (CSRF protection, JWT support)
🔧 Example Configuration:
Create an API route:
// pages/api/auth/[...nextauth].ts
import NextAuth from 'next-auth';
import GitHubProvider from 'next-auth/providers/github';
export default NextAuth({
providers: [
GitHubProvider({
clientId: process.env.GITHUB_ID,
clientSecret: process.env.GITHUB_SECRET,
}),
],
});
Then, use the useSession
, signIn
, and signOut
hooks in your React components.
🧩 Usage in Components
import { useSession, signIn, signOut } from 'next-auth/react';
export default function Component() {
const { data: session } = useSession();
if (session) {
return (
<>
<p>Signed in as {session.user?.email}</p>
<button onClick={() => signOut()}>Sign out</button>
</>
);
}
return (
<>
<p>Not signed in</p>
<button onClick={() => signIn()}>Sign in</button>
</>
);
}
NextAuth.js simplifies implementing authentication in modern web applications.
🖼 Image Optimization
Next.js automatically optimizes images with the next/image
component:
- ✅ Lazy loading
- ✅ Automatic resizing
- ✅ Responsive image handling (Automatically serve the correct size for each device.)
- ✅ Modern formats (WebP, AVIF)
📚 Additional Features
- Priority Loading: Use the priority prop for critical images.
- Placeholder Blurs: Show a low-resolution blur while the main image loads (placeholder="blur").
🖼 Examples:
import Image from 'next/image';
export default function MyImage() {
return (
<Image
src="/images/banner.jpg"
alt="Banner"
width={800}
height={400}
priority
/>
);
}
Example with blur placeholder:
import Image from 'next/image';
import banner from '../public/images/banner.jpg';
export default function MyImage() {
return (
<Image
src={banner}
alt="Banner"
width={800}
height={400}
placeholder="blur"
/>
);
}
This dramatically improves perceived performance on slower networks.
🛠 Middleware for Advanced Control
Middleware gives you fine-grained control over requests.
📚 Common Use Cases
-
Authentication: Redirect unauthorized users.
-
Localization: Redirect users based on their locale.
-
Feature Flags: Roll out new features to specific audiences.
-
Analytics: Inject additional tracking or logging early.
-
A/B testing
🛠 Examples:
// middleware.ts
import { NextResponse } from 'next/server';
import type { NextRequest } from 'next/server';
export function middleware(req: NextRequest) {
const token = req.cookies.get('token');
if (!token) {
return NextResponse.redirect(new URL('/login', req.url));
}
return NextResponse.next();
}
Example for locale redirection:
export function middleware(req: NextRequest) {
const locale = req.headers.get('accept-language')?.split(',')[0] ?? 'en';
const url = req.nextUrl.clone();
if (locale.startsWith('fr') && !url.pathname.startsWith('/fr')) {
url.pathname = `/fr${url.pathname}`;
return NextResponse.redirect(url);
}
return NextResponse.next();
}
📂 File Placement
Put middleware.ts in the root (/) or in specific folders (e.g., /app/admin/middleware.ts) to scope it.
🚀 Static Export
next export allows you to pre-render all pages as static HTML/CSS.
📚 Important Considerations
-
Only pages that use Static Generation (SSG) can be exported.
-
Server-Side Rendering (SSR) and API Routes will be skipped.
-
Great for deploying to GitHub Pages, Netlify, Vercel (in static mode), or any CDN.
Example:
next build && next export
This generates an /out
folder containing your site ready to be deployed on (static hosting):
- GitHub Pages
- Netlify
- Vercel (in static mode)
Note: Some features like SSR or API Routes are not compatible with next export
.
🧪 Testing in Next.js
Next.js integrates well with popular testing tools like Jest, Testing Library or Cypress.
⚙️ Example Jest Configuration:
Create a jest.config.js
file:
// jest.config.js
module.exports = {
testEnvironment: 'jsdom',
setupFilesAfterEnv: ['<rootDir>/jest.setup.js'],
moduleNameMapper: {
'^@/components/(.*)$': '<rootDir>/components/$1',
'^next/image$': '<rootDir>/__mocks__/nextImageMock.js', // Mock for next/image
'^next/head$': '<rootDir>/__mocks__/nextHeadMock.js', // Mock for next/head
},
};
Handling next/image
and next/head
By default, next/image
and next/head
can cause issues in Jest tests because they rely on Next.js-specific features. To resolve this, you can mock these modules.
Mocking next/image
:
Create a file __mocks__/nextImageMock.js
:
// __mocks__/nextImageMock.js
const NextImage = (props) => {
return <img {...props} />;
};
export default NextImage;
This replaces next/image
with a simple <img>
tag for testing purposes.
Mocking next/head
:
Create a file __mocks__/nextHeadMock.js
:
// __mocks__/nextHeadMock.js
const Head = ({ children }) => {
return <>{children}</>;
};
export default Head;
This ensures that next/head
renders its children without causing errors in the test environment.
Jest Setup File
Add a jest.setup.js
file to configure additional testing utilities:
// jest.setup.js
import '@testing-library/jest-dom'; // Provides custom matchers for DOM nodes
Writing a Test for a Component
Here’s an example of a test for a Next.js component:
// components/Header.tsx
export default function Header() {
return <h1>Welcome to My Site</h1>;
}
// components/__tests__/Header.test.tsx
import { render, screen } from '@testing-library/react';
import Header from '../Header';
test('renders the header', () => {
render(<Header />);
const heading = screen.getByText(/Welcome to My Site/i);
expect(heading).toBeInTheDocument();
});
Running Tests
Run your tests using the following command:
npx jest
By adding these configurations and mocks, you can ensure that Jest works seamlessly with Next.js features like next/image
and next/head
, making your testing environment more robust and reliable.
✅ Advantages of Next.js
1. Hybrid Rendering (SSG, SSR, ISR)
Next.js gives you full control over how pages are rendered:
- SSG (Static Site Generation): Generated at build time - great for blog posts, documentation.
- SSR (Server-Side Rendering): Generated on each request via
getServerSideProps
- perfect for dashboards or personalized pages. - ISR (Incremental Static Regeneration): Regenerates static pages after deployment using a
revalidate
interval.
Example:
export async function getStaticProps() {
const data = await fetch('https://api.example.com/posts');
return {
props: { data },
revalidate: 60, // re-generate every 60 seconds
};
}
2. SEO-Friendly
Because Next.js supports server-side rendering, pages are delivered to search engines as fully rendered HTML, improving crawlability and SEO. Ideal for marketing sites, blogs, and e-commerce platforms.
3. File-System Routing
Next.js provides a powerful file-based routing system, and with the introduction of the App Directory in Next.js 13, routing has become even more flexible and modern.
Page Router (Legacy)
In the Page Router, routes are automatically created based on the pages/
directory structure:
/pages/index.tsx
→/
/pages/about.tsx
→/about
/pages/blog/[slug].tsx
→/blog/my-article
Supports:
-
Dynamic routes:
[slug].tsx
Dynamic routes allow you to create pages with parameters, such as/blog/:slug
. -
Catch-all routes:
[...params].tsx
Catch-all routes can handle a variable number of parameters.For example:
/pages/blog/[...slug].tsx
can match routes like/blog/category
or/blog/category/subcategory
.Catch-all routes are particularly useful for deeply nested paths or when you need flexibility in your URL structure.
App Router (Modern)
With the App Router, introduced in Next.js 13, routing is based on the app/
directory. It provides additional features like Server Components, shared layouts, and streaming.
4. Performance Optimizations
Next.js includes several performance features:
- ✅ Automatic code splitting
- ✅ Lazy loading
- ✅ Image optimization via
next/image
- ✅ Link prefetching
5. API Routes
You can define API endpoints directly in your app:
// pages/api/hello.ts
export default function handler(req, res) {
res.status(200).json({ message: 'Hello from API!' });
}
Perfect for small full-stack applications.
6. TypeScript, ESLint, and Tailwind Integration
npx create-next-app@latest
scaffolds a project with TypeScript- Official plugins for ESLint, Prettier, Tailwind CSS make setup easy
- First-class DX with strong typing and linting support
7. Fast Deployment with Vercel
- Push your code → automatic deployment
- Built-in CI/CD, preview URLs, and instant rollback
- Optimized for Next.js by default
🧠 Rendering Strategies in Next.js
📚 Static Site Generation (SSG)
- Pages generated at build time
- Super fast, cached by CDN
- Ideal for: blogs, docs, marketing pages
⚙️ Server-Side Rendering (SSR)
- Pages generated at each request
- Always fresh data
- Ideal for: user dashboards, content personalization
🔄 Incremental Static Regeneration (ISR)
- Combines benefits of SSG and SSR
- Regenerates content at runtime based on a revalidation interval
- Ideal for: news articles, product listings
⚠️ Disadvantages of Next.js
1. Learning Curve
- More concepts to understand than CRA
- Need to grasp
getStaticProps
,getServerSideProps
, ISR, etc.
2. File-Based Routing Limitations
- Great for most use cases, but complex nested or custom routes may need workarounds
3. Large Static Builds
- Many pages with
getStaticProps
can slow down build time - Use ISR to mitigate this
4. Serverless Function Limitations
- Cold starts on Vercel
- Limited memory/execution time
- Not suitable for heavy backend logic
5. Opinionated Structure
- Enforces usage of
/pages
,/app
,/api
- Can be restrictive for some architectures
🧭 When to Use Next.js?
✅ Best use cases:
- Blogs and marketing sites (static and SEO-friendly)
- E-commerce platforms
- Internal tools with API routes
- Developer portfolios
- Dashboards
🚫 Not always ideal for:
- Apps with no SEO requirements and complex routing
- Projects needing fine-grained backend control
- Traditional apps using frameworks like Express/NestJS
What's New in Next.js 15:
Next.js 15 was officially released on October 21, 2024 . This stable release introduced several new features and improvements, including enhanced image optimization, support for React 19, and the integration of Turbopack for faster builds . (Next.js Support Policy | Next.js by Vercel - The React Framework, Next.js)
For more detailed information on the features introduced in Next.js 15, you can refer to the official release notes (Next.js 15).
-
@next/codemod CLI: Easily upgrade to the latest Next.js and React versions with this new CLI tool.
-
Async Request APIs (Breaking): A step towards a simplified rendering and caching model, which may require adjustments to existing code.
-
Caching Semantics (Breaking): Fetch requests, GET Route Handlers, and client navigations are no longer cached by default. This changes the way caching works in Next.js.
-
React 19 Support: Full support for React 19, React Compiler (experimental), and significant hydration error improvements.
-
Turbopack Dev (Stable): Performance and stability improvements to Turbopack, the new bundler designed for lightning-fast builds.
-
Static Indicator: A new visual indicator shows static routes during development, helping you quickly identify static content.
-
unstable_after API (Experimental): Execute code after a response finishes streaming, giving you more control over the lifecycle of requests.
-
instrumentation.js API (Stable): A new API that enhances server lifecycle observability, making it easier to monitor and debug server-side behavior.
-
Enhanced Forms (next/form): A new API that enhances HTML forms with client-side navigation, improving user experience and form handling.
-
next.config: TypeScript Support for next.config.ts: TypeScript support is now available for
next.config.ts
, making it easier to configure your Next.js app with full type safety. -
Self-hosting Improvements: More control over Cache-Control headers, allowing for better caching behavior when self-hosting Next.js apps.
-
Server Actions Security: Improved security with unguessable endpoints and the removal of unused actions to minimize attack vectors.
-
Bundling External Packages (Stable): New configuration options for the App and Pages Routers that enhance the bundling of external packages.
-
ESLint 9 Support: Full support for ESLint 9, bringing the latest features and improvements to your linting setup.
-
Development and Build Performance: Significant improvements to build times and Faster Fast Refresh for a more streamlined development experience.