Web Development
Next.js Questions
Comprehensive collection of Next.js interview questions covering fundamentals, the App Router, rendering strategies (SSR, SSG, ISR), and core components. Essential for modern web developers.
Next.js Basics25
Next.js is a powerful React-based framework that provides infrastructure for building performant, production-ready web applications. It offers built-in features like Server-Side Rendering, Static Site Generation, automatic code splitting, and optimized image handling. Developers use it because it simplifies SEO, improves initial load times, and handles complex routing out of the box.
Key features include file-system based routing, multiple rendering strategies (SSG, SSR, ISR, CSR), built-in API routes for backend logic, automatic image and font optimization, and middleware support. It also features 'Fast Refresh' for a superior developer experience and native TypeScript support for robust type-safe application development.
React is a JavaScript library used specifically for building user interfaces through components, whereas Next.js is a full-featured framework built on top of React. While React requires additional libraries for routing and state management, Next.js provides these integrated tools along with built-in server-side capabilities for better performance and SEO.
Create React App (CRA) is a tool for building Client-Side Rendered (CSR) single-page applications where the browser handles all rendering. Next.js, however, supports pre-rendering (SSR/SSG), allowing the server to generate HTML. Next.js is generally preferred for production because of its superior SEO, faster initial page loads, and built-in optimization features.
The easiest way to create a Next.js application is by using the 'create-next-app' CLI tool. You can run 'npx create-next-app@latest' in your terminal, which provides an interactive setup for naming your project, choosing between TypeScript or JavaScript, enabling ESLint, Tailwind CSS, and selecting the modern App Router architecture.
File-based routing is a system where the folder structure of your project automatically defines your application's routes. In the Pages Router, files in the 'pages' directory correspond to URLs (e.g., pages/about.js becomes /about). In the App Router, folders define segments, and a 'page.js' file within a folder makes that segment publicly accessible.
In the modern App Router, you create a new page by creating a new folder inside the 'app' directory and adding a 'page.js' file within it. In the legacy Pages Router, you simply add a new .js, .jsx, or .tsx file inside the 'pages' directory. The file or folder name determines the URL path.
The 'pages' directory is the legacy routing system for Next.js. Every file added to this directory automatically becomes a route based on its filename. It uses special functions like 'getStaticProps' and 'getServerSideProps' for data fetching. While still supported, it has been largely superseded by the more modular and efficient App Router.
The 'app' directory introduced the modern App Router architecture, built on React Server Components. It supports advanced features like nested layouts, streaming, and instantaneous loading states. It encourages a server-first approach, reducing client-side JavaScript by rendering components on the server by default and only hydrating interactive parts on the client.
The Pages Router is file-based and defaults to client-side components with pre-rendering. The App Router is folder-based and defaults to React Server Components. The App Router provides more granular control over layouts, supports data fetching directly within components using async/await, and enables improved performance through reduced bundle sizes and better streaming capabilities.
The 'Link' component from 'next/link' is a React component that extends the standard HTML anchor tag to provide client-side navigation. Unlike a regular <a> tag which triggers a full page reload, the Link component performs a 'soft' transition, only fetching the necessary data and preserving the application state, leading to a much faster user experience.
Programmatic navigation is handled using the 'useRouter' hook from 'next/router' (Pages Router) or 'next/navigation' (App Router). By calling 'router.push('/path')', you can trigger a navigation event in response to logic like form submissions or user interactions, providing a smooth, client-side transition without a full browser refresh.
The 'useRouter' hook allows you to access the router object inside your functional components. It provides access to the current route's pathname, query parameters, and methods for navigation. In the App Router, this has been split into more specific hooks like 'usePathname', 'useSearchParams', and 'useRouter' for better modularity.
The 'Image' component from 'next/image' is an extension of the HTML <img> element, evolved for the modern web. it provides built-in performance optimizations like automatic resizing for different devices, lazy loading by default to improve initial page load speed, and serving images in modern formats like WebP to reduce file sizes.
Using 'next/image' prevents Cumulative Layout Shift (CLS) by requiring dimensions, automatically optimizes images for different screen sizes, and implements lazy loading without extra code. It significantly improves Core Web Vitals scores by reducing unused bytes and ensuring that images only download when they are about to enter the viewport.
In the Pages Router, the 'Head' component from 'next/head' is used to append elements to the <head> of the HTML document. This is critical for SEO and accessibility, allowing you to set page-specific titles, meta descriptions, and link tags that search engines and social media platforms use to crawl your site.
In the App Router, you add meta tags using the 'metadata' object or 'generateMetadata' function in your layout or page files. In the Pages Router, you wrap your meta tags inside the 'Head' component. These tags are then injected into the final HTML during server-side rendering or static generation.
The '_app.js' file in the Pages Router is a special file used to initialize pages. It allows you to persist layouts between page changes, keep state when navigating, inject global CSS, and add custom error handling. It wraps every page in your application, making it the central point for global application logic.
The '_document.js' file in the Pages Router is used to augment your application's <html> and <body> tags. Since it only renders on the server, you can use it to add attributes like 'lang' to the html tag or inject external scripts and font links that need to be present before the React application hydrates.
While '_app.js' handles application-level logic like state and layouts that change on the client side, '_document.js' is strictly for server-side structure and initial HTML document setup. Code in '_app.js' runs on both server and client during hydration, while '_document.js' code only executes during the initial server-side render.
Global CSS can only be imported in the '_app.js' file (Pages Router) or the root 'layout.js' file (App Router). By importing a CSS file here, the styles will be applied to every page in your application. This is typically used for resetting styles, defining global variables, or setting up utility frameworks.
CSS Modules allow you to write CSS that is locally scoped to a specific component by default. By naming a file 'Component.module.css', Next.js will automatically generate unique class names during the build process. This prevents naming collisions and ensures that styles in one component do not accidentally leak into others.
To use Tailwind, you install it via npm along with 'postcss' and 'autoprefixer', then run 'npx tailwindcss init' to create configuration files. You then add the Tailwind directives to your global CSS file and ensure your 'tailwind.config.js' points to your app and components directories for content purging.
The 'public' folder is used to store static assets like images, robots.txt, sitemaps, and custom fonts. Any files placed in this directory are served from the root URL (e.g., public/favicon.ico is accessible at /favicon.ico). This folder is not processed by Webpack, so assets remain in their original form.
Static files are served by placing them in the 'public' directory at the root of your Next.js project. You can then reference these files in your code using a base path starting with '/', such as <img src='/logo.png' />. This is the standard way to handle assets that don't need dynamic processing.
Rendering25
Server-Side Rendering (SSR) is a rendering strategy where Next.js generates the HTML for a page on every single request. The server fetches the necessary data, renders the component, and sends the complete HTML to the browser. This ensures that users and search engine bots always see the most up-to-date content immediately.
Static Site Generation (SSG) is a pre-rendering method where the HTML is generated at build time. Once built, the static files are served to all users from a CDN. This is the most performant strategy because the server doesn't need to perform any computation during the request, leading to near-instant page loads.
Client-Side Rendering (CSR) is the traditional React approach where the server sends a minimal HTML shell and a large JavaScript bundle. The browser then executes the JavaScript to fetch data and build the UI. While highly interactive, it can lead to slower initial loads and poorer SEO compared to server-side strategies.
Incremental Static Regeneration (ISR) allows you to update static pages after you've built your site. By using the 'revalidate' property, Next.js can regenerate specific pages in the background as traffic comes in, ensuring that content stays fresh without requiring a full rebuild of the entire application.
SSG builds HTML once at build time for speed. SSR builds HTML on every request for dynamic data. CSR renders entirely in the browser. ISR is a hybrid that builds static pages but updates them periodically in the background. The choice depends on how often your data changes and your performance requirements.
Use SSG for content that rarely changes (like blogs or marketing pages). Use SSR for highly personalized or frequently changing data (like a user dashboard). Use ISR for large-scale sites where data updates periodically (like an e-commerce catalog), as it combines the performance of SSG with the freshness of SSR.
In the Pages Router, 'getStaticProps' is an asynchronous function used to fetch data at build time. It is used for Static Site Generation (SSG). By returning data in a 'props' object, Next.js ensures that the page is pre-rendered with that data, resulting in a fast, SEO-friendly static page.
In the Pages Router, 'getServerSideProps' is used to fetch data on every single request at runtime. It is the core of Server-Side Rendering (SSR). Use it when you need to fetch data that depends on the user's session or request headers, or when the data changes too frequently for static generation.
In the Pages Router, 'getStaticPaths' is used alongside dynamic routes to specify which paths should be pre-rendered at build time. For example, if you have a blog with multiple IDs, you return a list of those IDs so Next.js can generate a static HTML file for each post in advance.
The 'revalidate' property is an optional number in seconds that enables Incremental Static Regeneration (ISR). It tells Next.js how often it should attempt to re-generate the static page in the background after a request comes in, allowing static content to be updated without a site-wide redeploy.
Used in 'getStaticPaths', 'false' returns a 404 for missing paths. 'true' serves a fallback version (like a loading skeleton) immediately while generating the page in the background. 'blocking' makes the server wait until the page is fully generated before sending the HTML to the browser, appearing like SSR for the first visitor.
When 'fallback: true' is set, Next.js immediately serves a 'loading' version of the page. In the background, it runs 'getStaticProps' to generate the full page. Once complete, the browser swaps the fallback with the real content and caches the generated page for all future visitors to access instantly.
With 'fallback: 'blocking'', new paths that weren't generated at build time will cause the browser to wait (block) while the server generates the HTML. Once the page is rendered on the server, it is sent to the browser and cached. Subsequent requests for that same path will be served instantly as static files.
Yes, but you must also implement 'getStaticPaths' if the route has dynamic parameters (like [id].js). This tells Next.js which specific instances of that dynamic page need to be pre-rendered. Without 'getStaticPaths', the build will fail because the compiler won't know which values to use for the parameters.
You must return a plain JavaScript object that contains a 'props' key, which is itself an object containing the data your component needs. You can also return 'revalidate' for ISR, 'notFound: true' to trigger a 404 page, or 'redirect' to send the user to a different URL entirely.
The 'context' object provides information about the current request. It includes 'params' for dynamic routes, 'query' for URL parameters (SSR only), 'req' and 'res' for accessing headers and cookies (SSR only), and 'previewData' for handling CMS preview modes, enabling highly contextual and dynamic data fetching logic.
No, because 'getServerSideProps' runs exclusively on the server (Node.js environment). You cannot access browser-only APIs like 'window', 'document', or 'localStorage'. You should handle browser-specific logic within React hooks like 'useEffect' inside your component, which only executes once the page has reached the client.
React Server Components (RSC) are components that render exclusively on the server. They do not ship any JavaScript to the client, making them extremely fast and lightweight. They have direct access to backend resources like databases and file systems, and are the default component type in the App Router.
Client Components are standard React components that use the 'use client' directive. They are hydrated in the browser, allowing them to use interactive features like event listeners (onClick), browser APIs (localStorage), and React hooks (useState, useEffect). They are essential for parts of your UI that require user interaction.
Server Components stay on the server and have zero client-side bundle impact, but cannot use interactive hooks. Client Components are sent to the browser and allow for interactivity but increase the JavaScript bundle size. A good application uses Server Components for data-heavy sections and Client Components for small, interactive islands.
The 'use client' directive is a string at the very top of a file that signals to the Next.js compiler that this file and its imported dependencies should be treated as Client Components. This allows the component to use React hooks and browser APIs while still being part of a server-first architecture.
The 'use server' directive marks a function or file as a 'Server Action.' This allows the function to be called from a Client Component but executed exclusively on the server. It is primarily used for handling form submissions and data mutations without manually creating separate API route endpoints.
Use Server Components for data fetching, accessing backend resources, and rendering static content to minimize client-side JavaScript. Use Client Components only when you need interactivity, browser-specific APIs, or state management hooks. This 'server-first' approach ensures optimal performance and a smaller footprint for the end-user.
No, Server Components cannot use hooks because they are not hydrated in the browser and do not have a lifecycle or state in the traditional React sense. If you need to use state or effects, you must move that specific logic into a separate file marked with the 'use client' directive.
In Server Components, you fetch data directly using standard asynchronous 'fetch' calls inside an async function. Since the component runs on the server, you can use 'await' directly at the top level of your component. Next.js extends the native fetch API to provide automatic caching and data deduplication.
Intermediate Level - Routing20
Dynamic routes allow you to create pages where the URL contains custom parameters that are not known at build time. Instead of using static file names, you use bracket syntax like [id].js or [slug].js. This enables a single template to handle thousands of different pages based on data from an API or database.
To create a dynamic route, you add a folder (App Router) or file (Pages Router) with square brackets. For example, 'app/blog/[id]/page.js' creates a route for /blog/1, /blog/abc, etc. Inside the component, you can access the 'id' value to fetch specific content related to that unique identifier.
Catch-all routes extend dynamic routes to match multiple path segments. By using three dots inside brackets, like [...slug].js, the route will match /shop/clothes, /shop/clothes/tops, and /shop/clothes/tops/shirts. The 'slug' parameter is returned as an array containing all the matched URL segments in order.
Optional catch-all routes work like regular catch-all routes but also match the root path without any segments. While [...slug] requires at least one segment (e.g., /shop/items), [[...slug]] will also match /shop itself. In this case, the 'slug' parameter will be undefined for the root path.
In the App Router, route parameters are passed directly to the page component as a 'params' prop. In the Pages Router, you use the 'useRouter' hook and access them via 'router.query'. These parameters contain the keys and values defined in your dynamic folder or file names.
In the Pages Router, 'router.pathname' provides the actual path of the file in the pages directory (e.g., /blog/[id]). 'router.query' is an object containing the parsed dynamic parameters and query string values (e.g., { id: '1' }). Together, they help the developer determine both the logic and the state of the current route.
In the App Router, you use the 'useSearchParams' hook to get a read-only version of the query string. In the Pages Router, 'router.query' automatically includes these values. This allows you to handle filtering, search results, and pagination by reading values directly from the URL browser string.
Shallow routing allows you to change the URL without running data fetching methods like 'getServerSideProps' or 'getStaticProps' again. You receive the updated router object (pathname and query) via state without losing the component's state or triggering a server request, making it ideal for client-side filtering logic.
'router.push()' adds a new entry to the browser's history stack, allowing the user to use the 'back' button to return to the previous page. 'router.replace()' replaces the current history entry with a new one, which is useful for redirects or after successful form submissions where returning to the form is undesirable.
Nested routes are implemented by nesting folders within the 'app' directory (App Router) or files within subdirectories (Pages Router). In the App Router, you can also define 'layout.js' files at each level, which wrap all child segments, allowing for complex UIs with persistent sidebars and headers.
Route groups allow you to organize your route segments and layouts without affecting the URL path. By wrapping a folder name in parentheses, like '(marketing)', you can group related files together. This is useful for creating multiple root layouts or keeping the project structure clean without adding unnecessary segments to the public URL.
Parallel routes allow you to render one or more pages in the same layout that can be navigated independently. They are defined using 'slots' with the '@' prefix (e.g., @dashboard). This is perfect for complex dashboards or social feeds where multiple sections need to update without affecting the rest of the layout.
Intercepting routes allow you to load a route from another part of your application within the current layout. This is commonly used for the 'modal pattern,' where clicking an image opens a modal with a unique URL, but refreshing that URL directly loads the full-page version of that content.
Protected routes are created by checking for a user session either in a Server Component (App Router), in 'getServerSideProps' (Pages Router), or using Middleware. If the user is not authenticated, the application redirects them to a login page, ensuring sensitive data and UI are only accessible to authorized users.
These methods allow for programmatic navigation through the browser's history. 'router.back()' is equivalent to clicking the browser's back button, taking the user to the previous URL. 'router.forward()' moves them forward in the history stack. They provide a native-feeling navigation experience within the application's interactive components.
Next.js automatically prefetches pages linked with the 'Link' component as they enter the user's viewport. You can also manually prefetch a page using 'router.prefetch('/path')'. This background loading makes transitions near-instant because the data and code for the destination page are already present in the browser cache.
Automatic prefetching is a performance feature where the 'Link' component triggers a background request for the linked page's code and data when the link becomes visible in the viewport. This ensures that by the time a user actually clicks the link, the page is ready to be rendered immediately.
You can disable prefetching for a specific link by passing 'prefetch={false}' to the 'Link' component. This is useful for pages that are rarely visited, contain very heavy data, or to save bandwidth on mobile devices when pre-loading the content is not strictly necessary for a good user experience.
The 'scroll' prop determines whether the browser should scroll to the top of the page after navigation. It defaults to 'true'. By setting 'scroll={false}', you can maintain the user's current scroll position, which is useful for tabbed navigation or updates that shouldn't disrupt the user's focus on a specific section.
In the Pages Router, you create a custom '404.js' file in the 'pages' directory. In the App Router, you use a 'not-found.js' file within a route segment. These files allow you to provide a branded, user-friendly error message when a requested URL does not match any existing routes in your application.
Advanced Level - API Routes10
API routes allow you to build a backend API directly inside your Next.js project. They are server-side functions that handle HTTP requests and return JSON responses. This eliminates the need for a separate backend server for simple tasks like form processing, database queries, or interacting with third-party services.
In the Pages Router, you add a file to the 'pages/api' directory (e.g., api/user.js). In the App Router, you create a 'route.js' file inside a folder. These files export handler functions that receive request and response objects, allowing you to execute server-side logic securely.
The 'pages/api' directory is the dedicated location for API endpoints in the legacy Pages Router. Any file in this directory is mapped to '/api/*' and is treated as an API endpoint rather than a React page. These routes run on the server and do not increase the client-side bundle size.
In the Pages Router, you check 'req.method' inside a single handler function. In the App Router, you export named functions for each method (e.g., export async function GET() {}). This modular approach in the App Router provides better separation of concerns and improved readability for complex API logic.
In API routes, the request object ('req') contains a 'body' property for POST data and a 'query' property for URL parameters. In App Router Route Handlers, you use 'request.json()' to parse the body and the 'searchParams' API on the URL object to retrieve query strings efficiently.
Route Handlers are the App Router equivalent of API routes. They allow you to create custom request handlers for a given route using the Web Request and Response APIs. They are defined in 'route.js' files and support all standard HTTP methods, providing a modern, fetch-based approach to server-side logic.
The 'route.js' file is the special filename used to define an API endpoint in the App Router. It must be placed inside a folder, and it cannot exist in the same folder as a 'page.js' file. It exports asynchronous functions corresponding to HTTP methods to handle server-side requests.
CORS can be handled by setting the appropriate headers on the response object, such as 'Access-Control-Allow-Origin'. You can manually set these using 'res.setHeader' or use a middleware package. In the App Router, you return a standard 'Response' object with the required headers included in the init object.
Authentication is implemented by checking for cookies or authorization headers within the API handler. Often, libraries like NextAuth.js are used to simplify this process, providing helper functions like 'getServerSession' to verify the user's identity on the server before granting access to the requested data or functionality.
Yes, Next.js Middleware runs before every request, including API routes. You can use it to perform global tasks like authentication checks, logging, or rate limiting across all your endpoints. This provides a centralized way to handle security and request processing before the specific API handler is even invoked.
Advanced Level - Middleware & Configuration15
Middleware allows you to run code before a request is completed. It can modify the response by redirecting the user, rewriting the URL, or adding custom headers. It runs on the Edge Runtime, making it extremely fast and ideal for tasks like bot detection, A/B testing, and authentication guarding.
To create middleware, you add a 'middleware.js' (or .ts) file at the root of your project or inside the 'src' directory. This file exports a default 'middleware' function that receives the 'NextRequest' object. You can then use conditional logic or a 'config' object to determine which routes it should target.
The 'middleware.js' file is the special file that Next.js looks for to execute custom logic before requests are processed. It must export a function named 'middleware'. Because it runs on the Edge, it has limited access to Node.js APIs but provides high performance for request manipulation and security checks.
In middleware, you can read and set cookies, manage redirects, rewrite URLs for internationalization or A/B testing, and verify JWT tokens for authentication. You can also intercept requests to serve static files from different locations or block specific IP addresses, providing a powerful layer of control over the request lifecycle.
You protect routes by checking for an authentication token or session cookie inside the middleware function. If the token is missing or invalid, you use 'NextResponse.redirect' to send the user to a login page. By using the 'config' matcher, you can apply this protection to specific sub-paths like '/admin/:path*'.
The 'next.config.js' file is the primary configuration file for Next.js. It allows you to customize the Webpack build process, set up environment variables, configure redirects and rewrites, enable experimental features, and manage image optimization settings. It exports a JavaScript object that the Next.js server and build tools use.
Environment variables are configured using '.env' files in the root directory. You can use '.env.local' for local secrets and '.env.production' for live settings. Next.js automatically loads these variables into 'process.env', making them accessible in server-side functions like 'getStaticProps' or App Router Server Components securely.
'.env.local' is used for local development and is typically ignored by Git to protect sensitive keys. '.env.production' is used to define default values for the production build. While '.env.local' can override settings in any environment, it is best practice to use it only for local testing and secrets.
By default, environment variables are only available on the server. Prefixing a variable with 'NEXT_PUBLIC_' (e.g., NEXT_PUBLIC_API_URL) tells Next.js to inline the value into the JavaScript bundle sent to the browser. This makes the variable accessible in client-side code while keeping other secrets safely on the server.
Redirects are configured in 'next.config.js' using the 'redirects' key. It accepts an array of objects specifying the 'source', 'destination', and 'permanent' (boolean) status. This is handled at the server level, providing a SEO-friendly way to move users from old URLs to new ones automatically.
Rewrites are configured in 'next.config.js' and act like a proxy. They map a source URL to a destination without changing the URL shown in the browser. This is useful for masking legacy APIs or serving content from different services while keeping the user on your main domain.
A redirect tells the browser to go to a new URL and changes the address shown in the search bar (HTTP 301/302). A rewrite fetches the content from a different location but keeps the original URL visible to the user. Redirects are for navigation; rewrites are for internal routing and proxying.
Custom headers are added via 'next.config.js' using the 'headers' function. You define a pattern (source) and an array of header objects (key/value). This is commonly used for security policies (CSP), cache control, or adding custom identification headers to all responses from your server.
The 'basePath' allows you to serve your application under a sub-path of a domain (e.g., /docs). When configured in 'next.config.js', all links and assets in the application are automatically prefixed with this path, making it easier to host multiple applications on a single domain.
In the Pages Router, you use the 'i18n' object in 'next.config.js' to define locales and the default locale. In the App Router, i18n is typically handled by using route groups and dynamic segments (e.g., app/[lang]/page.js) alongside middleware to detect and set the user's preferred language.
Advanced Level - Optimization & Performance5
Next.js optimizes images by automatically resizing them, compressing them, and serving them in modern formats like WebP or AVIF. This happens on-demand via an image optimization API, ensuring that users only download the specific size needed for their device, which improves performance and reduces data usage.
Essential props include 'src', 'alt', 'width', and 'height' (for non-fill images). Optional props include 'layout' (legacy), 'fill', 'sizes', 'priority' (for LCP images), 'quality', and 'placeholder' (for blur-up effects). These props give developers fine-grained control over how images load and behave across different breakpoints.
The 'priority' prop should be added to images that are considered the Largest Contentful Paint (LCP) element. It tells Next.js to prioritize the loading of that image by adding a preload tag to the HTML head, ensuring it loads as fast as possible for better perceived performance.
The 'loading' prop determines the browser's loading strategy. By default, Next.js images use 'lazy' loading, which delays downloading the image until it is near the viewport. Setting it to 'eager' forces an immediate download, which is generally only recommended for images visible at the very top of the page.
Automatic code splitting means that Next.js breaks your application into small chunks based on pages. Instead of a single massive JavaScript file, users only download the code required for the specific page they are visiting. This reduces initial load times and ensures that the application remains fast as it grows.
Optimization & Performance15
Next.js optimizes bundles through automatic code splitting, minification using Terser, and tree-shaking to remove unused code. It also utilizes a 'Shared Chunks' strategy where common dependencies across different pages are extracted into a single file, allowing the browser to cache them effectively and reduce redundant downloads.
The 'next/dynamic' function is a specialized version of React.lazy that allows for the dynamic loading of components. It enables you to import components only when they are needed, which is essential for reducing the initial JavaScript payload for heavy components like maps, editors, or complex charts.
You implement lazy loading by wrapping your import statement with 'dynamic(() => import('./Component'))'. This ensures that the component's code is split into a separate chunk. You can also provide a 'loading' option to display a skeleton or spinner while the component is being fetched in the background.
The 'ssr: false' option tells Next.js to only render the dynamic component on the client side. This is crucial for components that rely on browser-only APIs like 'window' or 'document' (e.g., a canvas-based library), preventing errors during the server-side rendering phase of the application.
The 'next/font' module automatically optimizes your fonts (including Google Fonts) by self-hosting them. It downloads the font files at build time and serves them from your same domain, eliminating external network requests. It also prevents layout shifts by using the CSS 'size-adjust' property for fallback fonts.
The 'next/script' component is an extension of the HTML <script> tag that allows you to manage the loading priority of third-party scripts. It provides built-in strategies to ensure that external scripts (like analytics or ads) do not block the main thread or negatively impact your page's performance scores.
Next.js offers several strategies: 'beforeInteractive' (loads before hydration), 'afterInteractive' (default, loads after hydration), 'lazyOnload' (loads during idle time), and 'worker' (experimental, offloads to a web worker). Choosing the right strategy is vital for balancing functionality with fast Time to Interactive (TTI) metrics.
The Node.js Runtime is the standard environment with access to all Node.js APIs. The Edge Runtime is a lightweight, high-performance environment based on Web APIs. Use the Edge Runtime for Middleware or simple API logic to achieve lower latency and faster startup times across global deployments.
Streaming allows you to progressively break down your page's HTML into smaller chunks and send them from the server to the client as they become ready. This allows users to see parts of the page almost immediately, rather than waiting for the entire page's data to be fetched before any rendering begins.
React Suspense is the mechanism that enables Streaming in Next.js. By wrapping a component in <Suspense>, you can show a fallback UI (like a loading state) while that component is performing an asynchronous operation. This allows for more granular and interactive loading experiences without blocking the entire page.
The 'loading.js' file is a special file in the App Router that automatically creates a React Suspense boundary for a route segment. It allows you to define an immediate loading state (like a skeleton) that users see while the page's data is being fetched, improving perceived performance.
The 'error.js' file is a special file used to define a UI error boundary for a route segment. It catches unexpected runtime errors in its child components, allowing you to display a graceful fallback UI and provide a 'reset' functionality to attempt recovery without a full page refresh.
Errors in Server Components are caught by the nearest 'error.js' boundary in the file hierarchy. Because Server Components run on the server, these errors are captured during the rendering phase. You can log these errors on the server while presenting a user-friendly error message to the client through the error component.
The 'not-found.js' file is used to render a custom UI when the 'notFound()' function is triggered within a route segment or when a URL doesn't match any routes. It provides a more integrated way to handle 404 scenarios within the App Router compared to the legacy pages/404.js.
In the Pages Router, you create a '500.js' file in the 'pages' directory. In the App Router, you typically use a global 'error.js' file in the root directory to catch and display custom UI for server-side errors, ensuring your application maintains its branding even during critical failures.
Data Fetching & Caching15
Next.js implements four layers of caching: the Request Memoization (per-request), the Data Cache (persistent across requests), the Full Route Cache (at build time), and the Router Cache (client-side). This sophisticated system ensures that data and HTML are reused whenever possible to minimize server load and latency.
In Next.js, the native Web 'fetch' API is extended to support server-side caching and revalidation. You can use it in Server Components to fetch data with fine-grained control over how that data is stored and refreshed, making it the primary tool for data management in the App Router.
'force-cache' (default) tells Next.js to store the data indefinitely in the Data Cache. 'no-store' bypasses the cache entirely for every request (equivalent to SSR). 'revalidate' (a number in seconds) enables ISR by setting a time-to-live for the cached data before it is refreshed in the background.
'force-cache' is designed for static data that doesn't change often, resulting in high performance. 'no-store' is for dynamic data that must be fresh on every request (e.g., banking transactions). Using 'no-store' effectively turns that specific route or component into a dynamically rendered entity.
The 'revalidate' option in a fetch request specifies the frequency (in seconds) that Next.js should wait before checking if the data needs an update. If a request comes in after the interval, Next.js serves the old data while simultaneously re-fetching the fresh data in the background.
The 'revalidatePath()' function is a server-side utility that allows you to manually purge the cached data for a specific URL path. This is commonly used inside Server Actions after a database update to ensure that the user sees the latest changes immediately across all affected pages.
The 'revalidateTag()' function allows you to purge cache entries based on a custom tag rather than a URL path. By tagging your fetch requests (e.g., tags: ['products']), you can invalidate specific groups of data across your entire application with a single call, providing highly precise cache control.
On-demand revalidation is implemented using 'revalidatePath' or 'revalidateTag' within a Server Action or an API Route Handler. This allows you to update static content as soon as a data mutation occurs (like a new CMS post), rather than waiting for a time-based interval to expire.
Server Actions are asynchronous functions that run on the server but are called directly from the client. They are integrated with React and Next.js to handle data mutations, form submissions, and state updates securely, significantly reducing the amount of boilerplate code needed for client-server communication.
To create a Server Action, you define an 'async' function and add the '"use server"' directive at the top of the function body or the file. You can then pass this function to form 'action' props or call it inside event handlers in Client Components directly.
You pass the Server Action directly to the 'action' attribute of a standard HTML <form>. Next.js handles the submission, automatically serializes the form data into a 'FormData' object, and manages the network request, providing a seamless experience even if JavaScript is slow to load.
The 'useFormStatus' hook is a React hook used within child components of a form to access information about the current submission state. It provides properties like 'pending', which allows you to disable buttons or show loading indicators while a Server Action is being processed.
The 'useFormState' (now 'useActionState' in newer versions) hook allows you to track the result of a Server Action (like success messages or validation errors). It takes an action and an initial state, returning the current state and a wrapped version of the action to be used in a form.
Mutations are handled by executing database logic inside the Server Action and then calling 'revalidatePath' or 'revalidateTag' to update the UI. You can also use 'redirect' to send the user to a new page once the mutation is complete, ensuring the local cache is always consistent with the database.
API Routes are standard HTTP endpoints intended for generic use (including external consumers). Server Actions are deeply integrated with React, allowing for direct function calls from the UI, automatic form integration, and native cache invalidation, making them the preferred choice for internal application data mutations.
SEO & Metadata10
SEO is implemented by providing metadata like titles and descriptions for every page. In the App Router, you export a 'metadata' object or 'generateMetadata' function. Next.js handles injecting these into the HTML head, along with standard SEO features like automatic sitemap generation and image optimization.
The 'metadata' object is a static configuration object exported from a layout or page file. It supports standard tags like title and description, as well as Open Graph, Twitter, and icons. It allows for a declarative and hierarchical way to manage SEO across nested routes in your application.
The 'generateMetadata()' function is an asynchronous function used to dynamically generate metadata based on route parameters or external data. For example, you can fetch a blog post by its ID and use the post's title as the page title, ensuring highly specific SEO for every dynamic page.
Dynamic metadata is implemented using 'generateMetadata'. Inside this function, you can perform data fetching (Next.js will deduplicate the request if it's also used in the page). You then return a metadata object containing dynamic titles, descriptions, and custom Open Graph images specific to the current content.
Open Graph tags are added within the 'metadata' object or returned from 'generateMetadata' under the 'openGraph' key. You can specify the title, description, URL, and images that social media platforms like Facebook and LinkedIn use when your website link is shared by users.
Similar to Open Graph, Twitter Card tags are defined under the 'twitter' key in your metadata configuration. You can choose the card type (e.g., 'summary_large_image'), title, description, and images specifically formatted for Twitter's link previews to improve engagement and click-through rates from social traffic.
Next.js can automatically generate a 'robots.txt' file by adding a 'robots.js' or 'robots.ts' file to the root of your App directory. This allows you to dynamically define which parts of your site search engine crawlers are allowed to access based on your environment or database rules.
Next.js supports automatic 'sitemap.xml' generation via a 'sitemap.js' file in the App directory. This file exports a function that returns an array of URLs, allowing you to programmatically include all your static and dynamic routes (like blog posts or products) for better search engine indexing.
Structured data is implemented by including a <script> tag with 'type="application/ld+json"' inside your Page or Layout component. You can pass a JSON object containing schema.org metadata, which helps search engines understand the content type (e.g., Product, Article, or Event) and display rich snippets.
Canonical URLs are handled in the metadata object using the 'alternates' key. You specify the canonical link to tell search engines which version of a page is the primary one, which is essential for preventing duplicate content issues when the same page is accessible via multiple URLs.
Authentication & Security10
Authentication is typically implemented using libraries like NextAuth.js or Clerk. These provide pre-built providers for OAuth (Google, GitHub), email/password login, and session management. In the App Router, you can check sessions on the server to protect routes and securely handle user data during rendering.
NextAuth.js is the most popular open-source authentication library for Next.js. It is designed to work seamlessly with both Pages and App Routers, providing built-in support for multiple OAuth providers, database persistence, and secure cookie-based session management without the need for a separate backend authentication server.
Providers are configured in the NextAuth configuration file (api/auth/[...nextauth].js or route.js). You import the desired providers (like Google or GitHub), provide your Client ID and Client Secret from the provider's developer console, and define optional callbacks to customize the user session or profile data.
Pages are protected by checking for a session at the top level. In Server Components, you use 'getServerSession' and redirect if null. In Client Components, you use the 'useSession' hook. Alternatively, you can use Middleware to intercept requests to specific paths and redirect unauthenticated users globally.
JWT (JSON Web Token) authentication is implemented by creating a token during login and storing it in an HTTP-only cookie. Next.js can then decode and verify this token in Middleware or Server Components to authorize requests, providing a stateless and scalable way to manage user sessions.
The most secure way to store authentication tokens in Next.js is in 'HTTP-only', 'Secure' cookies. This prevents client-side JavaScript from accessing the token, which mitigates Cross-Site Scripting (XSS) risks. You should avoid 'localStorage' for sensitive tokens as it is vulnerable to malicious script access.
HTTP-only cookies are cookies that cannot be accessed through the 'document.cookie' API in JavaScript. They are only sent to the server with HTTP requests. Using these for session IDs or JWTs is a critical security best practice to protect user sessions from being stolen by XSS attacks.
Next.js provides built-in CSRF protection for Server Actions by checking the origin header. For standard API routes, you can use libraries like NextAuth.js which automatically include CSRF tokens in requests. It is also important to use 'SameSite' cookie attributes to prevent unauthorized third-party site requests.
Security headers are HTTP response headers that enhance application security. In Next.js, you configure these in 'next.config.js' under the 'headers' key. Important headers include Content Security Policy (CSP), Strict-Transport-Security (HSTS), and X-Frame-Options to prevent clickjacking and other common web vulnerabilities.
XSS is prevented by React's default behavior of escaping content and by using a strong Content Security Policy (CSP) in 'next.config.js'. Additionally, using HTTP-only cookies for tokens and avoiding 'dangerouslySetInnerHTML' without proper sanitization (using libraries like DOMPurify) are essential steps for protecting user data and sessions.
Expert Level - Deployment & Build10
To build for production, you run the command 'next build'. This process optimizes your application by compiling the code, generating static pages (SSG), minifying JavaScript, and creating a highly optimized bundle. The resulting output is stored in the '.next' folder, ready to be served by a production server.
'next dev' starts a local development server with hot-module replacement. 'next build' creates an optimized production build of your application. 'next start' launches the production server using the pre-built files from the build step. You must always run build before you can run start in a production environment.
You analyze bundle size using the '@next/bundle-analyzer' plugin. When enabled, it generates a visual treemap during the build process, showing the size of each dependency and page. This helps developers identify 'heavy' libraries that should be dynamic imports or replaced with lighter alternatives to improve load times.
This is an official Next.js plugin that integrates the Webpack Bundle Analyzer. It allows you to see which modules are taking up the most space in your client-side JavaScript bundles. By analyzing these reports, you can optimize your application's performance by implementing code splitting and tree-shaking effectively.
Vercel is the creator of Next.js and provides the most seamless deployment experience. You simply connect your GitHub, GitLab, or Bitbucket repository to Vercel. Every time you push code, Vercel automatically runs 'next build' and deploys your application to a global edge network with built-in CI/CD and preview environments.
Yes, Next.js can be deployed anywhere that supports Node.js. On AWS, you can use Amplify, Elastic Beanstalk, or Lambda (via OpenNext). On Netlify, a dedicated adapter handles features like ISR. For custom servers, you run 'next build' followed by 'next start' on a virtual machine or containerized environment.
Static export is a configuration mode where Next.js generates a purely static HTML/CSS/JS version of your site. This allows you to host the application on any static web server (like S3 or Nginx) without needing a Node.js runtime, though it disables features like SSR and API routes.
The primary limitations include the lack of dynamic server-side logic. You cannot use 'getServerSideProps', API Routes, Middleware, or advanced App Router features like Server Actions and dynamic Image Optimization. All data must be available at build time, and routing must be handled entirely on the client side.
To deploy with Docker, you create a multi-stage Dockerfile. The first stage installs dependencies and builds the project. The second stage uses a lightweight Node.js image and copies only the necessary production files (like the .next and public folders), ensuring a small, secure, and portable container image.
The 'standalone' mode is a build configuration that automatically traces all files needed for production, including 'node_modules'. It creates a minimal server.js file that can be run independently, significantly reducing the size of Docker images and simplifying deployment on serverless platforms or custom orchestration systems.
Expert Level - Advanced Patterns15
The 'layout.js' file defines a UI that is shared across multiple pages in a route segment. Unlike pages, layouts do not re-render on navigation, allowing you to maintain state (like search inputs) and provide persistent elements like navigation bars and footers across your entire application.
Nested layouts occur when a folder structure has multiple 'layout.js' files. A child layout is wrapped by its parent layout. For example, a root layout handles the <html> and <body> tags, while a nested 'dashboard' layout handles a sidebar that only appears within dashboard-related pages.
Route groups allow you to apply specific layouts to a set of routes without affecting the URL structure. By placing folders in parentheses, e.g., '(auth)', you can give login and signup pages a unique shared layout while keeping them accessible at '/login' and '/signup' instead of '/auth/login'.
A 'template.js' file is similar to a layout but creates a new instance for each child on navigation. While layouts persist state and avoid re-renders, templates re-synchronize and re-mount. They are useful for features that require fresh state on every visit, such as entrance animations or per-page feedback forms.
Layouts are persistent and do not re-render when navigating between sibling routes, making them more performant for shared UI. Templates re-mount on every navigation, which means their state is reset and effects (like 'useEffect') are re-run. Use layouts by default unless you specifically need the re-mounting behavior.
In the App Router, you cannot pass props directly from a layout to a page. Instead, you should fetch the data in both components. Because Next.js automatically deduplicates fetch requests, the data is only requested once from the source, ensuring efficiency while maintaining the decoupling of layouts and pages.
Loading UI is implemented using 'loading.js', which leverages React Suspense to show a fallback during data fetching. Streaming allows the server to send HTML in chunks. This means the layout can be sent to the client immediately while the slower, data-heavy parts of the page are still being rendered.
Parallel routes allow you to render multiple pages simultaneously in the same layout. Slots are defined using the '@' prefix in folder names. They are passed as props to the layout, allowing you to build complex interfaces like dashboards where different sections have their own independent navigation and loading states.
Modal routes are implemented using 'intercepting routes' (e.g., (..)folder). This allows you to 'intercept' a navigation and show a modal instead of a full page. If the user refreshes or shares the link, Next.js will render the full-page version, providing a seamless and accessible user experience.
The 'default.js' file serves as a fallback UI for a parallel route slot when Next.js cannot recover the slot's state from the current URL. It ensures that your layout doesn't break or show a 404 when navigating between routes that don't share all the same parallel slots.
Next.js handles race conditions by using the native Web 'Request' object and internal memoization. When multiple requests for the same data are made simultaneously, Next.js ensures only one network call is made. For client-side fetching, using libraries like S3 or React Query with proper 'key' management is recommended.
RSC caching (Request Memoization) is a feature where React caches the result of 'fetch' requests within a single server render pass. This allows you to call the same 'fetch' function in multiple components throughout your tree without worrying about duplicate network requests or passing data through props.
Optimistic UI is implemented using the 'useOptimistic' hook. It allows you to immediately update the interface to show the expected result of an action (like adding a comment) while the Server Action is still processing in the background, providing a much faster and more responsive user experience.
The 'unstable_cache' API is a utility that allows you to manually cache the results of expensive operations, such as direct database queries or complex calculations, that do not use the standard 'fetch' function. It integrates with the Next.js Data Cache to provide consistent revalidation and tagging capabilities.
Debugging Next.js involves using the browser's DevTools for client-side issues and Node.js debuggers for server-side code. You can use 'console.log' in Server Components to see output in your terminal, and utilize the 'next dev' overlay to identify hydration mismatches, routing errors, and data fetching bottlenecks in real-time.