You can also find all 50 answers here π Devinterview.io - Next.js
Next.js stands out as a production-grade React framework, offering both speed and specialization. Its versatility, data fetching techniques, search engine optimization (SEO), and various deployment options make it a top choice for modern web development.
-
Server-Side Rendering (SSR): Next.js dynamically generates the HTML on the server for each page request, delivering consistently rendered content to users and ensuring optimal SEO.
-
Static Site Generation (SSG): By pre-computing the HTML of individual pages at the build time, Next.js achieves superior performance and security. It's especially adept at handling content-heavy websites.
-
Hybrid Rendering: Combining SSR and SSG, Next.js provides the flexibility to render specific pages during build time (SSG) and others on-demand (SSR). This feature is valuable when creating applications comprising both dynamic and static content.
-
Routing: Opt for either filesystem-based or dynamic routing. The former obviates the need for explicit route definition while the latter enables programmatic control. Both options synergize with SSG and SSR.
-
Code Splitting: Out of the box, Next.js identifies separate sections of your app and optimizes the bundling, dispatching only the necessary code (and resources) to clients, benefiting both performance and load times.
-
Formidable Image Optimization: Image components from Next.js automate image optimization, ensuring picture quality for varying devices while still upholding rapid load times.
-
Instant Deployment: Rely on services like Vercel for swift, straightforward deployments, A/B testing, and other advanced features.
-
API Routes: Embrace Next.js API routes for seamless incorporation of backend resources into your application.
-
Built-in CSS Support: With Next.js, you can utilize standard CSS files, CSS modules, or even preprocessors like Sass, all without additional setup.
-
Vast Plugin Ecosystem: Extend the functionality of your Next.js project by plugging into a wide array of solutions from the Next.js community.
Let's look at the key differences between Next.js and Create React App.
-
Next.js: Built-in server-side rendering (SSR) and client-side rendering (CSR). It also supports static site generation (SSG). Offers features like pre-fetching data to enhance performance. Provides integrated API routing.
-
Create React App: Primarily focuses on client-side rendering (CSR). Doesn't provide built-in server-side rendering or advanced rendering modes such as SSG or ISR. Relies on third-party solutions for server-side rendering.
-
Next.js: Offers file-system-based routing, which simplifies route definition. It also supports custom routes. Dynamic routing and code splitting are automatic, enhancing performance.
-
Create React App: Utilizes client-side routing with packages like React Router. Routes need to be explicitly defined in a configuration file or as components.
-
Next.js: Employs a zero-configuration model for quick setup. It comes with sensible defaults that promote best practices. Added configuration options can further customize its behavior.
-
Create React App: Similar to Next.js, it uses a zero-configuration approach. Customizations are managed via
react-scripts
and some advanced, but extensive,eject
configurations. This step irrevocably detaches from the default configuration, making it harder to integrate future updates.
-
Next.js: Integrates seamless serverless API routes. Developers can design API endpoints using standard HTTP requests. The result is a simplified backend setup without the need for server management or additional server logic.
-
Create React App: Leverages libraries like Axios or Fetch to interact with backend services. Unlike Next.js, it doesn't have a built-in solution for server-side API routes.
-
Next.js: Automatically code-splits imports across pages and components. This on-demand loading optimizes initial page load times. Furthermore, module-level or even granular control over code splitting are possible.
-
Create React App: Incorporates the
React.lazy
function andSuspense
components to facilitate code splitting. Developers need to identify points for code splitting manually.
-
Next.js: Offers numerous approaches for data retrieval, such as
getStaticProps
,getStaticPaths
,getServerSideProps
, and client-side methods. These functions are part of the Data Fetching API and are tailored for specific rendering strategies. -
Create React App: Lacks specialized data-fetching techniques inherent in Next.js. Instead, it integrates with data-fetching libraries or employs traditional strategies such as using
useEffect
in functional components.
-
Next.js: Provides flexible deployment options. With serverless hosting, deploys are efficient and scalable, especially suited for small to medium projects. Traditional hosting with a Node.js server allows for more extensive customizations.
-
Create React App: Most suitable for static or single-page applications, often deployed via content delivery networks (CDNs). For dynamic content, it needs to be paired with a backend like a REST API, and then deployed as two distinct applications.
-
Next.js: Offers superior support for search engine optimization (SEO). It covers foundational aspects like meta tags, but its advanced rendering modes, such as SSG and ISR, significantly benefit SEO, which is especially critical for content-heavy sites.
-
Create React App: Lacks built-in mechanisms like SSR or specialized rendering modes for optimizing SEO. Developers have to leverage third-party tools or implement their own solutions to achieve search engine friendliness.
To create a new Next.js application, you can utilize npx
in combination with the create-next-app
package.
npx create-next-app folder-name
Replace "folder-name" with your desired workspace directory. Here, npx
is a package runner that ensures you get the most recent version of create-next-app
. If you're on Node.js 10 or earlier, you need to install create-next-app
as a global package:
npm install -g create-next-app
create-next-app folder-name
This method, involving npm
and a global package, is not recommended for more recent Node.js versions.
Next.js handles Server-Side Rendering (SSR) through a simple and intuitive mechanism.
- Page: Represents the specific route that's being accessed.
- Route: A user-defined endpoint corresponding to a specific page or screen.
- App: The root of a Next.js application, managing the overall setup and state.
- Initial Request: When a user accesses a specific route or page, Next.js intercepts this request.
- Data Fetching: Any necessary page data is fetched, ensuring it's available before the page is rendered.
- SSR Render: Next.js renders the requested page on the server and sends the generated content, including any fetched data, as a response to the user's initial request.
Since page content and data are rendered on the server and sent in the initial response, users see meaningful content faster, even for dynamic, data-driven pages.
Here's the typical structure of a Next.js page:
import Head from 'next/head';
const SamplePage = ({ data }) => {
return (
<div>
<Head>
<title>Sample Page</title>
</Head>
<h1>Sample Content</h1>
<p>{data}</p>
</div>
);
};
export async function getServerSideProps() {
// Perform necessary data fetching
let data = 'Some server-rendered data';
return { props: { data } };
}
export default SamplePage;
In this example:
- The data-fetching function,
getServerSideProps
, ensures the data is available before the page is rendered. This method is specifically for Server-Side Rendering in Next.js. - The
data
prop, obtained from the data-fetching function, is passed to theSamplePage
component before its initial rendering. This permits immediate display of relevant data. - The
Head
component fromnext/head
encapsulates metadata specific to the page, such as the title.
After the initial server-side rendering, the client-side JavaScript takes over. This process involves:
- Page Navigation: Whenever the user navigates to a new page within the application.
- Client-Side Routing: The browser takes control over route changes, and Next.js manages the synchronization.
- Data Fetching: If required, Next.js triggers additional data fetching on the client side for subsequent state updates or dynamic content changes.
Here is the code:
import Link from 'next/link';
const Navigation = () => {
return (
<nav>
<Link href="/about">
<a>About</a>
</Link>
<Link href="/contact">
<a>Contact</a>
</Link>
</nav>
);
};
export default Navigation;
In this example:
- The
Link
component encapsulates anchor tags, ensuring user navigation within the application is efficient and considers both server-rendered content and any subsequent client-rendered updates.
- Initial Server Response: Delivers server-rendered content and necessary data in the first request, improving perceived loading times.
- Enhanced Dynamic User Experience: Seamlessly combines server-rendered content with client-side updates, optimizing page state and content.
The pages
directory serves as your primary route definition tool in a Next.js app. Each file within it represents a unique URL.
-
Automatic Routing: No custom routing is needed. Any file added to
pages
becomes a route. -
File-Route Mapping: The file structure under
pages
maps to specific routes. For instance,pages/post/index.js
is associated with/post
. -
Dynamic Routes: Files using square brackets like
[id].js
allow for dynamic parameters. For example,pages/post/[id].js
is tied to/post/:id
.
Consider the following file structure:
pages/
βββ index.js
β
βββ post/
β βββ [id].js
β
βββ about.js
This structure corresponds to the following routes:
-
/index.js: Serves as the landing page.
-
/post: Serves the
post
route. Should contain code to handle non-id
requests. -
/about.js: Direct route to the about page and is also treated as a route.
-
/post/abc:
- Maps to
pages/post/[id].js
withid
as "abc".
- Maps to
Some specific filenames or subdirectories have distinct routing roles.
404.js
: If present, Next.js uses this file to render a custom 404 page._app.js
: Sits at the top level. It is the master page that wraps other pages. Useful for including global styles or contexts._document.js
: Also on the top level, controls the server-rendered HTML document. Strong for continuous UI across pages.
In Next.js, you can configure server-side rendering for pages that always need fresh data. A good example is a real-time dashboard or a data-driven landing page.
- SSR: This sends the request to the server on every visit to fetch fresh data and generate the HTML response. This approach is often necessary if the data changes frequently.
- SG: All requests serve pre-built HTML, ideal for content that doesn't change upon every visit. However, SG could become inappropriate for certain use-cases where real-time data is a requirement.
To enable SSR in Next.js, follow these practical steps:
-
Configure Your Page's Component:
- Along with the standard static
getStaticProps
method, includegetServerSideProps
for server-side rendering. - This method runs on every request, making it suitable for dynamic data.
- Ensure you export this method within your page component.
- Along with the standard static
-
Using the Data:
getServerSideProps
should return the necessary data, as is common for data-fetching methods in Next.js.- Data will be available as props to your page component.
-
Deploy on a System that Supports SSR:
- Next.js delivers SSR out-of-the-box, which means you do not need to change any configurations to support this concern.
- For efficiency, you may utilize a caching mechanism to shape data that stays constant between subsequent requests.
Here is the code:
-
Page Component: Pages/ServerSide.js
export default function ServerSide({ time }) { return <p>Current time: {time}</p>; } export async function getServerSideProps() { return { props: { time: new Date().toISOString(), }, }; }
This code provides a
time
prop that updates on each request, showcasing server-side rendering. -
Linking in Your Application: ParentComponent.js
import Link from 'next/link'; export default function ParentComponent() { return ( <div> <Link href="/ServerSide">Live Time Page</Link> </div> ); }
Users see the updated time each time they access the "Live Time Page."
Next.js allows great flexibility in defining page components, supporting a variety of file extensions.
.js
: Traditional JavaScript..jsx
: React with JavaScript..ts
: TypeScript..tsx
: React with TypeScript.
.mdx
: MDX for markdown with JSX extensions, enabling interactivity.
.css
: Standard CSS..module.css
: CSS Modules for local scoping..scss
: SCSS, with its extended features and nesting.
.fetch.js
: Client-side data fetching..fetch.ts
: Client-side TypeScript data fetching.
_error.*
: Special filename used to define custom error pages.
- Page using
.jsx
and.scss
// myPage.jsx
import React from 'react';
import styles from './myPage.module.scss';
const MyPage = () => {
return <div className={styles.myPage}>Hello, SCSS!</div>;
};
export default MyPage;
- MDX file with React components and styling
// myMDXPage.mdx
import MyComponent from '../components/MyComponent';
import './myMDXPageStyles.css';
# My MDX Page
<MyComponent />
Environment variables in Next.js are used to configure deployment-specific values.
- Server: Required before app/web server starts, e.g., API keys.
- Build: For settings during the build process.
- Client: For global client-side use. Ensure no sensitive information here.
-
Create an
.env.local
file in the project's root, listing the key-value pairs, each on a separate line:DB_HOST=localhost DB_USER=myuser DB_PASS=mypassword
-
Access the defined variables in code using
process.env.<VARIABLE_NAME>
:console.log(process.env.DB_HOST);
-
Secure sensitive information on platforms like Vercel, by using their environment variable management tools.
-
Establishing Default Values:
By adding defaults in the code, Next.js ensures the app doesn't break if an environment variable is missing:
const dbHost = process.env.DB_HOST || 'localhost';
Automatic Static Optimization in Next.js enables pages to be automatically prerendered to static HTML as long as they do not fetch data from an external source.
This technique eliminates the need for runtime server rendering when a page only relies on client-side data.
-
Page Analysis: During build time, Next.js analyzes each page to determine if it fetches data. If not, the page is marked for static optimization. This often includes pages that only use internal state, client-side libraries, or context.
-
Prerendering: For pages marked as static, Next.js generates HTML files during the build process. These optimized pages are then served without requiring server-side rendering.
-
Data Revalidate: Static pages can contain stale data. Next.js uses a revalidate strategy to periodically refresh data. This is an ideal trade-off for many applications, balancing performance and data accuracy.
- Content Pages: Ideal for content-rich pages, such as blogs or marketing content, where the data doesn't change frequently and a slight delay in updates is acceptable.
- Marketing Campaigns: For promotional campaigns or one-time events, where real-time data isn't a priority and quick initial page loads are crucial.
-
Client Cache: Since individual users might be served cached static pages, client-side data can still become out-of-date. Techniques like incremental static regeneration (ISR) offer better freshness guarantees by updating pages at specified intervals.
-
Data Dependencies: Pages dependent on fresh, external data need server-side or hybrid rendering. Otherwise, they risk serving outdated content.
// pages/product/[id].jsx
// Using getStaticProps for static optimization
export async function getStaticProps({ params }) {
const product = await someFetchFunction(params.id);
return {
props: { product },
revalidate: 10, // Regenerate every 10 seconds for a more updated page
};
}
// If real-time data is an absolute must, use getServerSideProps instead
export async function getServerSideProps({ params }) {
const product = await someFetchFunction(params.id);
return { props: { product } };
}
function Product({ product }) {
// Render product details
}
File-based routing in Next.js simplifies the organization of web application pages. By adhering to specific file naming conventions in tandem with dedicated folders, developers can seamlessly structure their Next.js projects.
The presence of JavaScript or Markdown files under specific directories signals Next.js to configure pages within the application. For instance, navigational links can be established using file directory structure.
- The
pages
directory serves as the root where the application pages are located. - Secondary folders, such as
pages/blog
, represent sections within the application.
- For a standard page, use a
.js
or.jsx
extension, or.ts
/.tsx
for TypeScript. - For blog or documentation-like sections,
.mdx
(Markdown and React hybrid) can be employed, allowing content to render alongside components.
- Straightforward Navigation: The project's structure in the
pages
directory mirrors the website's structure. - Modularity: Sections are self-contained in separate directories, contributing to a more manageable and structured codebase.
- Isomorphism Support: The presence of both client- and server-side code fosters web applications that run on both the client and server, enhancing SEO and initial load times.
- Limited to Core Directories: Unique configurations might necessitate stepping beyond the capabilities of file-based routing, demanding additional setup.
- Directory Depth Complexity: Managing a large number of deeply nested directories can be challenging, potentially triggering comprehension or performance issues.
Consider a simple e-commerce website.
Below is the directory structure:
pages/
|__ index.js
|__ cart.js
|__ products/
|__ index.js
|__ [product-id].js
In the above structure, we see:
pages/index.js
: Serves as the landing page.pages/cart.js
: Represents the shopping cart accessible viayoursite.com/cart
.pages/products/index.js
: Defines the 'Products' landing page.pages/products/[product-id].js
: Corresponds to a product details page, accessible with the product's unique identifier, for example,yoursite.com/products/123
.
Dynamic Routes in Next.js allow you to render pages or components based on URL parameters. This feature is useful for scenarios involving user dashboards, blog posts, or product pages, where content is tied to specific, dynamic URLs.
-
File Naming Structure:
- For top-level dynamic routes, depending on whether it's a page or a client-only route, use
[param].js
or[param].client.js
respectively. For parameterized routes nested under a directory, the filename format is[[...slug]].js
.
- For top-level dynamic routes, depending on whether it's a page or a client-only route, use
-
Route and Query Parameters:
- To capture route parameters, use
[param]
for a single parameter or[[...slug]]
for chains of parameters. - Additionally, you can use the
useRouter
hook orrouter
object to extract query parameters like a typical URL query.
- To capture route parameters, use
Here is the Next.js and React code:
// pages/post/[id].js
import { useRouter } from 'next/router';
export default function Post() {
const router = useRouter();
const { id } = router.query;
return <div>Post: {id}</div>;
}
The URL /post/abc
would render "Post: abc".
-
Grouped Page Paths: To group a set of dynamic routes under a shared path segment, define a parameterized
pages/[category]/[id].js
page:- Inner pages like
/food/sandwich
,/drinks/cola
, and so on would be caught within that route.
- Inner pages like
-
Shallow Routing: Shallow routing, enabled with the
shallow
option ofrouter.push
ornext/link
, keeps query data when navigating:- For instance, when on
/view?user=48&page=1
and clicking on<Link href='/view?user=12'>
, the URL remains/view?user=12&page=1
.
- For instance, when on
Here is the Next.js and React code:
// pages/view.js
import Link from 'next/link';
export default function View() {
return (
<div>
<Link href={{ pathname: '/view', query: { user: 10 } }} shallow>
<a>User 10</a>
</Link>
</div>
);
}
- Visual Studio Code with the Path Autocomplete and vscode-react-docgen extensions streamlines route and query param management.
Next.js simplifies the process of handling dynamic routes and URL parameters. Here are the steps to access URL parameters in a dynamic route.
For instance, create a dynamic route as pages/post/[id].js
where id
denotes the unique identifier you'll extract as the parameter.
To access the parameter, use router.query
in your component file. Here is the code snippet:
import { useRouter } from "next/router";
const DynamicPost = () => {
const router = useRouter();
const { id } = router.query;
return <p>Post ID: {id}</p>;
};
export default DynamicPost;
- Fetching Content: Ideal for retrieving specific content from a database.
- Pagination: Useful for navigating through multiple pages of data.
- SEO Optimization: Offers cleaner and more descriptive URLs.
- Custom URL Structures: Great for creating custom URL structures catering to unique requirements.
The Link
component in Next.js simplifies client-side navigation within your application, enhancing performance and user experience.
-
Prefetching:
Link
automatically pre-caches linked pages that the user is likely to visit next. This optimizes load times during navigation. -
Accessibility: Assists with navigational cues for screen readers and keyboard users.
-
Code Splitting: Next.js employs this strategy by default, loading only the JavaScript code necessary for the specific page being visited.
-
Intelligent Routing: Handles link clicks gracefully, enhancing the user experience by providing polished navigational effects.
In the following Example.tsx
, you can see how Next.js handles code splitting under the hood.
import Link from 'next/link';
const Home = () => {
return (
<div>
<h1>Home</h1>
<Link href="/about">
<a>About Us</a>
</Link>
{/* Other navigational links */}
</div>
);
};
export default Home;
The AboutUs
component is only loaded when the user navigates to that specific route, showcasing effective code splitting:
const AboutUs = () => {
return (
<div>
<h1>About Us</h1>
{/* About Us content */}
</div>
);
}
export default AboutUs;
The Link
component excels for in-app navigations, particularly when:
- Directing users from one section of an app to another.
- Positive feedback is needed on user actions, such as in form submission success screens.
However, it's not the ideal choice for:
- Opening links in new browser tabs/windows.
- Managing dynamic state or side-effects.
Catch-All Routes in Next.js allow dynamic URL matching with flexible path parameters, empowering your app's routing system.
- Dynamic Parameters: Access route wildcard segments through the
...
rest parameter. - Matching Order: Definition order determines the route precedence.
- Fallback behavior: Optionally configure fallback settings.
Next.js routing file (pages/foo/[...bar].js
):
-
Root Catch-All: Load for any unmatched path or
/foo
route without further subpaths.export default function CatchAll() { // Logic for Root Catch-All }
-
Nested Catch-Alls: Triggered when the root path is accessed along with subdirectories.
export default function NestedCatchAll() { // Logic for Nested Catch-All }
-
Fallback Pages: Ideal for error handling or awaiting dynamic data.
export async function getServerSideProps() { // Data-fetching logic return { props: { data } }; } export default function Fallback({ data }) { return <div>{data}</div>; }
Specify fallback behavior in the getStaticPaths
method:
- True: Generates paths at build time and handles missing routes as fallbacks (SSG-only).
- False: Precisely predefines page paths during build (no fallback).
- 'blocking': Handles fallbacks via server at run-time (SSR or SSG).
getStaticProps
in Next.js allows you to pre-render a page at build time by fetching data from an API, database, or any other data source.
However, the static page isn't rebuilt until you trigger a redeployment. This makes getStaticProps
beneficial when content doesn't require frequent updates and can be cached for improved performance.
By choosing when content is updated based on a request or a rebuild, you can optimize your build and improve on tasks such as SEO.
-
Content Persistence: For content that doesn't change often, like a blog post, where you don't need to rebuild the page every time there's a change.
-
Data Fetching: When data is fetched from a headless CMS, internal API, external services, or databases that are not linked in real-time.
-
Performance: To benefit from caching and serve pages faster, especially for content that's accessed frequently.
Here is the JavaScript code:
export async function getStaticProps() {
// Fetch data from an API
const res = await fetch('https://example.com/data');
const data = await res.json();
// Pass data to the page via props
return {
props: { data },
// Re-generate the page every 60 seconds (optional)
revalidate: 60,
};
}
export default function MyStaticPage({ data }) {
// Render data on the page
// ...
}