In recent years, client-side Single-Page Applications have become incredibly popular for various reasons. By embracing the SPA (Single-Page Application) model, developers now build pages that offer a level of interactivity previously unattainable.
For instance, when using React in its typical client-rendering approach, the server initially delivers an almost empty HTML page along with the necessary JavaScript files, allowing the browser to generate the visible content. This method significantly reduces the load on the server, but it can also strain the browser, potentially degrading the user experience on less powerful devices. Additionally, there are SEO (Search engine optimization) concerns to consider—Google’s documentation notes that processing JavaScript can be challenging, and not all search engine crawlers can handle it effectively or immediately.
Why Next.js?
In this series of articles, we will build a Next.js project and explore its features step by step. The project will run on Bun, and the code can be found in this repository
How Next.js Extends React
React is primarily a client-side rendering (CSR) library, relying on the browser to fetch and render content. This can lead to slower initial loads and SEO challenges.
Next.js extends React with multiple rendering strategies:
- Server-Side Rendering (SSR) – Pages are generated per request for fresh content and better SEO.
- Static Site Generation (SSG) – Pages are pre-built for faster performance.
- Incremental Static Regeneration (ISR) – Allows updating static pages without a full rebuild.
In addition to these rendering strategies, Next.js also includes several built-in features that make development smoother and performance better:
- File-based routing – Routes are automatically created based on the file structure inside the app or pages directory.
- API routes – You can define backend endpoints directly in your project, without setting up a separate server.
- Image optimization – The built-in <Image /> component automatically optimizes images for size, format, and loading behavior.
Rendering strategies
The way Next.js renders a page directly impacts performance, SEO, and user experience. Each approach is suited for different needs:
Client-Side Rendering (CSR)
With CSR, the browser fetches JavaScript and renders content dynamically. This can lead to slow initial page loads.
|
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 |
import { useEffect, useState } from ‘react’; export default function Page() { const [data, setData] = useState(null); useEffect(() => { fetch(‘/api/data’) .then((res) => res.json()) .then((data) => setData(data)); }, []); return ( <div> {data ? JSON.stringify(data) : ‘Loading…’} </div> ); } |
Server-Side Rendering (SSR)
Next.js fetches data on every request and renders it on the server.
|
1 2 3 4 5 6 7 8 9 |
export default async function Page() { const res = await fetch(‘https://api.example.com/data’, { // This ensures fresh data on every request cache: ‘no-store’ }); const data = await res.json(); return <div>{JSON.stringify(data)}</div>; } |
Static Site Generation (SSG)
Next.js pre-builds the page at compile time, meaning the HTML is generated ahead of time—during the build process—not on each user request. Basically, when someone visits your page, the server simply serves this ready-made HTML file, which makes loading the page extremely fast.
|
1 2 3 4 5 6 |
export default async function Page() { const res = await fetch(‘https://api.example.com/data’); const data = await res.json(); return <div>{JSON.stringify(data)}</div>; } |
This approach reduces the work needed at runtime, leading to better performance, especially under high traffic. It also improves SEO, since search engine crawlers get fully-rendered HTML immediately.
SSG is ideal for content that doesn’t change too often—like blogs, marketing pages, or documentation.
A popular alternative focused on SSG is Gatsby, which also pre-generates static content and works well for content-heavy sites.
Incremental Static Regeneration (ISR)
Next.js updates static pages without a full rebuild.
|
1 2 3 4 5 6 7 8 |
export default async function Page() { const res = await fetch(‘https://api.example.com/data’, { next: { revalidate: 10 } // Revalidate every 10 seconds }); const data = await res.json(); return <div>{JSON.stringify(data)}</div>; } |
| Rendering Method | Initial Load Time | SEO | Best for |
| CSR | Slow | Poor | Interactive apps that don’t require SEO |
| SSR | Medium | Decent | Dynamic content that needs SEO |
| SSG | Fast | Good | Static sites with infrequent updates |
| ISR | Fast | Good | Static pages that need periodic updates |
Now, you may wonder: Why do we need to understand these strategies? Which one is right for my app? Well, it depends on your needs. We need to understand the requirements of our app and choose the best approach.
For example, if you’re building a blog or news website, SSR would be a great choice, as it fetches data on each request and ensures the content is SEO-friendly.
On the other hand, if you’re working on a chat app, CSR could be a better fit since SEO isn’t a priority, and fast, dynamic interactivity is essential.
Understanding these strategies helps you tailor your app’s performance and SEO optimally based on the content and interactivity needed.
Project initialization
Next.js runs both on the server (for SSR/SSG) and the client (for interactivity). To execute JavaScript outside the browser—such as rendering pages on the server or handling API routes—we need a JavaScript runtime like Node.js or Bun.
Bun is a modern JavaScript runtime, faster than Node.js. It comes with a built-in bundler, test runner, and package manager. I chose Bun for its speed and simplicity, and also to try it out because I never had a chance
Bun provides a built-in command to quickly scaffold a new Next.js application:
|
1 |
bun create next–app |
This command initializes a new Next.js project and installs all dependencies. You will need to answer a few questions from the installer based on your preferences.
Understanding Next.js Routing
Next.js simplifies routing with its file-based routing system, eliminating the need for external routing libraries. This system automatically maps files inside the src/app directory to application routes.
File-based routing
Each file inside the src/app directory corresponds to a route:
- src/app/page.tsx → / (Home route)
- src/app/about/page.tsx → /about
Nested routes
You can create nested routes by organizing files into folders. For example, placing page.tsx inside an about folder creates a nested route at /about.
Dynamic routes
Next.js supports dynamic routes using square brackets:
src/app/post/[id].tsx → Matches /post/1, /post/2, etc.
Example Routing Structure
|
1 2 3 4 5 6 7 8 9 10 |
src/ ├── app/ │ ├── about/ │ │ └── page.tsx # About page component │ ├── blog/ │ │ ├── [id]/ │ │ │ └── page.tsx # Dynamic blog post │ │ └── page.tsx # Blog listing page │ ├── layout.tsx # Root layout │ ├── page.tsx # Home page component |
App routing cache provider
In our layout, I have also added a AppRouterCacheProvider component from @mui/material–nextjs.
|
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 |
import { AppRouterCacheProvider } from ‘@mui/material-nextjs/v14-appRouter’; import { ThemeProvider } from ‘@mui/material/styles’; import CssBaseline from ‘@mui/material/CssBaseline’; import theme from ‘./theme’; export default function Providers({ children, }: { children: React.ReactNode; }) { return ( <AppRouterCacheProvider> <ThemeProvider theme={theme}> <CssBaseline /> {children} </ThemeProvider> </AppRouterCacheProvider> ); } |
This package helps optimizing the performance of your Next.js app. Here is how it works:
- CSS Injection: It ensures that CSS is injected correctly during server-side rendering, preventing style flickering during page transitions.
- Cache Management: It manages caching for the App Router, which helps in reducing the load time by caching static assets and styles.
- Seamless Transitions: By managing the cache, it allows for smoother transitions between pages, enhancing the user experience.
Summary
In this article, we unlocked the power of Next.js and how it can supercharge your app’s performance and SEO. We saw how client-side rendering (CSR) falls short and how Next.js steps in with SSR, SSG, and ISR to solve those problems.
We broke down which strategy is perfect for different scenarios — like SSR for blogs and CSR for chat apps — helping you make smarter choices for your projects.
We also touched on getting started with a Next.js project, routing, and performance boosts with tools like AppRouterCacheProvider.
In the next article, we’ll take things a step further and build an API with Next.js.
Leave a Reply