When building web applications with React.js, developers often face the challenge of choosing the right way to render their pages: Should the HTML for each page be pre-built ahead of time, or should it be generated on the fly for each request? This question directly affects the speed, scalability, user experience, and technical complexity of your application.
Next.js, a popular React.js framework, introduces two powerful rendering paradigms—Static Generation (SG) and Server Side Rendering (SSR). Mastering these techniques is crucial for building high-performance applications, understanding system design trade-offs, and deploying scalable web services (even in Docker environments or integrated with backends like Django APIs). This article provides a detailed technical guide on how both methods work in Next.js, with real-world examples, practical trade-offs, and illustrative code snippets you can use right away.
Static Generation—sometimes called Static Site Generation (SSG)—is a rendering technique where pages are created as plain HTML files at build time. In simple terms, when you run next build, Next.js generates HTML for every statically generated page, prepares them as files on your server, and directly serves those files when users visit your site.
next build). All HTML is generated once at this stage.about.html, blog/post-1.html).
During the build process, Next.js runs any functions defined under getStaticProps (or getStaticPaths for dynamic routes). This is when you fetch data—such as posts from a Django API or markdown files for your blog.
// pages/posts/[id].js
export async function getStaticProps({ params }) {
// Fetch data, e.g., from a Django REST API
const res = await fetch(`https://api.example.com/posts/${params.id}`);
const post = await res.json();
return { props: { post } };
}
export async function getStaticPaths() {
// Predefine paths to generate at build time
const res = await fetch('https://api.example.com/posts');
const posts = await res.json();
const paths = posts.map(post => ({ params: { id: post.id.toString() } }));
return { paths, fallback: false };
}
export default function Post({ post }) {
return <div>{post.title}</div>;
}
This code generates an HTML file for each blog post at build time, fetching its content from a Django backend API. No further requests to Django or a database occur when a visitor loads the page—the page is already “baked” into HTML.
Server Side Rendering (SSR) is a rendering approach where HTML for each page is generated on the server for every incoming request. Instead of pre-building pages, Next.js dynamically executes code on demand—fetching data, rendering the React.js components to HTML, and then sending the HTML and JSON payload to the browser.
getServerSideProps), fetches the required data, and constructs the HTML response right then.
For SSR pages, you implement getServerSideProps in your page component. This function runs on the server for each page request, fetching up-to-date data (for instance, from a Django REST API).
// pages/products/[id].js
export async function getServerSideProps(context) {
const { id } = context.params;
// Fetch the latest data from Django API
const res = await fetch(`https://api.example.com/products/${id}`);
const product = await res.json();
return { props: { product } };
}
export default function Product({ product }) {
return <div>{product.name} - ${product.price}</div>;
}
Here, every time a user requests a particular product, Next.js fetches the current product data from your Django API and renders it into HTML. This ensures users always see up-to-date pricing or inventory.
Deciding between SG and SSR is a core system design choice:
Hybrid approaches are also common—you can build a React.js app where some pages (like your blog) use static generation, while others (like user dashboards pulling live data from Django backends) use SSR.
Picture a Next.js application deployed in Docker containers, backed by a Django REST API. The architecture looks like this:
Build workflows often combine Next.js for React.js-based UIs, Django for backend data/APIs, and Docker for repeatable development/deployment:
{/* docker-compose.yml */}
version: '3'
services:
django:
build: ./backend
ports:
- "8000:8000"
nextjs:
build: ./frontend
ports:
- "3000:3000"
In this setup, the Next.js build steps (next build) run inside Docker, calling the Django API to generate static files. Any time you update blog content in Django, you must rebuild and redeploy the Next.js frontend.
// pages/profile.js
export async function getServerSideProps({ req }) {
// Retrieve user session, fetch profile from Django API
const token = getCookieFromReq(req, 'auth_token');
const res = await fetch('http://django:8000/api/user/', {
headers: { Authorization: `Bearer ${token}` }
});
const profile = await res.json();
return { props: { profile } };
}
export default function Profile({ profile }) {
return <div>Hello, {profile.name}!</div>;
}
In a Docker orchestration, the Next.js container can always talk to the Django service, ensuring real-time personalized dashboard data. The cost is that each profile page request triggers a call to Django, impacting both performance and infrastructure design.
Next.js offers a hybrid static regeneration feature known as ISR. It allows you to statically generate a page at build time, but also “regenerate” it on the server in the background when a request comes in, based on a defined time window.
export async function getStaticProps() {
const res = await fetch('https://api.example.com/content');
const content = await res.json();
return {
props: { content },
revalidate: 60 // In seconds: regenerate this page every minute
};
}
ISR blends the performance of static pages with near-real-time data updates—suitable for sites with frequent, but not truly “real-time,” updates.
Running both Next.js and Django inside Docker containers mimics production. You can benchmark:
Choosing between Static Generation (SG) and Server Side Rendering (SSR) in Next.js is a fundamental system design decision that shapes your application’s scalability, performance, development workflow, and user experience. SG delivers lightning-fast, scalable content for stable data, while SSR unlocks real-time, personalized experiences—at the cost of more server complexity and attention to caching.
By understanding the operational flow, real-world deployment setups (including React.js and Django with Docker), and practical trade-offs, you can architect React.js apps using the best rendering paradigm for each page, mixing SG, SSR, and ISR as needed. Next steps: experiment with both methods in production-like (Dockerized) environments, integrate with APIs (e.g., Django REST framework), and continuously profile and optimize your rendering strategy based on your user and business needs.
