Blog post
Next.js Static Site Generation - SSG
Next.js Static Site Generation - SSG
Nikola Ivanović
2021-05-26
One of the main Next.js features is page pre-rendering. What does this mean?
Next.js generates HTML for each page in advance, instead of having it all done by client-side JavaScript. Pre-rendering the pages can result in better performance and SEO.
Each generated HTML is associated with minimal JavaScript code necessary for that page. When the browser loads a page, its JavaScript code runs and makes the page fully interactive.
When building websites or web applications, you generally have to choose between 2 strategies:
Static generation (SSG) or
Server-side rendering (SSR).
The main difference between SSG and SSR is that with Static generation, the HTML is generated at build time and will be reused on each request, while on the other hand, with server-side rendering, the HTML is generated on each request.
And the great thing is: Next.js is the first hybrid framework that allows us to choose the technique that fits our use case best on a per-page basis.
Next.js developers recommend using Static generation over Server-side Rendering for performance reasons. CDN can cache statically generated pages with no extra configuration to boost performance. However, in some cases, Server-side Rendering might be the only option.Static generation
As previously mentioned, if a page uses Static Generation, the page HTML is generated at build time. That means in production, and the page HTML is generated when you run next build
. This HTML will then be reused on each request. A CDN can also cache it.
In Next.js, we can statically generate pages with or without data.
This is the most simple case where the page does not need to fetch any external data to be pre-rendered, and by default, Next.js pre-renders pages using Static Generation without fetching data.
Here is a basic example:
1function About() {
2 return <div>About</div>
3}
4
5export default About
Before we dive deeper into these examples and variations, I would like to point out some things as a small introduction.
Since version 9.3, Next.js added a new life-cycle method getStaticProps
and getStaticPaths
for improved SSG support, allowing us to fetch data and create static pages at build time. With the new data fetching method getServerSideProps
used for SSR, Next.js allows us to separate code intended for SSG or SSR. This is, in many cases, much better than the classic hybrid approach of getInitialProps
(which was introduced in the 9.0 version with the Automatic static optimization concept ).
Some pages require fetching external data for pre-rendering. Considering this, there are two scenarios, and one or both might apply. In each case, you can use mentioned Next.js life-cycle methods:
getStaticProps
- if our page content depends on external data
getStaticPaths
- if our page paths depend on external data (usually in addition to getStaticProps
)
If you export an async
function called getStaticProps
from a page, Next.js will pre-render this page at build time using the props returned by getStaticProps
.
1export async function getStaticProps() {
2 return {
3 props: {}, // will be passed to the page component as props
4 }
5}
getStaticProps
should return an object with:
props
- A required object with the props that the page component will receive. It should be a serializable object
revalidate
- An optional amount in seconds after which a page re-generation can occur. More on Incremental Static Regeneration
notFound
- An optional boolean value to allow the page to return a 404 status and page. More on Incremental Static Regeneration
We will cover an example that uses getStaticProps
to fetch a list of blog posts from a CMS.
1// TODO: Need to fetch `posts` (by calling some API endpoint)
2// before this page can be pre-rendered.
3function Blog({ posts }) {
4 return (
5 <ul>
6 {posts.map((post) => (
7 <li>{post.title}</li>
8 ))}
9 </ul>
10 )
11}
12
13export default Blog
To fetch this data on pre-render, we will export
an async
function called getStaticProps
from the same file. This way, we can pass fetched data to the page's props
on pre-render.
1function Blog({ posts }) {
2 // Render posts...
3}
4
5// This function gets called at build time
6export async function getStaticProps() {
7 // Call an external API endpoint to get posts
8 const res = await fetch('https://.../posts')
9 const posts = await res.json()
10
11 // By returning { props: posts }, the Blog component
12 // will receive `posts` as a prop at build time
13 return {
14 props: {
15 posts,
16 },
17 }
18}
19
20export default Blog
Also, besides this example, a good question that sums up this excellent method is:
When should I use getStaticProps
?
If the data required to render the page is available at build time ahead of a user’s request.
If the data comes from a headless CMS.
If the data can be publicly cached (not user-specific).
If 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.
In Next.js, we can create pages with dynamic routes.
If a page has dynamic routes and uses getStaticProps
it needs to define a list of paths that have to be rendered to HTML at build time.
For example, we can create a file called pages/posts/[id].js
to show a single blog post based on id
. This will allow us to show a blog post with id: 1
when we access posts/1
.
Later, we might add the second post with id: 2
. Then we’d want to pre-render posts/2
as well.
Our page paths that are pre-rendered depend on external data. To handle this, we can export
an async
function called getStaticPaths
from a dynamic page (pages/posts/[id].js
in this case). This function gets called at build time and lets us specify which paths we want to pre-render.
1// This function gets called at build time
2export async function getStaticPaths() {
3 // Call an external API endpoint to get posts
4 const res = await fetch('https://.../posts')
5 const posts = await res.json()
6
7 // Get the paths we want to pre-render based on posts
8 const paths = posts.map((post) => `/posts/${post.id}`)
9
10 // We'll pre-render only these paths at build time.
11 // { fallback: false } means other routes should 404.
12 return { paths, fallback: false }
13}
In the example above, we need to mention that paths
and fallback
keys are required.
The paths key determines which paths
will be pre-rendered. For example, our page uses dynamic routes named pages/posts/[id].js
. If we export getStaticPaths
from this page and return the following for paths
:
1return {
2 paths: [
3 { params: { id: '1' } },
4 { params: { id: '2' } }
5 ],
6 fallback: ...
7}
Then Next.js will statically generate posts/1
and posts/2
at build time using the page component in pages/posts/[id].js
.
The object returned by getStaticPaths
must contain a boolean fallback
key.
If fallback
is false
, then any paths not returned by getStaticPaths
will result in a 404 page. We can do this if we have a small number of paths to pre-render - so they are all statically generated during build time. It’s also useful when the new pages are not added often. If we add more items to the data source and need to render the new pages, we’d need to run the build again.
We will show a full example that pre-renders one blog post per page called pages/posts/[id].js
. The list of blog posts will be fetched from a CMS and returned by getStaticPaths
. Then, for each page, it fetches the post data from a CMS using getStaticProps
:
1// pages/posts/[id].js
2
3function Post({ post }) {
4 // Render post...
5}
6
7// This function gets called at build time
8export async function getStaticPaths() {
9 // Call an external API endpoint to get posts
10 const res = await fetch('https://.../posts')
11 const posts = await res.json()
12
13 // Get the paths we want to pre-render based on posts
14 const paths = posts.map((post) => ({
15 params: { id: post.id },
16 }))
17
18 // We'll pre-render only these paths at build time.
19 // { fallback: false } means other routes should 404.
20 return { paths, fallback: false }
21}
22
23// This also gets called at build time
24export async function getStaticProps({ params }) {
25 // params contains the post `id`.
26 // If the route is like /posts/1, then params.id is 1
27 const res = await fetch(`https://.../posts/${params.id}`)
28 const post = await res.json()
29
30 // Pass post data to the page via props
31 return { props: { post } }
32}
33
34export default Post
We use fallback: true
case when our app has many static pages that depend on data (for example: an e-commerce website with lots of products). Find more information about fallback:true
Static Generation (with and without data) should be used whenever possible because the page can be built once and served by CDN, making it much faster than having a server render the page on every request and when the page can be pre-rendered ahead of a user's request.
Static Generation can be used for many types of pages, such as:
Marketing pages
Blog posts
E-commerce product listings
Help and documentation
Both SSG methods, getStaticProps
, and getStaticPaths
are run at a build time and can only be exported from a page. Also, both methods must be export
-ed as async
functions and will not work if added as a property of the page component.
I would also recommend watching this nice tutorial covering most of the mentioned things and some other features such as using SSG with fs (file-system).
Happy coding!
Nikola Ivanović
2021-05-26
Nikola is JavaScript developer passionate about React, CSS, Animations and UX design. Loves frontend work but he is not afraid to touch backend.
Leave your thought here
Your email address will not be published. Required fields are marked *