Rendering strategies
- Static Site Generation (SSG) uses the serverside-only method getStaticProps() to provide your React component with data that is then used to pre-render your components at build time. The pre-rendered HTML is then reused on each request. This is the default and recommended rendering method as it results in better performance, caching and SEO.
- Server-side Rendering (SSR) uses the serverside-only method getServerSideProps() which pre-renders the HTML of your component on each request.
- Client-side Rendering (CSR) uses the browser-only JS engine to render components
- Incremental Static Regeneration (ISR) uses the serverside-only method getStaticProps() and allow you to create or update static pages after you’ve built your site. So you can create static pages without needing to rebuild the entire site.
Each generated HTML is associated with minimal JavaScript code necessary for that page. When a page is loaded by the browser, its JavaScript code runs and makes the page fully interactive. This process is called hydration.
How hydration works
Hydration is the process by which React “attaches” to existing HTML that was already rendered by React in a server environment. During hydration, React will attempt to attach event listeners to the existing markup and take over rendering the app on the client1.
Here are the steps involved in hydration:
- The server generates HTML for a page and sends it to the client.
- The client receives the HTML and displays it to the user.
- The client also receives a JavaScript bundle that contains the React code for the page.
- The JavaScript bundle is executed, and React starts up on the client.
- React reads the existing HTML on the page and creates a virtual DOM that represents it.
- React compares the virtual DOM to what it would have generated if it had rendered the page from scratch.
- If there are any differences between the two, React will update the DOM to match its expected state.
- React attaches event listeners to the DOM and takes over rendering the app on the client.
During this process, React will attempt to preserve as much of the existing HTML as possible while updating it to match its expected state. If there are any differences between the server-generated HTML and what React would have generated, React will patch up these differences during hydration.
Let me know if this helps or if you have any further questions.
Choosing the right rendering strategy
You can create a “hybrid” NextJS app by using Static Generation for most pages and using Server-side Rendering for others.
npm run dev
or yarn dev
), every page is pre-rendered on each request — even for pages that use Static Generation.Static Site Generation (SSG) via getStaticProps
You should use getStaticProps
if:
- The data required to render the page is available at build time ahead of a user’s request.
- The data comes from a headless CMS.
- The data can be publicly cached (not user-specific).
- The page must be pre-rendered (for SEO) and be very fast —
getStaticProps
generates HTML and JSON files, both of which can be cached by a CDN for performance.
If you export an async getStaticProps function, it will get called at build time and fetch external data before it is send as props to the page. For example your blog page might need to fetch the list of blog posts from a CMS.
export default function Home(props) { ... } export async function getStaticProps(context) { // context = { // params - route parameters for pages using dynamic routes // preview - is true if the page is in the preview mode // previewData - contains the preview data set by setPreviewData // locale - contains the active locale (if enabled) // locales - contains all supported locales (if enabled) // defaultLocale - contains the configured default locale (if enabled) // } // You should not use fetch() to call an API route in getStaticProps. // Instead, directly import the logic used inside your API route // Get external data from the file system, API, DB, etc. const data = ... // The value of the `props` key will be passed to the `Home` component return { // A required object with the props that will be received by the page component props: ... // An optional amount in seconds after which a page re-generation can occur (see Incremental Static Regeneration) revalidate: ... // An optional boolean value to allow the page to return a 404 status and page notFound: ... // An optional redirect value to allow redirecting to internal and external resources redirect: { destination: '/', permanent: false, } } }
- In development mode,
getStaticProps
runs on each request instead. - Next.js polyfills
fetch()
on both the client and server. You don’t need to import it. getStaticProps
only runs on the server-side, that means you can write code such as direct database queries without them being sent to browsers.getStaticProps
can only be exported from a page. You can’t export it from non-page files.getStaticProps
runs in the background when usingfallback: truegetStaticProps
is called before initial render when usingfallback: blocking
When a page with getStaticProps
is pre-rendered at build time, in addition to the page HTML file, Next.js generates a JSON file holding the result of running getStaticProps
. This JSON file will be used in client-side routing through next/link
or next/router
. When you navigate to a page that’s pre-rendered using getStaticProps
, Next.js fetches this JSON file (pre-computed at build time) and uses it as the props for the page component.
When using Incremental Static Generation, getStaticProps
will be executed in the background to generate the JSON needed for client-side navigation. You may see this in the form of multiple requests being made for the same page, however, this is intended and has no impact on end-user performance.
Server Side Rendering (SSR) via getServerSideProps
Server Side Rendering is a good idea if you cannot pre-render a page ahead of a user’s request with Static Site Generation, because the requested data is request specific, such as authorization headers, geo location or if your page shows frequently updated data that changes with every request. In that case, you can use Server-side Rendering which will be slower than statically generated pages, but the pre-rendered page will always be up-to-date. Pages will only be cached if cache-control headers are configured. If you do not need to render the data during the request, then you should consider fetching data on the client side or getStaticProps
.
To use Server-side Rendering, you need to export getServerSideProps
instead of getStaticProps
from your page. You can’t export it from non-page files.
getStaticProps
can only be exported from a page.
function Page({ data }) { // Render data... } // This gets called on every request export async function getServerSideProps() { // Fetch data from external API const res = await fetch(`https://.../data`) const data = await res.json() // Pass data to the page via props return { props: { data } } } export default Page
Client-side Rendering
With this approach you first statically generate (pre-render) parts of the page that do not require external data. Then, when the page loads, you fetch external data client-side. This works well for user dashboard pages, because a dashboard is a private, user-specific page where SEO is not relevant. The data is frequently updated, which requires request-time data fetching.
You can use any client side library that supports SSR to fetch data, but the team behind NextJS has created a React hook for data fetching called SWR. It is highly recommended for REST APIs if you’re fetching data on the client side. It handles caching, revalidation, focus tracking, refetching on interval, and more.
import useSWR from 'swr' function Profile() { const { data, error } = useSWR('/api/user', fetch) if (error) return <div>failed to load</div> if (!data) return <div>loading...</div> return <div>hello {data.name}!</div> }
Statically Generated Pages with Dynamic Routes via getStaticPaths
If you want to statically generate a page at a path called /posts/<id>
where <id>
can be dynamic, then you first have to create a page /pages/posts/[id].js
(explicitly use brackets in the filename).
getStaticPaths
must be used with getStaticProps and will by default only run during build in production, but getStaticPaths
allows you to control which pages are generated during the build instead of on-demand with fallback
. For example, you can defer generating all pages on-demand by returning an empty array for paths
.
In development (next dev
), getStaticPaths
will be called on every request.
getStaticPath must return an object containing paths
as key which itself holds an array with objects that must have params as key. The params
strings are case-sensitive. For example, if the page name is pages/posts/[postId]/[commentId]
, then params
should contain postId
and commentId
:
{ paths: [ { params: { postId: '1', commentId: '53' } }, { params: { postId: '2', commentId: '98' } } ] }
If the page name uses catch-all routes like pages/[...slug]
:
{ paths: [ { params: { slug: ['hello', 'world'] // would generate the page for url /hello/world } } ] }
If you provide an optional catch-all-route like pages/[[...slug]]
:
{ paths: [ { params: { slug: false // would generate the page for the root url / } } ] }
import Layout from '../../components/layout' export default function Post() { return <Layout> <Head> <title>{postData.title}</title> </Head> </Layout> } export async function getStaticPaths() { // Return a list of possible value for id const paths = getAllPostIds(); // paths MUST be an array of objects, each // containing a 'params' key and an object with 'id' because // we are using [id] in the file name. // Example { params: { id: 4711 } } return { paths, // if fallback is false, any paths not returned by getStaticPaths will result in a 404 page // if fallback is true, getStaticProps runs in the background // if fallback is 'blocking', getStaticProps is called before initial render fallback: false } } export async function getStaticProps({ params }) { // Fetch necessary data for the blog post using params.id const postData = getPostData(params.id) return { props: { postData } } }
Fallback for getStaticPaths
If fallback is false
, then any paths not returned by getStaticPaths
will result in a 404 page. This option is useful if you have a small number of paths to create, or new page data is not added often.
Fallback ‘true’
fallback: true
is useful if your app has a very large number of static pages that depend on data (such as a very large e-commerce site). If you want to pre-render all product pages, the builds would take a very long time. Instead, you may statically generate a small subset of pages and use fallback: true
for the rest. fallback: true
will not update generated pages, for that take a look at Incremental Static Regeneration. The behavior of getStaticProps
changes:
- The paths returned from
getStaticPaths
will be rendered to HTML at build time bygetStaticProps
. - The paths that have not been generated at build time will not result in a 404 page. Instead, NextJS will serve a “fallback” version of the page on the first request to such a path (see “Fallback pages” below for details).
- In the background, NextJS will statically generate the requested path HTML and JSON. This includes running
getStaticProps
. - When that’s done, the browser receives the JSON for the generated path. This will be used to automatically render the page with the required props. From the user’s perspective, the page will be swapped from the fallback page to the full page.
- At the same time, NextJS adds this path to the list of pre-rendered pages. Subsequent requests to the same path will serve the generated page, just like other pages pre-rendered at build time.
fallback: true
is not supported when usingnext export
.
// pages/posts/[id].js import { useRouter } from 'next/router' function Post({ post }) { const router = useRouter() // If the page is not yet generated, this will be displayed // initially until getStaticProps() finishes running if (router.isFallback) { return <div>Loading...</div> } // Render post... } // This function gets called at build time export async function getStaticPaths() { return { // Only `/posts/1` and `/posts/2` are generated at build time paths: [{ params: { id: '1' } }, { params: { id: '2' } }], // Enable statically generating additional pages // For example: `/posts/3` fallback: true, } } // This also gets called at build time export async function getStaticProps({ params }) { // params contains the post `id`. // If the route is like /posts/1, then params.id is 1 const res = await fetch(`https://.../posts/${params.id}`) const post = await res.json() // Pass post data to the page via props return { props: { post }, // Re-generate the post at most once per second // if a request comes in revalidate: 1, } } export default Post
Fallback ‘blocking’
If fallback
is 'blocking'
, new paths not returned by getStaticPaths
will wait for the HTML
to be generated, identical to SSR (hence why blocking), and then be cached for future requests so it only happens once per path.
fallback: 'blocking'
will not update generated pages by default. To update generated pages, use Incremental Static Regeneration in conjunction with fallback: 'blocking'
Fallback pages
The page’s props will be empty for fallback pages. Using the router, you can detect if the fallback is being rendered:
function Post({ post }) { const router = useRouter() // If the page is not yet generated, this will be displayed // initially until getStaticProps() finishes running if (router.isFallback) { return <div>Loading...</div> } // Render post... }
Incremental Static Regeneration
Incremental Static Regeneration allows you to update existing pages by re-rendering them in the background as traffic comes in. This ensures traffic is served uninterruptedly, always from static storage, and the newly built page is pushed only after it’s done generating.
// This function gets called at build time on server-side. // It may be called again, on a serverless function, if // revalidation is enabled and a new request comes in export async function getStaticProps() { const res = await fetch('https://.../posts') const posts = await res.json() return { props: { posts, }, // Next.js will attempt to re-generate the page: // - When a request comes in // - At most once every second revalidate: 1, // In seconds } } export default Blog
Reading files within getStaticProps
Since Next.js compiles your code into a separate directory you can’t use __dirname
. Instead you can use process.cwd()
:
import { promises as fs } from 'fs' import path from 'path' // posts will be populated at build time by getStaticProps() function Blog({ posts }) { return ( <ul> {posts.map((post) => ( <li> <h3>{post.filename}</h3> <p>{post.content}</p> </li> ))} </ul> ) } // This function gets called at build time on server-side. // It won't be called on client-side, so you can even do // direct database queries. See the "Technical details" section. export async function getStaticProps() { const postsDirectory = path.join(process.cwd(), 'posts') const filenames = await fs.readdir(postsDirectory) const posts = filenames.map(async (filename) => { const filePath = path.join(postsDirectory, filename) const fileContents = await fs.readFile(filePath, 'utf8') // Generally you would parse/transform the contents // For example you can transform markdown to HTML here return { filename, content: fileContents, } }) // By returning { props: { posts } }, the Blog component // will receive `posts` as a prop at build time return { props: { posts: await Promise.all(posts), }, } } export default Blog
Accessing dynamic route parameters on client side
getStaticPaths is the preferred way to access route parameters, because it allows you to build static pages at runtime. But if you need to access route parameters client side, you could use this:
// pages/post/[pid].js will match for example: // /post/abc -> { "pid": "abc" } // /post/1 -> { "pid": 1 } // /post/abc?foo=bar -> { "foo": "bar", "pid": "abc" } // /post/abc?pid=123 -> { "pid": "abc" } route param pid=abc has priority over query param pid=123 // page pages/post/[pid]/[comment].js -> { "pid": "abc", "comment": "a-comment" } import { useRouter } from 'next/router' const Post = () => { const router = useRouter() const { pid } = router.query return <p>Post: {pid}</p> } export default Post