Next.js is a powerful React framework that enables you to build high-performance web applications with features like Server-Side Rendering (SSR) and Static Site Generation (SSG) out of the box. While React is a library for building UIs, Next.js provides the "scaffolding"—handling routing, optimization, and data fetching—making it a full-stack framework.
- App Router: A modern routing system built on React Server Components, supporting layouts, nesting, and streaming.
- Hybrid Rendering: Choose between SSR, SSG, and ISR (Incremental Static Regeneration) on a per-page basis.
-
Automatic Image Optimization: Automatically resizes and serves images in modern formats like WebP or AVIF using the
component. - Built-in CSS/Sass Support: Support for CSS Modules, Tailwind CSS, and CSS-in-JS.
- Turbopack: A high-performance Rust-based bundler that makes development builds significantly faster than Webpack.
- SEO Friendly: By pre-rendering content on the server, search engines can easily crawl your site, which is a major advantage over standard Client-Side React.
- Fast Initial Load: Only the necessary JavaScript and CSS are sent to the client, reducing "Time to Interactive."
- Zero Config: Features like TypeScript support, linting, and bundling are configured automatically.
- API Routes: You can write backend code (like handling form submissions or database queries) directly within your Next.js project.
To start a new project, you need Node.js (v20.9 or later as of 2026). The recommended way is using the automated CLI tool:
-
Run the installer:
npx create-next-app@latest - Follow the prompts: You’ll be asked to name your project and choose options like TypeScript, ESLint, and Tailwind CSS.
-
Start development:
cd your-app-nameand thennpm run dev. -
View it: Open
http://localhost:3000.
Note: As of early 2026, Next.js 15/16 is the stable standard. These versions focus on deep integration with React 19, stable Server Actions, and the use of Turbopack as the default bundler for lightning-fast development cycles.
In the modern App Router, components are Server Components by default. They stay on the server and do not send JavaScript to the client, which keeps your bundle size small.
-
Client Components: If you need interactivity (like
useStateor ,onClick), you must add the'use client'directive at the top of the file.
Next.js uses a file-system based router. In the App Router, folders define routes.
-
app/page.tsx? maps to / -
app/about/page.tsx? maps to/about -
app/blog/[slug]/page.tsx? maps to a dynamic route like/blog/hello-world
A layout is UI that is shared between multiple pages. On navigation, layouts preserve state, remain interactive, and do not re-render. This is where you typically put your Navigation bar and Footer.
In the App Router, you can fetch data directly inside Server Components using standard async/await and the native fetch API.
Next.js extends fetch to allow for automatic caching and revalidation (replacing the old getStaticProps).
Server Actions are asynchronous functions that run on the server. They are often used to handle form submissions without needing to manually create an API endpoint.
Comparison of App Router vs. Pages Router
The App Router is the modern standard for Next.js, introduced to leverage React Server Components, while the Pages Router is the legacy system still maintained for backwards compatibility.
| Feature | App Router (Modern) | Pages Router (Legacy) |
|---|---|---|
| Directory | Uses the app/ directory. |
Uses the pages/ directory. |
| Routing Logic | Folder-based: Folders define routes; page.js makes them public. |
File-based: Every file in the folder is a route. |
| Component Type | Server Components by default; Client Components via 'use client'. | Client Components only (with SSR/SSG hydration). |
| Data Fetching | Uses async/await in Server Components + fetch(). |
Uses getStaticProps, getServerSideProps, and getInitialProps. |
| Layouts | Supports Nested Layouts (native layout.js). |
Requires custom _app.js or _document.js wrappers. |
| Rendering | Granular (Server, Client, Streaming, and Static). | Page-level (SSR, SSG, or CSR). |
Key Technical Differences
- Server Components (RSC): The App Router allows components to stay on the server, significantly reducing the JavaScript bundle sent to the browser. In the Pages Router, all components are sent to the client for hydration.
-
Data Fetching Evolution: In the App Router, data fetching is simplified. You no longer need specific Next.js functions like
getServerSideProps; you simply usefetchwith caching options (>{ cache: 'no-store' }for dynamic data or>{ next: { revalidate: 3600 } }for ISR). - Streaming & Suspense: The App Router is built on React Suspense. This allows for Streaming, where parts of the page can load and display UI immediately while slower data-fetching segments are still loading.
-
SEO & Metadata: The App Router uses a built-in
MetadataAPI (exporting ametadataobject), whereas the Pages Router relies on the>< Head>component.
Server Components vs. Client Components
In the Next.js App Router, components are Server Components by default. The distinction lies in where the component renders and how much JavaScript is sent to the browser.
| Feature | App Router (Modern) | Pages Router (Legacy) |
|---|---|---|
| Directory | Uses the app/ directory. |
Uses the pages/ directory. |
| Routing Logic | Folder-based: Folders define routes; page.js makes them public. |
File-based: Every file in the folder is a route. |
| Component Type | Server Components by default; Client Components via 'use client'. | Client Components only (with SSR/SSG hydration). |
| Data Fetching | Uses async/await in Server Components + fetch(). |
Uses getStaticProps, getServerSideProps, and getInitialProps. |
| Layouts | Supports Nested Layouts (native layout.js). |
Requires custom _app.js or _document.js wrappers. |
| Rendering | Granular (Server, Client, Streaming, and Static). | Page-level (SSR, SSG, or CSR). |
When to Use Each
- Use Server Components for:
- Fetching data from a database or API.
- Storing sensitive information (API keys, tokens).
- Keeping large dependencies on the server to reduce client-side bundle size.
- Static content that doesn't change based on user input.
- Use Client Components for:
- Interactivity (buttons, forms, carousels).
-
Using State and Lifecycle hooks (
useState, useReducer, useEffect). -
Browser-only APIs (Geolocation,
localStorage, document). - Custom hooks that depend on state or effects.
The "Component Tree" Rule
You can import a Client Component into a Server Component. However, you cannot import a Server Component into a Client Component directly. To use a Server Component inside a Client Component, you must pass it as children or a prop to maintain the server-side rendering boundary.
The 'use client' Directive
The 'use client' directive is a convention used to declare a boundary between server-side and client-side code. It signals to the bundler that the module and its dependencies are intended for the browser.
-
Placement: It must be the very first line of the file, appearing before any import statements. - Scope: Once a file is marked with 'use client', all other modules imported into it are also considered part of the client bundle.
Usage Rules and Best Practices
-
Leaf Component Pattern: To keep the application fast, you should move
'use client'as far down the component tree as possible. For example, instead of making a whole layout a Client Component just for asearch bar, make only the SearchBar component a Client Component. -
Default Behavior: You do not need
'use client'in every file. In the App Router, files are Server Components by default. - Interoperability: You can pass Server Components as children or props to a Client Component, but you cannot import and render a Server Component directly inside a Client Component file.
Implementation Comparison
| Aspect | Correct Implementation | Incorrect Implementation |
|---|---|---|
| Position | Top of the file (Line 1). | Inside a function or after imports. |
| Syntax | 'use client'; (quotes required). |
use client; (no quotes). |
| Imports | Imports follow the directive. | Imports precede the directive. |
Example Code
Static Site Generation (SSG) in the App Router
In the Next.js App Router, Static Site Generation (SSG) is the process of fetching data and rendering pages at build time rather than at request time. The result is a set of static HTML, JavaScript, and CSS files that can be served instantly from a CDN.
In the App Router, SSG is the default behavior for any component that does not use dynamic functions (like cookies() or headers()) or uncached data fetches.
How SSG is Triggered
Next.js automatically statically generates routes unless you explicitly opt out. It relies on the caching behavior of the fetch API.
-
Static Fetch (Default):
fetch('https://api.example.com/data')is cached indefinitely by default. -
Force Cache:
fetch('url', { cache: 'force-cache' })explicitly ensures the data is fetched once at build time.
Dynamic Routes and SSG
For routes with dynamic parameters (e.g., /blog/[slug]), Next.js needs to know which paths to pre-render at build time. This is handled by the generateStaticParams function.
| Function/Feature | Purpose in SSG |
|---|---|
generateStaticParams |
Replaces the old getStaticPaths. It returns an array of objects representing the route segments to be statically generated. |
fetch Caching |
Replaces getStaticProps. Data is fetched during the build and stored in the Data Cache. |
export const dynamic |
Setting force-static ensures a layout or page is always treated as static, even if it uses dynamic logic. |
Key Advantages of SSG
- Maximum Performance: Since the HTML is already generated, the server just sends a file, leading to a near-instant "Time to First Byte" (TTFB).
- Reduced Server Load: The server (or Edge Network) doesn't need to execute database queries or complex logic for every user request.
- SEO Optimization: Fully rendered HTML is available immediately for search engine crawlers.
Example: SSG for Dynamic Routes
Dynamic Rendering in Next.js
Dynamic Rendering is a strategy where routes are rendered for each user at request time. This is essential for pages that display personalized data (like user profiles), real-time information (like stock prices), or information that depends on information only known at the time of the request (like cookies).
How Next.js Decides: Static vs. Dynamic
Next.js automatically switches from Static Rendering (default) to Dynamic Rendering if it detects the use of Dynamic Functions or Uncached Data Requests.
| Category | Triggers Dynamic Rendering | Description |
|---|---|---|
| Dynamic Functions | cookies(), headers() |
Accessing request-specific info like auth tokens or browser headers. |
| Search Params | searchParams prop |
Accessing URL query strings (e.g., ?query=nextjs) in a page component. |
| Uncached Fetch | cache: 'no-store' |
Explicitly telling a fetch request to bypass the cache and get fresh data. |
| Segment Config | export const dynamic = 'force-dynamic' |
Manually forcing the route to render dynamically. |
Key Characteristics
- Request-Time Execution: The server processes the code and generates HTML only when a user visits the URL.
- Data Freshness: Since rendering happens on-demand, the content is always up-to-date with the latest database or API changes.
- Performance Trade-off: It is generally slower than Static Rendering because the server must do work for every request rather than serving a pre-built file from a CDN.
The "Caching" Nuance
In the App Router, even if a route is dynamically rendered, Next.js may still cache specific data fetches within that route unless specified otherwise. This is known as the Data Cache, which operates independently of whether the page itself is static or dynamic.
Incremental Static Regeneration (ISR)
Incremental Static Regeneration (ISR) allows you to update static content without needing to rebuild the entire site. It provides the performance benefits of Static Site Generation (SSG) with the flexibility of Dynamic Rendering by revalidating specific pages in the background.
How it Works
- Build Time: The page is generated statically.
- Initial Requests: All users see the cached static version.
- Revalidation Window: After a specified time interval, the next request triggers a background regeneration
- Background Update: Next.js fetches new data and regenerates the page. Once successful, the cache is updated for future visitors.
Implementation Methods
In the App Router, ISR is implemented through the fetch API or segment configuration rather than the legacy revalidate prop from getStaticProps.
| Method | Implementation Code | Use Case |
|---|---|---|
| Time-based (Fetch) | fetch(url, { next: { revalidate: 3600 } }) |
Automatically updates the data cache every hour. |
| Segment Config | export const revalidate = 3600; |
Applies a revalidation frequency to the entire page or layout. |
| On-Demand (Tag) | revalidateTag('collection') |
Manually clears cache when a specific event occurs (e.g., CMS update). |
| On-Demand (Path) | revalidatePath('/blog/post-1') |
Manually updates a specific URL immediately. |
Example: Implementation with Fetch
Benefits of ISR
- Scalability: Handles millions of pages without massive build times.
- Freshness: Keeps content updated without manual redeploys.
- Reliability: If a background regeneration fails, the old page remains available, ensuring zero downtime.
Streaming and React Suspense
Streaming allows you to break down a page's HTML into smaller chunks and progressively send them from the server to the client. This prevents "slow" data requests from blocking the entire page, allowing the user to see and interact with parts of the page (like navigation or sidebars) while the main content is still loading.
How it Works
- Traditional SSR: The server must fetch all data for a page before it can send any HTML to the client. The user sees a blank screen or a loading spinner for the whole page.
-
Streaming with Suspense: Next.js sends the static parts of the page immediately. Parts of the UI wrapped in
are replaced by a loading fallback. Once the server finishes fetching the data for that specific component, it "streams" the final HTML and swaps it into place.
Implementation Methods
| Method | Level | Description |
|---|---|---|
loading.js |
Route Level | A special file placed in a folder. Next.js automatically wraps the page.js and any nested children in a <Suspense> boundary with this file as the fallback. |
<Suspense> |
Component Level | Manual wrapping around specific components. This allows for more granular control, letting multiple parts of a page load independently. |
Example: Component-Level Streaming
Key Benefits
- Reduced Time to First Byte (TTFB): The server starts sending HTML almost immediately.
- Improved First Contentful Paint (FCP): Critical UI elements appear sooner.
- Prioritized Interaction: Users can click links or use the sidebar without waiting for the entire data-heavy center of the page to load.
The Purpose of loading.tsx
The loading.tsx file is a special Next.js file used to create instant loading states for a specific route segment. It leverages React Suspense under the hood to improve the user experience during data fetching.
Core Functions
-
Next.js automatically wraps your
page.tsxfile (and any nested children) in aboundary. The content ofloading.tsxserves as thefallbackUI. - Instant Feedback: Because it is pre-rendered on the server, the loading UI appears immediately upon navigation, even before the server has finished fetching the data for the page.
-
Shared Layouts: Navigation remains interactive. Users can still interact with shared elements like sidebars or navbars defined in
layout.tsxwhile the specific page content loads.
Hierarchy and Nesting
The loading.tsx file follows the file-system hierarchy. A file in a parent folder will apply to all nested child routes unless the child route has its own
| Feature | Description |
|---|---|
| Behavior | loading.tsx behavior |
| Placement | Place in any folder within the /app directory. |
| Trigger | Triggered automatically during server-side data fetching or client-side navigation. |
| User Experience | Prevents "stuck" navigations where the screen doesn't change until data is ready. |
| Implementation | Usually contains Skeletons, Spinners, or "Ghost" UI elements. |
Example Implementation
Nested Layouts in Next.js
A Nested Layout is a UI structure created by placing a layout.tsx file inside a sub-folder of the /app directory. This layout wraps only the segments within that specific folder, while being "nested" inside the parent layout (usually the root layout).
How Nesting Works
Next.js uses a containment model. A child layout or page is passed as the children prop to the layout immediately above it in the file hierarchy.
-
Root Layout (app/layout.tsx): Applies to the entire application (contains
< html>and< body>). -
Segment Layout (
app/dashboard/layout.tsx): Applies only to routes starting with/dashboard.
State Persistence
One of the primary advantages of layouts is that they persist state and maintain interactivity during navigation between their child routes.
| Feature | Behavior in Layouts |
|---|---|
| Partial Rendering | On navigation, only the changing page segment re-renders; the layout remains static. |
| Component State | Client-side state (e.g., a search input value or a toggle) inside a layout is preserved. |
| Scroll Position | The scroll position within a layout (like a sidebar) is maintained. |
| Lifecycle Hooks | useEffect or useState inside a layout do not re-trigger when switching between sibling pages. |
Route Groups in Next.js
A Route Group is a folder naming convention using parentheses—for example, (auth) or (marketing)—that allows you to organize your route segments and layouts without affecting the URL path.
Primary Purposes
- Organized Hierarchy: Group related routes (e.g., all authentication pages) into a single folder to keep the app directory clean.
- Opt-in Layouts: Apply a specific layout.tsx to a group of routes while excluding others at the same level.
- Multiple Root Layouts: Create entirely different UI shells for different sections of the app (e.g., a (shop) layout vs. a (dashboard) layout) by creating multiple route groups at the top level, each with its own layout.tsx.
How the URL is Affected
The folder name in parentheses is completely omitted from the URL.
| Folder Structure | Resulting URL |
|---|---|
app/(auth)/login/page.tsx |
/login |
app/(auth)/register/page.tsx |
/register |
app/(marketing)/about/page.tsx |
/about |
Use Case: Scoped Layouts
Without Route Groups, any layout.tsx file automatically applies to all nested child routes. With Route Groups, you can isolate layouts:
-
Shared Logic: If you have
app/(admin)/layout.tsx, only the pages inside the(admin)folder will inherit that layout. -
Exclusion: Pages in
app/(user)/will not see the admin layout, even though both groups live at the same "level" in the file system.
Important Constraints