Blog Details
Irfanul Haque Sajid
18 Oct 2024
8 min read
What is Next.js?
Next.js is a powerful React framework that simplifies the process of building web applications, especially those that require server-side rendering (SSR) or static site generation (SSG). By providing a structured approach and built-in features, Next.js helps developers create high-performance, scalable, and SEO-friendly applications.
Brief history and development
Next.js was first released in October 2016 by Vercel (formerly Zeit), a company founded by Guillermo Rauch. The framework was born out of the need to simplify and improve React application development, particularly in areas where React alone fell short.
Key milestones in Next.js history:
Throughout its development, Next.js has consistently focused on improving developer experience, performance, and scalability, making it one of the most popular React frameworks in use today.
Next.js offers several compelling reasons for developers to choose it over vanilla React or other React-based frameworks:
Performance Optimization: Many performance optimizations are handled automatically by Next.js, saving developers time and effort.
1. File-based Routing
Next.js uses a file-system based routing mechanism. This approach simplifies the routing process by automatically creating routes based on the file structure in your project's pages directory. Here's how it works:
For example:
2. App Routing in Next.js
Next.js 13 introduced the App Router, which builds upon the file-based routing concept but offers more flexibility and powerful features.
Key features of the App Router:
Here's how it works:
Example of App Router structure:
app/
├── page.js
├── about/
│ └── page.js
├── blog/
│ ├── page.js
│ └── [slug]/
│ └── page.js
└── layout.js
Example Code Snippet for App Router:
// app/page.js
export default function Home() {
return <h1>Welcome to the Home Page</h1>
}
// app/about/page.js
export default function About() {
return <h1>About Us</h1>
}
// app/blog/page.js
export default function BlogIndex() {
return <h1>Blog Posts</h1>
}
// app/blog/[slug]/page.js
export default function BlogPost({ params }) {
return <h1>Blog Post: {params.slug}</h1>
}
// app/layout.js
export default function RootLayout({ children }) {
return (
<html lang="en">
<body>{children}</body>
</html>
)
}
3. Dynamic Routing in Next.js
Creating Dynamic Routes:
// pages/blog/[id].js
import { useRouter } from 'next/navigation';
function BlogPost() {
const router = useRouter();
const { id } = router.query;
return (
<div>
<h1>Blog Post: {id}</h1>
</div>
);
}
export default BlogPost;
Handling Parameters:
Use Cases:
What is code splitting?
Code splitting is a technique that breaks down large JavaScript bundles into smaller, more manageable chunks. These chunks are then loaded on demand, which can significantly improve the initial load time of your application.
How Next.js implements it automatically:
Next.js provides automatic code splitting out of the box. It achieves this through a few key mechanisms:
Benefits for performance:
Code Examples:
// pages/index.js
export default function Home() {
return <h1>Welcome to the home page!</h1>
}
// pages/about.js
export default function About() {
return <h1>About us</h1>
}
In this example, Next.js automatically creates separate bundles for the home and about pages. When a user visits the home page, only the code for that page is loaded.
Dynamic imports for component-level code splitting:
import dynamic from 'next/dynamic'
const DynamicComponent = dynamic(() => import('../components/heavy-component'))
export default function Page() {
return (
<div>
<h1>Page with dynamic import</h1>
<DynamicComponent />
</div>
)
}
Here, HeavyComponent is only loaded when the page renders, not during the initial page load.
Dynamic imports with custom loading component:
import dynamic from 'next/dynamic'
const DynamicComponentWithCustomLoading = dynamic(
() => import('../components/heavy-component'),
{
loading: () => <p>Loading...</p>
}
)
export default function Page() {
return (
<div>
<h1>Page with dynamic import and custom loading</h1>
<DynamicComponentWithCustomLoading />
</div>
)
}
This example shows how to display a custom loading state while the dynamic component is being loaded.
How SSR works in Next.js
In SSR, the HTML is generated on the server for each request. Next.js performs this automatically for every page, unless specified otherwise.
Example of SSR in Next.js:
// pages/ssr-example.js
export async function getServerSideProps() {
const res = await fetch('https://api.example.com/data')
const data = await res.json()
return { props: { data } }
}
export default function SSRPage({ data }) {
return <div>SSR Page with data: {data.title}</div>
}
In this example, getServerSideProps fetches data on every request, and the page is rendered on the server with this data.
When to use SSR : Use cases and benefits
Understanding SSG in Next.js
SSG generates HTML at build time, creating static pages that can be served quickly.
Example of SSG in Next.js:
// pages/ssg-example.js
export async function getStaticProps() {
const res = await fetch('https://api.example.com/static-data')
const data = await res.json()
return { props: { data } }
}
export default function SSGPage({ data }) {
return <div>SSG Page with data: {data.title}</div>
}
Here, getStaticProps fetches data at build time, and the page is pre-rendered with this data.
When to use SSG
SSG is ideal for content that doesn't change frequently and can be generated ahead of time.
Comparison between SSR and SSG:
1. Build Time vs. Request Time
2. Data Freshness
3. Performance
4. Use Cases
5. Scalability
Next.js also offers ISR, which combines benefits of both SSG and SSR:
// pages/isr-example.js
export async function getStaticProps() {
const res = await fetch('https://api.example.com/data')
const data = await res.json()
return {
props: { data },
revalidate: 60 // Regenerate page every 60 seconds
}
}
export default function ISRPage({ data }) {
return <div>ISR Page with data: {data.title}</div>
}
ISR allows you to update static pages after you've built your site, combining the benefits of SSG with the ability to serve fresh content.
API Routes
Creating and Using API Routes:
// pages/index.js
import { useState, useEffect } from 'react';
export default function Home() {
const [data, setData] = useState(null);
useEffect(() => {
const fetchData = async () => {
try {
const response = await fetch('/api/hello');
const data = await response.json();
setData(data);
} catch (error) {
console.error('Error fetching data:', error);
}
};
fetchData();
}, []);
return (
<div>
{data && <p>Hello, {data.name}!</p>}
</div>
);
}
// pages/index.js
import { useServerAction } from 'next/server';
export default function Home() {
const fetchData = useServerAction(async () => {
const response = await fetch('/api/hello');
return response.json();
});
const data = fetchData();
return (
<div>
{data && <p>Hello, {data.name}!</p>}
</div>
);
}
Key Benefits of Next.js Server Actions
Implementing Authentication:
1. Choose an authentication strategy:
2. Use a suitable authentication library:
3. Store authentication tokens:
4. Protecting Routes:
// pages/api/auth/[...nextauth].js
import NextAuth from 'next-auth';
import Providers from 'next-auth/providers';
export default NextAuth({
providers: [
Providers.GitHub({
clientId: process.env.GITHUB_ID,
clientSecret: process.env.GITHUB_SECRET,
}),
],
});
// pages/profile.js
import { getSession } from 'next-auth/react';
export default function Profile() {
return (
<div>
{/* Profile content */}
</div>
);
}
export async function getServerSideProps(context) {
const session = await getSession(context);
if (!session) {
return {
redirect: {
destination: '/login',
permanent: false,
},
};
}
return {
props: {
session,
},
};
}
Don’t worry, we don’t spam!