Introduction to Next.js basics
Next.js is a framework built on React for building full-stack web applications, and it offers several optimizations. In this article, I will introduce the most essential concepts in Next.js, which are pre-rendering and Incremental Static Regeneration (ISR), and then will mention some optimizations.
Pre-rendering
Pre-rendering is one of the most important concepts in Next.js, and it means that Next.js by default generates HTML for each page in advance, without loading JavaScript. There are two forms of pre-rendering:
- Static Generation: The pre-rendering method generates HTML at build time, the pre-generated HTML is then reused upon each request. The
getStaticProps
method is used to fetch the data from the source (an external API, a database, etc), and returns an object of the following format:
{
props:{
data,
},
where “data” is the data retrieved from the data source, and which will be passed to the component as a prop for rendering it.
- Server Side Rendering (SSR): The pre-rendering method generates HTML during runtime upon each request. The
getServerSideProps
method is used to fetch the data from given source, and returns an object of the same format as the one returned bygetStaticProps
.
The developer has the possibility to choose which type of pre-rendering to use per page. It is recommended to use static generation (with and without data, with data can be for instance, fetching data from local storage, some api, or querying a database) whenever possible as the page can be built once and then served by CDN. Examples of pages for which to use static generation: blog posts, marketing pages, help and documentation, and E-commerce product listings. Basically, if you can generate a given page ahead of the user’s request, then you should use static generation. Take look at static-generation-without-data.png and static-generation-with-data.png.
On the other hand, if you need user’s request in order to generate the page, then server-side-rendering is what to go for. Use cases to use SSR, your page is frequently updated with data, and the content may change on each request. SSR is slower as compared to static generation, since it generates the HTML on each request. Both, static generation and server-side rendering optimize SEO since pre-rendering HTML results in faster initial page loading, which is used as a factor in SEO. Also, SSR achieves progressive enhancement, which means that essential content is served even when Javascript is disabled, and which makes the content accessible to search bots. You can take a look at the figure server-side-rendering-with-data.png.
On the other hand, client-side rendering results in poor SEO since the browser needs to download and execute before rendering the page (slower initial page rendering). Check out this figure : client-side-rendering.png
In the case when it’s not needed to pre-render the data (as an example, a private dashboard that is not indexed by search engines, and SEO doesn’t matter), developers can use client-side rendering, which means statically generate HTML that don’t require external data, and then use Javascript on the client side to fetch the data and update the remaining parts.
❓❓❓ Now a question, in static generation, what if you want to update or create a static page after you’ve built your site? Let’s say you have a blog website, which was already built and running in production, and one of your static pages, for instance, a new post was created, so the list of posts in the home page needs to be updated…Surely you don’t wanna rebuild and redeploy your website on each change that takes place. Let’s see what utilities Next.js offers so that your website gets updated automatically 😉😁🔥🔥
Incremental Static Regeneration (ISR)
Next.js provides Incremental Static Regeneration (ISR) that allows to update specific static pages that had changed without rebuilding your entire website. Initially, Next.js provided a utility that allows to purge the cache in time intervals, by specifying the attribute revalidate
in the the getStaticProps
method in the return object, or when specifying it in fetch
.
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 10 seconds
revalidate: 10, // In seconds
}
}
In the code snippet above, Next.js will purge the cache every 10 seconds, if it receives a new request in 10 seconds, Next.js will serve the old page, and starts regenerating the new page in the background, in the upcoming request, the up-to-date page will be served. “getStaticProps” is called during build time, and can be called during runtime when the “revalidate” is specified.
Starting from v12.2.0, Next.js also supports On-Demand Incremental Regeneration so that developers can manually purge the cache for a specific page, without specifying a time interval, which makes it easier and efficient to update your site when some content of your website changes (Eg, your e-commerce website and the price for a specific product was updated). In this case, developers don’t need to use the “revalidate” attribute in the “return” object of the getStaticProps method. The default is “false”, which means no revalidation in the page unless the developer calls the “revalidate()” method. On-demand Revalidation can be achieved by revalidatePath()
or revalidateTag()
. The first allows to purge a specific url from the cache. Example:
revalidatePath('/blog/post-234')
It will clear the cache from the data that has as url /blog/post-234
. We can also use revalidatePath()
to clear the cache from a page or layout path:
revalidatePath('/blog/[slug]', 'page')
The first argument is for the path (a url or path in the filesystem), the second argument to indicate if the type is a “page” or “layout”. This will revalidate any URL that matches the provided page in the next visit.
The second allows to use tags to revalidate purge the data in the cache that fall under a certain tag. The tag is created when fetching the data:
fetch(url, { next: { tags: [...] } });
The tags is an array that can contain multiple ones, and when you invoke
revalidateTag('yourTag')
All the paths that are associated with yourTag
get cleared from the cache.
Note: ISR allows to combine performance with up-to-date data, as only specific pages are regenerated in the background when needed, and then served directly from the cache to upcoming requests. However, if many pages need to be regenerated at once, it can add load to the server.
☄️🔥⚡️ As a summary, if you can always generate a page before user’s request, then use static generation for that page. If the static page needs to be updated with a certain frequency, then use Incremental Static Regeneration. If somehow a given page is frequently updated with data and needs to be generated on each user’s request, then use server-side-rendering. Server-side-rendering can be slow as it generates pages on each request time, and when the data does not need to be pre-rendered (and SEO does not matter), you can use client-side-rendering in order to pre-render the parts that don’t require external data and then use browser’s javascript to populate the remaining parts.☄️🔥⚡️
Some optimizations
- Code splitting & pre-fetching: Next.js initially loads only the portion of code that is necessary. For instance, when the homepage is initially loaded, the code of other pages is not. In production build, whenever the Link component (will speak of shortly) appears in the viewport, the code of those pages in the links is prefetched in the background, and hence once you click on the link, the transition to the page will be super fast, near relatime.
- Link vs <a> tag: When HTML <a> is used, the whole page is refreshed, however, Next.js comes with the Link component that allows client-side navigation between pages, which means the navigation is done through javascript giving more control to the developer, instead of the browser’s default navigation, in such a way that only the changed part is loaded, instead of loading the whole page.
- Image vs <img>: When using <img>, the developer needs to manually handle the lazy loading when the image appears in the viewport, and how to make it responsive on different screen sizes. Next.js Image component is an extension to <img> and meets the above optimizations out of the box.
- Script component: It contains several props that allows you to run a script at a different stage of your application. For example, the strategy prop contains values beforeInteractive, which means the script is run before the app appears in the viewport, afterInteractive when the some of the app appears in the viewport, and lazyOnload to load a script when the browser is idle. It also contains the onError prop for catching errors when a script fails to load.
Some Next.js App Conventions
In order to create an app in Next.js, you can check out this setup guide. Once you create the app, you’ll need to respect some directory conventions:
- pages/: In Next.js, a page is a React component that is exported from a file in this directory. Pages are associated with routes according to their file names. For example,
pages/index.js
is associated with the/
route, andpages/posts/nextjs-post.js
is associated with the/posts/nextjs-post
route. - pages/index.js: It is the home page.
- pages/posts/[id].js: In order to indicate dynamic routes.
- pages/_app.js: This is the entry to all pages, and it is used to do some effect that applies to all the pages, such as global CSS.
Styling a Next.js App
Next.js offers to style your application in several ways:
- CSS modules
- Sass
- PostCSS libraries such as Tailwind CSS.
- CSS-in-js libraries such as styled-jsx, styled-components, and emotion.
Tailwind CSS is recommended to be used with Next.js. Tailwind has become popular and powerful in recent years, and it is already lightweight (its file size is about 27 kb), and it is supported out of the box in Next.js. Another major advantage of Tailwind is that only the styles used in your application will be included in your app’s final bundle, and leave out other Tailwind utility classes that are not used, which will make your app’s size lighter and enhances performance.
🏀 This article introduced the essential basics of Next.js, surely there is a lot more to delve into, for more details and more concepts, and for building your first Next.js app, you can follow this next.js tutorial. The end result of the tutorial is deplyed on vercel and accessible here, looks something like:
Happy reading 😊🤓!