Mastering Next.js for Full-Stack Development
Next.js Rendering Strategies
Rendering Strategies in Next.js
Next.js offers a powerful advantage over traditional React applications by pre-rendering pages. Instead of sending an empty HTML file for the browser to populate, Next.js generates the HTML in advance. This leads to better performance and improved search engine optimization (SEO).
The key is that you can choose how and when this pre-rendering happens on a per-page basis. There are three main strategies: Static Site Generation (SSG), Server-Side Rendering (SSR), and a hybrid approach called Incremental Static Regeneration (ISR).
Static Site Generation (SSG)
With SSG, the HTML for a page is generated once at build time—when you deploy your application. When a user requests the page, the server simply sends this pre-built static file. This process is incredibly fast because no computation happens on the server at request time. The files can be cached on a Content Delivery Network (CDN) and served to users from a location near them.
To implement SSG for a page, you export an async function called getStaticProps. This function runs at build time. It fetches any data the page needs and passes it as props to your page component.
// pages/posts/[id].js
export async function getStaticProps(context) {
const { id } = context.params;
// Fetch data for a single post based on the id
const res = await fetch(`https://api.example.com/posts/${id}`);
const post = await res.json();
// The value of the `props` key will be
// passed to the `Post` component
return {
props: { post },
};
}
function Post({ post }) {
// Render post...
return <h1>{post.title}</h1>;
}
But what if the page has a dynamic route, like a blog post with a specific ID? You can't know every possible blog post ID at build time. For this, you use getStaticPaths. This function tells Next.js which paths to pre-render for a dynamic route.
// pages/posts/[id].js
// This function gets called at build time
export async function getStaticPaths() {
// Call an external API endpoint to get posts
const res = await fetch('https://api.example.com/posts');
const posts = await res.json();
// Get the paths we want to pre-render based on posts
const paths = posts.map((post) => ({
params: { id: post.id },
}));
// We'll pre-render only these paths at build time.
// { fallback: false } means other routes should 404.
return { paths, fallback: false };
}
Use SSG for content that doesn't change often, like blog posts, documentation, marketing pages, and portfolio sites. It provides the best performance.
Server-Side Rendering (SSR)
Sometimes, generating HTML at build time isn't enough. If your page displays data that changes frequently, you need SSR. With this strategy, the HTML is generated on the server for every single request.
While this is slower than SSG because the server has to do work each time, it ensures the user always sees the most up-to-date content. This is crucial for pages where data must be fresh.
To use SSR, you export an async function called getServerSideProps from your page. It works similarly to getStaticProps, but it runs on every request instead of at build time.
// pages/dashboard.js
export async function getServerSideProps(context) {
// Fetch data from an external API on every request
const res = await fetch(`https://api.example.com/user/profile`);
const data = await res.json();
// Pass data to the page via props
return { props: { data } };
}
function Dashboard({ data }) {
// Render user-specific dashboard data...
return <div>Welcome, {data.name}</div>;
}
Use SSR for pages that require live data, such as user dashboards, stock tickers, or e-commerce pages with rapidly changing inventory.
Incremental Static Regeneration (ISR)
ISR offers a middle ground between the speed of SSG and the freshness of SSR. It allows you to update static pages after they have been built, without needing to redeploy your entire site.
It works by adding a revalidate property to the object returned by getStaticProps. This property sets a time in seconds. When a request comes in after this time has passed, two things happen:
- The user is immediately served the old, cached (stale) version of the page.
- In the background, Next.js re-generates the page with fresh data.
The next user to visit the page will get the newly generated version. This strategy is often called "stale-while-revalidate."
// pages/products/[id].js
export async function getStaticProps(context) {
const { id } = context.params;
const res = await fetch(`https://api.example.com/products/${id}`);
const product = await res.json();
return {
props: {
product,
},
// Next.js will attempt to re-generate the page:
// - When a request comes in
// - At most once every 60 seconds
revalidate: 60, // In seconds
};
}
function Product({ product }) {
// Render product details...
return <h1>{product.name} - ${product.price}</h1>;
}
Use ISR for content that should be fresh but doesn't need to be real-time, like popular e-commerce product pages, news sites, or social media feeds.
| Strategy | When HTML is Generated | Use Case | Performance |
|---|---|---|---|
| SSG | At build time | Blogs, Docs, Marketing Pages | Fastest |
| SSR | On every request | User Dashboards, Live Data | Slower, server-intensive |
| ISR | Build time + periodically | E-commerce, News Sites | Fast, with fresh data |
Choosing the right rendering strategy is a key part of building high-performance Next.js applications. By understanding the trade-offs, you can select the best approach for each page, delivering a fast and dynamic experience for your users.
What is a primary advantage of Next.js's pre-rendering feature compared to a standard client-side rendered React application?
You are building a documentation site where the content is updated infrequently. To ensure the fastest possible response time for users, which pre-rendering strategy is most appropriate?