Next.js pre-generates HTML for every page rather than having it all done by the client-side JavaScript. Pre-rendering is finished by default in a Next.j app.
In the React app the JavaScript is loaded and then executes to mount the HTML elements onto the dom. When a page is served we just have a div tag with id adequate root. Once the JavaScript for the page is loaded it’ll execute within the browser, create different dom nodes, and mount them onto the root div element.
Why pre-render? Docs
It improves performance - in a React app one needs to wait for the JavaScript to be executed as we have to fetch data from external API and then render the UI. With a pre-rendered page the HTML is already generated and loads faster.
Helps with Search Engine Optimization (SEO) - in a React app if the search engine hits your page it only used a div tag with id equal to root. But if a search engine hits a pre-rendered page all the content is present in the source code which will help index that page.
1. Static generation without data | Docs
In a static generation, HTML pages are generated at the build time. The HTML with all the data that makes up the content of the web pages is generated in advance when we build the application. Pages can be built once by a CDN and served to the client almost instantly. Used mainly in article pages, e-commerce product pages, and documentation pages.
As we know, Next.js pre-renders every page in-app by default. The HTML for every page will automatically be statically generated when we build our application.
2. Static generation with data | Docs
In Next.js when we export a page component we can also export an async function called getStaticProps. If we export this function it will run at build time in production and inside the function we can fetch external data and send it as props to the page.
To test things out, let’s create a project and create a new file airlines.js and a fake API https://api.instantwebtools.net/v1/airlines.
airlines.js
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
function AirlineList({
airlines
}) {
return (
<>
<h1> List of Airlines </h1>
{airlines.map(airline => {
return (
<div key = {airline.id} >
<p> {airline.name} </p>
<p> {airline.country} </p>
<p> {airline.slogan} </p>
</div>
)
}
)
}
</>
)
}
export default AirlineList
export async function getStaticProps() {
const response = await fetch('https://api.instantwebtools.net/v1/airlines')
const data = await response.json()
return {
props: {
airlines: data
}
}
}
Now run the app and navigate to localhost:3000/airlines.
3. Server-side generation | Docs
SSR is a form of pre-rendering. Here the HTML is generated at request time; it is required when you need to fetch data per request and also when you need to fetch personalized data, keeping in mind SEO.
Problems with static generation:
We cannot fetch data at the request time. With not being able to fetch data per request we run into the problem of stale data.
We don’t get access to the incoming request, which occurs when data that needs to be fetched is specific to a user.
Before we begin we first have to create a server mock API using the json-server package and create a file, db.json, which contains the list of items that we will be using in this section.
db.json
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
{
"books": [
{
"id": 1,
"title": "Things fall apart",
"pages": 209,
"languages": "English"
},
{
"id": 2,
"title": "Fairy tails",
"pages": 784,
"languages": "Danish"
},
{
"id": 3,
"title": "The book of Job",
"pages": 176,
"languages": "Hebrew"
},
{
"id": 4,
"title": "Pride and Prejudice",
"pages": 443,
"languages": "French"
}
]
}
package.json
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21
{ "name": "pre-rendering", "private": true, "scripts": { "dev": "next dev", "build": "next build", "start": "next start", "lint": "next lint", "server-json": "json-server --watch db.json --port 2000" }, "dependencies": { "json-server": "^0.16.3", "next": "12.0.7", "react": "17.0.2", "react-dom": "17.0.2" }, "devDependencies": { "eslint": "8.4.0", "eslint-config-next": "12.0.7" } }
To run: npm run server-json
Now let’s create a book listing page that will use SSR for pre-rendering. In our last created project, pre-rendering in the pages folder, create a new folder, books, within the file create index.js.
To use SSR for a page we need to export an async function called getServerSideProps. When we export this function it will be called by the server on every request and inside this function, we can fetch external data and send it as props to the page.
The getServerSideProps never run client-side and we can write server-side code directly. Querying a database can be done inside this.
books/index.js
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
function BooksList({
books
}) {
return (
<>
<h1>List of News Books</h1>
{books.map(book => {
return (
<div key={book.id}>
<h2>
{book.id} {book.title} | {book.language}
</h2>
<hr />
</div>
)
}
)
}
</>
)
}
export default BooksList
export async function getServerSideProps() {
const response = await fetch('http://localhost:4000/books')
const data = await response.json()
return {
props: {
books: data
}
}
}
Now navigate to localhost:3000/books.
Remember how we discovered that Next.js generates static sites by default? It just works right out of the box. It will, however, attempt to determine which pre-rendering approach you are using for each page.
There are absolutely some circumstances when selecting the appropriate function for retrieving your data will make a difference. It should be taken into account because it might have a significant influence on the user experience.
Assume we’re building a simple article website. Isn’t the material entirely created statically? We will just have a few pages and the material will be retrieved from our server, therefore SSG makes perfect sense in this scenario.
We may presume every topic contains at least two crucial pages:
articles: This page is in charge of retrieving and showing all articles.
article: This page is in charge of retrieving and displaying a single article.
Using the getStaticProps method on our articles page, we can utilize SSG to get all of our article entries:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
export default function Articles({
articles
}) {
return (
<div>
<h1>My latest articles</h1>
{articles.map((article) => (
<h2>{article.title}</h2>
<h3>{article.headline}</h3>
<h3>{article.topic}</h3>
))}
</div>
);
}
export async function getStaticProps() {
return {
props: {
articles: await fetchArticles(),
}
};
The fetchArticles function will be called during the construction process. It will retrieve our external data and pre-render our page’s content.
What if we wish to add a little function to our simple article website?
Assume we are a well-known individual, and our fans want to purchase products from our store. We want to convert our simple article website into an eCommerce application and begin selling products like shirts, mugs, and stickers.
The same pre-rendering approach may be used by all of the components of an eCommerce application. We can access our data at build time by using the getStaticProps method.
What might we do for our checkout session? Every eCommerce application must include a checkout session in which the user completes their purchase. In this instance, the data may be retrieved from our database throughout the construction process, but it may differ for each user.
For rendering our page, we may utilize client-side fetching on top of SSG. We can construct our simple component without utilizing any data fetching functions at build time, and then inside our component, we can use a data fetching library like SWR to collect our data on the client side:
1
2
3
4
5
6
7
8
9
10
import useSWR from "swr";
export default function Checkout() {
const {
data,
error
} = useSWR("/api/checkout", fetcher)
if (error) return <div>Something went wrong...</div>
if (!data) return <div>Loading...</div>
return <div>Cart: {data.items.length}</div>
}
Most of the time, using getServerSideProps and server-side rendering will be enough. The issue is that you may lose some of the benefits and functionality that SSG provides for Next.js apps. On each request, the page will be pre-rendered using the data supplied.
The fact that the website is pre-rendered during development improves performance and gives it a quicker feel.
A CDN server may also fetch the HTML, which can help with speed.
Another advantage of SSG is that it eliminates the need to query the database. Because the page is pre-rendered at build time, you’ll be able to render it even if your database goes down.