Generating static sites with Next.js
This article is part of a series called "Next.js".
- Next.js: The big picture
- Pages and routing in Next.js
- Static files, styled-jsx and CSS modules in Next.js
- Generating static sites with Next.js
Many web projects have a certain percentage of pages that are static. A static page is a page in your project, that does not change based on which user accesses the page or the time they access it.
Static pages will always be the same for all users, which makes them a perfect candidate for caching and pre rendering. Those pages can usually be generated when the website is deployed1 and create minimal load on the server, even when many users access them at the same time.
Next.js offers a very convenient way to generate static pages, and by that making your website very fast and easy to host. You can even generate many static pages from a dynamic source at compile time. What this means is that you can generate all the static pages for all of your products in your eCommerce page when you deploy the page. This allows you to have super fast product sites, while still being able to generate them from your actual product data.
Defining pages
Let’s start by defining some basic websites, that don’t use any dynamic data. I will re-use the Hello
page from the Pages and routing in Next.js article.
export default function Hello() {
return (
<main>
<h1>Hello there!</h1>
<p>I hope you have a great day. 🙂</p>
</main>
);
}
Adding this page will actually create an HTML file in Next.js’s internal static folder2. With this, you create a static page, that can be served super fast. You will still get all the benefits of of styling your elements.
Static properties
Generating fully static pages is possible, but it certainly is not really exciting. Next.js offers a way for you to add data from any source you want at compile time. This means that when you deploy your site, it will get data from a source and generate your site with that.
This allows you to have static pages, that are still based on dynamic data. One great use case is to re-generate all your static sites once or twice a day, if you know that your data will not update more often. Many eCommerce system only update their product information and images once per night, so this would be a great opportunity to re-generate all static sites, once the product information has been updated.
Another benefit is that you can generate your static files based on data that is only accessible in a VPN or something similar: Your build server could be part of the VPN, generate the static sites and upload the data to another server that is able to function as a public web server.
getStaticProps
function
If you want to run Javascript code, that fetches that data at compile time, you can achieve this by simply exporting a function called getStaticProps
. Next.js will know to run this at compile time and pass the information to the page component.
A simplified example might look like this.
import axios from "axios";
export async function getStaticProps(context) {
const products = await axios.get("https://pim.int/products.json");
return {
props: {
numberProducts: 1 + products.length
}
}
}
export default function Hello(props) {
return (
<main>
<h1>Hello there!</h1>
<p>Buy one of our {props.numberProducts} products.</p>
</main>
);
}
This code would still generate a static page using the Hello
component, but this time the component will receive props generated by the getStaticProps
function.
The getStaticProps
function gets data from an imaginary internal PIM3 system, and returns some data for the component. This is just example code and will not actually work.
Static routes
The features, that really makes this super valuable are static routes. until now, we only generated one page called /hello
and used data from an imaginary PIM3 system to populate the page. What we want to achieve next, is to build a site that is fetching dynamic data on compile time in order to pre-render many pages.
Our plan to achieve this is:
- Create a new page with dynamic routing
- Fetch the data based on the dynamic id in the route
- Use the dynamic data to render the static page
- Tell Next.js which pages it is supposed to pre-render
Dynamic routing
Creating a dynamic route is actually quite simple in Next.js. It is quite common for systems to use some kind of configuration to determine which routes exist4, but Next.js tries to stick to information in files and their filename.
In order to create a new page that allows us to react to dynamic routes like /products/1
or /products/42
you need to create a new folder called products
and place a file called [id].js
inside id. The [id]
will be recognized by our system as a dynamic part of the route with the name id
.
Opening the page /products/99
will tell our code to use our newly create Javascript page /pages/products/[id].js
and use the number 99 as the id
parameter.
The content of our file can look like this:
export default function Products(props) {
return (
<div>
<h1>Product page!</h1>
</div>
);
}
Using this code, our new Products
page will always look the exact same, but we can call any products’s URL (like /product/102
) and still see this page.
Fetch data using id from route
Our next step is to use the id
parameter to fetch data from our imaginary internal PIM system. In order to do that, we again use the getStaticProps
function.
This time we don’t “just” get all the products’ information, but use the id to get a specific products information. The code for this function can look like this:
export async function getStaticProps(context) {
const id = context.params.id;
return {
props: await axios.get( `https://pim.int/product-${id}.json`)
};
}
[...]
This will fetch product data from an imaginary PIM3 system and provide it as props to the page component.
Render static page
Having fetched the data for the product page, we can now go ahead and update our Products
component to actually output valuable information to the user.
The code of the updated Products
page component can look like this:
[...]
export default function Products(props) {
return (
<div>
<h1>{props.title}</h1>
<p className="description">{props.description}</p>
<div className="price">{props.price}</div>
</div>
);
}
Tell Next.js which pages to pre-render
This is all great, but one very important part is still missing: We generated a dynamic page. The page should be pre-rendered when the page is deployed so that we have all the pages as static files on disk. Right now, our project does not do that, though because we did not tell Next.js which pages it is supposed to pre-render: The system cannot know which products actually exist and which product ids it should use to pre-generate the page.
In order to do that, we need to export one last function. This function should tell the system which pages actually need to be pre-rendered. This function is rather simple to build: It needs to be called getStaticPaths
, and export an object with two properties: the paths
that are supposed to be pre-rendered and a fallback
property, which determines if the system is “allowed” to dynamically render pages in case it did not yet pre-render the page.
Let’s write the function using our imaginary PIM3 system again:
export async function getStaticPaths() {
const products = await axios.get("https://pim.int/products.json");
return {
paths: products.map(product => {
return {
params: {
id: product.id
}
}
}),
fallback: false
};
}
[...]
The result will be that the function returns an object which will look like this:
{
"paths": [
{ "params": { "id": "1" } },
{ "params": { "id": "2" } },
{ "params": { "id": "3" } },
{ "params": { "id": "4" } }
],
"fallback": false
}
This will tell Next.js to pre-render all of those product pages, which it will. Once users visit one of your sites, they will enjoy the super fast load times, and your server will have minimal load even when serving many many users.
The complete file for your dynamic product page might look like this, which is actually not a lot of code, given the value you get out of it:
export async function getStaticPaths() {
const products = await axios.get("https://pim.int/products.json");
return {
paths: products.map(product => {
return {
params: {
id: product.id
}
}
}),
fallback: false
};
}
export async function getStaticProps(context) {
const id = context.params.id;
return {
props: await axios.get( `https://pim.int/product-${id}.json`)
};
}
export default function Products(props) {
return (
<div>
<h1>{props.title}</h1>
<p className="description">{props.description}</p>
<div className="price">{props.price}</div>
</div>
);
}
You can generate your new set of static pages using Next.js’s yarn command:
yarn build
Recap
If you want to achieve super fast page load times as well as low load on your server, pre rendering static sites at deployment is a very good tool in your tool box. This strategy will only work, if you have pages that have a lot in common and are basically the same for all users.
Next.js provides a great way to generate those pages at compile time, by allowing you to define a dynamic page and then pre-rendering those pages. Using this technique allows you to define some of your pages as static pages, while others can still be as dynamic as you need them to be.
I hope this article helped you to have a look into generating those pages with Next. It also provides ways to generate pages “on the fly” without any pre-rendering, and we will talk about those in a future article.
-
There are a lot of applications and command line tools you can use to generate a static site, Next.js is one of them. StaticGen.com has a great overview over the available options, if you want to learn more. ↩
-
Next.js does a lot to optimize files so it’s server is able to serve these files quickly. This does not always resolve in lean code, so don’t be surprised by what you see. The file for out
hello.js
page will probably be here:./.next/server/static/{RANDOM-HASH}/hello.html
.{RANDOM-HASH}
is a placeholder for the build hash that Next.js uses internally. It is probably a number of letters and numbers, similar to this:IihdGbwKkkMKqD-MCAunQ
. ↩ -
A product information management system (PIM for short) is a software used by medium to large businesses to organize all the information around their company’s products. Often times, the PIM does not only include titles and descriptions, but also assets like photos and videos. ↩ ↩2 ↩3 ↩4
-
Many systems use a dedicated configuration files to determine which routes exist in the application. On famous example for this would be the way that django handles routes. ↩
This website does not use any tracking or feedback mechanism. Instead, I would love for you to get in touch with me to let me know if you liked it or if you have any suggestions or questions.