Course
Server-Side Rendering Strategies
Next.js Mastery: From Fundamentals to Full-Stack
Unlock the power of Next.js with this comprehensive course! Starting with the basics, you’ll learn essential skills such as routing, data fetching, and styling. Progress through practical projects, including building your own React Notes app, to gain hands-on experience. Dive into the source code to understand the inner workings and core principles of Next.js. Perfect for developers with a basic knowledge of React and Node.js, this course ensures you’ll be ready to create high-performance full-stack applications efficiently. Join us and master Next.js, backed by industry experts and a supportive learning community.
Introduction
Now let's create a new file,
app/server/page.js
, with the following code: export default async function Page() { const url = (await (await fetch('https://api.thecatapi.com/v1/images/search')).json())[0].url return ( <img src={url} width="300" alt="cat" /> )}
The URL
https://api.thecatapi.com/v1/images/search
returns a random cat image each time it is called.Let's run
npm run dev
. In development mode, refreshing the page returns a new image each time:
However, after running
npm run build && npm run start
, refreshing the page always returns the same cat image:
Why is this happening?
Let's look at the build output:
/server
is marked as Static, meaning it is pre-rendered as static content. This means the returned content for /server
is determined at build time. The image displayed on the page is the one fetched during the build.So, how can we make
/server
return a new image each time?This leads us to Next.js's server-side rendering strategies.
Server-Side Rendering Strategies
Next.js has three different server-side rendering strategies:
- Static Rendering
- Dynamic Rendering
- Streaming
Let's introduce each of these.
Static Rendering
Static Rendering is the default rendering strategy. Routes are rendered at build time or re-rendered in the background after revalidation, and the results can be cached and pushed to a CDN. This is suitable for non-personalized data that is known in advance, such as static blog posts or product pages.
In the example above, the page was rendered at build time. To re-render after revalidation, you can use a route segment configuration option like
revalidate
.Modify
app/server/page.js
to include:export const revalidate = 10
export default async function Page() { const url = (await (await fetch('https://api.thecatapi.com/v1/images/search')).json())[0].url return ( <img src={url} width="300" alt="cat" /> )}
Even though
/server
is still marked as Static in the build output, the image now updates periodically:
The
revalidate = 10
line sets a revalidation frequency of 10 seconds. Note:- The code does not make the server automatically update
/server
every 10 seconds. - Instead, it triggers revalidation at least 10 seconds after the last request.
For example, if you access
/server
at time 0s
and continue to access it within the next 10 seconds, the returned image remains the same. After 10 seconds, a new request at 12s
still returns the cached result, but triggers a cache update. The next request at 13s
returns the updated result.Dynamic Rendering
Dynamic Rendering occurs at request time, suitable for personalized data or data dependent on request information (e.g., cookies, URL parameters).
When using dynamic functions or uncached data requests, Next.js switches to Dynamic Rendering:
As a developer, you don't need to choose between static or dynamic rendering. Next.js automatically selects the best strategy based on the features and APIs you use for each route.
1. Dynamic Functions
Dynamic functions fetch information only available at request time (e.g., cookies, request headers, URL parameters). In Next.js, these include:
cookies()
andheaders()
for cookies and headerssearchParams
for query parameters
Using any of these functions triggers dynamic rendering.
Example 1: Using Cookies
Modify
app/server/page.js
:import { cookies } from 'next/headers'
export default async function Page() { const cookieStore = cookies() const theme = cookieStore.get('theme')
const url = (await (await fetch('https://api.thecatapi.com/v1/images/search')).json())[0].url return ( <img src={url} width="300" alt="cat" /> )}
Running
npm run build && npm run start
, /server
is dynamically rendered:
Example 2: Using
searchParams
Modify
app/server/page.js
:export default async function Page({ searchParams }) { const url = (await (await fetch('https://api.thecatapi.com/v1/images/search')).json())[0].url return ( <> <img src={url} width="300" alt="cat" /> {new Date().toLocaleTimeString()} {JSON.stringify(searchParams)} </> )}
Running
npm run build && npm run start
, /server
is dynamically rendered:
However, the image does not change on page refresh.
This is because data request caching is separate from rendering strategy. Even with dynamic rendering, the fetch request is cached, resulting in the same image.
Modify
app/server/page.js
to disable caching:export default async function Page({ searchParams }) { const url = (await (await fetch('https://api.thecatapi.com/v1/images/search', { cache: 'no-store' })).json())[0].url return ( <> <img src={url} width="300" alt="cat" /> {new Date().toLocaleTimeString()} {JSON.stringify(searchParams)} </> )}
With
cache: 'no-store'
, the fetch request does not cache, and the image updates on refresh:
2 Using Uncached Data Requests
By default, fetch results are cached. However, you can configure fetch requests to skip caching. This makes the route dynamically rendered:
- Add
{ cache: 'no-store' }
to fetch requests. - Add
revalidate: 0
to fetch requests. - Use POST method in route handlers.
- Fetch after using
headers
orcookies
. - Configure
const dynamic = 'force-dynamic'
. - Set
fetchCache
route segment option to skip caching. - Use Authorization or Cookie headers in fetch requests, combined with an uncached request higher in the component tree.
For example:
export default async function Page() { const url = (await (await fetch('https://api.thecatapi.com/v1/images/search', { cache: 'no-store' })).json())[0].url return ( <> <img src={url} width="300" alt="cat" /> {new Date().toLocaleTimeString()} </> )}
This renders the page dynamically and fetches a new image each time the page is refreshed.
Streaming
Using
loading.js
or the React Suspense
component enables Streaming. Refer to the "Rendering | Suspense and Streaming" section for more details.Other Terms for Clarification
In addition to static rendering, dynamic rendering, dynamic functions, and uncached data requests, you may encounter related terms like partial rendering and dynamic routes in the documentation. Here’s a breakdown to help distinguish them.
1. Partial Rendering
Partial rendering refers to re-rendering only the changed route segments during client-side navigation while retaining shared segments. For example, navigating between
/dashboard/settings
and /dashboard/analytics
, re-renders settings
and analytics
pages but retains the shared dashboard
layout:
Partial rendering aims to reduce data transfer and execution time during route changes, improving performance.
2. Dynamic Routes
Dynamic routes were covered in "Routing | Dynamic Routes, Route Groups, Parallel Routes, and Intercepting Routes":
export default function Page({ params }) { return <div>My Post: {params.slug}</div>}
Dynamic routes are not necessarily dynamically rendered; you can statically generate routes using
generateStaticParams
.Sometimes, dynamic routes (Dynamic Routes) are used to refer to "dynamically rendered routes." The documentation rarely uses the term static routes (Static Routes), but when it does, it refers to "statically rendered routes."
3. Dynamic Segment
Dynamic segments are parts of the route that are dynamic. For example, in
app/blog/[slug]/page.js
, [slug]
is a dynamic segment.Summary
Congratulations on completing this section!
We've covered the three server-side rendering strategies in Next.js. This wraps up our discussion on rendering, and we’ll now move on to data fetching.