Ramping-up with Next.js

Richard Austin Melchior
elmmly
Published in
9 min readMay 19, 2021

What is Next.js? Is it a tool? A language? A library? Next.js is described by its creators as The React Framework for Production. In other words, it’s an abstraction of the React Javascript library used to ease the creation of production apps. This framework sets you up to bring code from inception to production quickly and efficiently through it’s rich feature-set including: File-based Routing, Page Pre-Rendering, API capabilities, Image Optimizations and more.

I found that given my experience working with React apps, getting started with Next.js was very easy. Running the command npx create-next-app bootstraps a project similarly to how create-react-app does. This out-of-the-box generator has all of the necessary structure for a running production Next.js app, including the base package.json and the file-structure necessary to facilitate the development of the app. The code can be organized in any manner, as long as the main page components are created within the pages folder. Let's take a look at some of the features, starting with File-based Routing.

File-Based Routing

I feel that routing in React tends to be a painful necessity. It has a large number of moving parts, and takes a significant amount of time, code, and effort to get right. Next.js provides a built-in system for file-based routing where the routes for your application are determined by the file structure of your app. The forced file-structure provides organization to the application that allows developers to visually see the navigation structure of the app. This code-free approach to routing is actually really intuitive and I really like the approach. Each Next.js project generated with a pages folder which is the destination for all page-level visual components used within the app. A page component is a normal React component that is loaded when a specific route is visited within the application. Let's take a look at how this works:

Given the following file structure:

The route / will load the index of the pages folder.

Given the following file structure:

The route /contact will load the contact page.

Next.js also provides the ability to utilize nested routes. To create nested routes simply create a file-structure that reflects the desired nesting for the route:

Given the following file structure:

The route /contacts will load the page component exported by the index.js file. The route /contacts/emily will load the page component exported by the emily.js file.

Another necessary feature that is included in the Next.js suite is dynamic routing. Dynamic routing is handled within this file-based routing system, in addition to static routing. The way you can “declare” a route page for a dynamic route is by naming the file using bracket notation. Lets see how this looks:

Given the following file structure:

When you declare a file using this notation, the Next.js engine knows that you intend that component to be a template for a dynamic route. When you use this naming convention for dynamic routing, you gain access to the dynamic name of the route through the router’s query object, which is stored in a variable named the same as the filename.

For example, this structure:

When this url accessed from the client: domainName.com/contacts/emily

The component’s router would contain a query object that contains the following key-value pair: { contactId: “emily” }

A developer could then use this dynamic id to query for the data required to display the entire page. This can be done a few different ways, two of which I will be discussing when we go deeper into pre-rendering.

These are the basics of the file-based routing system implemented when the Next.js framework is used. While this system takes a bit of time to get used to, it is a really intuitive way to structure the files within the app. It provides a place to pull smaller visual components together into a cohesive page, which is displayed automatically when the correct route is visited. Routing can be a very cumbersome feature to manage, and the way Next.js handles it streamlines and organizes the process much better than other solutions I’ve seen in the past.

There are more nuances and intricacies when it comes to dynamic routing, the next/router package, and the next/link component.

To learn more about each of these features check out the documentation here.

Page Pre-Rendering

Another feature that Next.js provides out of the box is Page Pre-rendering. Page pre-rendering fills a very large gap found in most apps that are developed using the React library. Single page applications are a nightmare for site engine optimization due to the fact that they only have a single page for the search engines to crawl and gather data from. The single page is usually a very basic boilerplate that has a single node which gets filled dynamically after page loads. In addition to solving this problem, pre-rendering also provides faster load times and the same smooth feel that single page applications are known for.

There are two types of pre-rendering that Next.js provides: static generation and server-side rendering. Static generation is a generation of html pages at build time for the web server to serve when the given page is requested. Server-side rendering, on the other hand, is the generation of html files on the server upon each request that is received by the server. Server-side rendering is facilitated by a different method, getServerSideProps. Let’s take a look at the two methods that facilitate static generation, getStaticProps and getStaticPaths.

Static Generation

Static generation is when the client generates static html pages at build time, before deployment of the app to the web-server. Next.js uses static generation on every defined page by default. This means that any page that does not need additional data to render is statically generated, automatically, at build time. If a page needs data in order to be rendered properly, you can inject that data into the component’s props using an async function called getStaticProps. This function must be defined within the page’s component file, but outside of the component itself.

Lets see how this looks:

When the application is built, Next.js looks for pages that export this function and execute the function, taking the return value and injecting the appropriate props into the component for the generation of the html file. This is great for pages with data that doesn’t change after the initial build, but how do we update objects that have data that transforms over time? Next.js provides a revalidate option for the return value of getStaticProps to handle this use-case.

Providing the revalidate option to the return value of getStaticProps tells the framework that we want this page to be regenerated when a request is received by the server, at most once every minute. This process is called incremental static regeneration. This type of rendering is great for pages like the above index of contacts, but how do we pre-render dynamic pages? Next.js provides the getStaticPaths method for dynamic pages so that we can maximize the benefits of pre-rendering even on pages that are programmatically created.

Notice that getStaticPaths returns a list of path objects that contain parameters about the contact object which are then used to fetch specific information about the contact itself. We have the control to determine which paths are pre-rendered, and which ones can be rendered upon request. This allows us to control the build process and manage the resources used at build time.

Server Side Rendering (SSR)

Server side rendering is similar to static generation using the getStaticProps method. The main difference between the two is that getServerSideProps is run on every single request received from the client.

Server side rendering should be reserved for pages whose data must be fetched at the time the request is made to the server. Due to the fact that the page isn’t rendered until the request is sent to the server, the time for the page to be received by the client is increased compared to Statically Generated pages.

I love ability to control when and how a page gets its data, as well as when it is rendered. It allows me to make micro-optimizations that enhance the user’s experience immensely. Setting up each type of rendering is really straightforward but each of them has their own costs and benefits. These considerations should me made carefully when choosing the method of rendering as they are directly passed onto the user of the app.

To learn more about pre-rendering check out the documentation here.

To learn more about data fetching for the renders you can find that information here.

API Routes

When I think of a full-stack framework, the picture I get is something like Ruby on Rails. Rails is an awesome framework, but its very large and has alot going on. Next.js on the other-hand is extremely lightweight in its construction. Putting together a cohesive full-stack application can be done quickly and with significantly less code. It does this by providing the ability to produce an API that can directly interact with the outside world without exposing the server related code to the users through the client. The lightweight API system is really simple to use and has the benefit of being packaged with the client itself. The definition of these API pages works similarly to how regular client pages are defined.

Let’s take a look at a simple example:

Any file created within the /pages/api directory is treated as an API handler. These API pages are not included within the client bundle at build time so we don’t have to worry about using server-side code within this directory.

To access the API for the contacts index a user could navigate to domain.com/api/contacts. Alternatively, if a user wanted to access the API for the contact “emily”, they would navigate to domain.com/api/contacts/emily. Each of these files should export a single handler function that handles the API request.

This API feature allows for an application to access data from external sources and databases in a manner that protects sensitive data from the end user and fetches the data required for the application to work properly. In other words, Next.js provides all of the benefits of the React library in a bundle that allows full-stack applications to be built seamlessly.

Check out the documentation around the API features here.

Image Optimizations

One of the most resource-consuming parts of a web application is the handling of images. Images often present a challenge to me as a developer because they have proven to be large, bulky, and slow to process. Next.js comes packaged with a really phenomenal image component that makes the optimization for images extremely straightforward. It completely removes the need to have copies of the same images in different dimensions, qualities, and formats by handling all of these pain points behind the scenes which allows for good quality images to be loaded into the application quickly.

The use of this image component is extremely simple:

Using the image component automates key image optimizations that are critical when using images on any web application. It provides the flexibility required to serve every user this type of content regardless of device size, browser compatibility or bandwidth limitations. It also increases SEO by decreasing load times of the application and handling accessibility features. I’ve worked on a number of sites that were really heavy on images and I had a rough time getting the particulars right. Next.js completely removes that headache which is a huge selling point for me, given my experience with image handling.

Learn more about Next.js image optimization here.

Conclusion

Next.js truly is an awesome React framework. It provides really straightforward solutions to common tasks and problems that we developers face when working on an application. It is lightweight, powerful, and intuitive to use. It also is built on the React library which is extremely common these days. If you’re a developer who has experience working with React apps, I highly recommend you give Next.js a spin and see how it feels.

To learn more about what Next.js has to offer check out their documentation.

--

--

Richard Austin Melchior
elmmly
Writer for

Software Engineer at GoodRx | Software Engineering Consultant with Elmmly