Getting Started With Next.js
Lately, Next.js has termed itself The React Framework for Production, and with such bold claim comes a bevy of features that it offers to help you take your React websites from zero to production. These features would matter less if Next.js isn’t relatively easy to learn, and while the numerous features might mean more things and nuances to learn, its attempt at simplicity, power, and perhaps success at it is definitely something to have in your arsenal.
As you settle in to learn about Next.js, there are some things you might already be familiar with and you might even be surprised at how it gives you a lot to work with that it might seem almost overwhelming at face value. Next.js is lit for static sites and it has been well-engineered for that purpose. But it also takes it further with its Incremental Static Regeneration that combines well with existing features to make development a soothing experience. But wait, you might ask. Why Next.js?
This tutorial will be beneficial to developers who are looking to get started with Next.js or have already begun but need to fill some knowledge gaps. You do not need to be a pro in React, however, having a working experience with React will come in handy.
But Why Next.js?
- Relatively easy to learn.
That’s it. If you’ve written any React at all, you’d find yourself at home with Next.js. It offers you advanced tools and a robust API support, but it doesn’t force you to use them. - Built-in CSS support.
Writing CSS in component-driven frameworks comes with a sacrosanct need for the “cascade”. It’s why you have CSS-in-JS tools, but Next.js comes out of the box with its own offering — styled-jsx, and also supports a bevy of styling methodologies. - Automatic TypeScript support.
If you like to code in TypeScript, with Next.js, you literally have automatic support for TypeScript configuration and compilation. - Multiple data fetching technique.
It supports SSG and/or SSR. You can choose to use one or the other, or both. - File-system routing.
To navigate between one page to another is supported through the file-system of your app. You do not need any special library to handle routing.
There are many more other features, e.g. using experimental ES features like optional-chaining, not importing react everywhere you use JSX, support for APIs like next/head
that helps manage the head of your HTML document, and so on. Suffice to say the deeper you go, the more you enjoy, appreciate, and discover many other features.
Requirements For Creating A Next.js App
Creating a Next.js app requires Node.js, and npm
(or npx
) installed.
To check if you have Node.js installed, run the command in your terminal:
# It should respond with a version number node -v
Ideally, npm (and npx) comes with your Node.js installation. To confirm that you have them installed, run the commands in your terminal:
# Run this. It should respond with a version number npm -v # Then run this. It should also respond with a version number npx -v
In case any of the commands above fails to respond with a version number, you might want to look into installing Node.js and npm.
If you prefer the yarn package manager instead, you can run install it with the command:
# Installs yarn globally npm i -g yarn
Then confirm the installation with:
# It should also respond with a version number yarn -v
Creating A Next.js App
Getting the requirements above out of the way, creating a Next.js can be done in two ways, the first being the simplest:
- With create-next-app, or
- Manually
Creating A Next.js App With create-next-app
Using create-next-app is simple and straightforward, plus you can also get going with a starter like Next.js with Redux, Next.js with Tailwind CSS, or Next.js with Sanity CMS etc. You can view the full list of starters in the Next.js examples repo.
# Create a new Next.js app with npx npx create-next-app <app-name> # Create a new Next.js app with npm npm create-next-app <app-name> # With yarn yarn create next-app <app-name>
If you’re wondering what the difference between npm and npx is, there’s an in-depth article on the npm blog, Introducing npx: an npm package runner.
Creating A Next.js Project Manually
This requires three packages: next
, react
, and react-dom
.
# With npm npm install next react react-dom # With yarn yarn add next react react-dom
Then add the following scripts to package.json
.
"scripts": { "dev": "next dev", "start": "next start", "build": "next build" }
dev
starts Next.js in development mode.start
starts Next.js in production mode.build
builds your Next.js app for production.
Folder Structure
One salient thing you might notice after creating a Next.js app is the lean folder structure. You get the bare minimum to run a Next.js app. No more, no less. What you end up with as your app grows is up to you more than it is to the framework.
The only Next.js specific folders are the pages
, public
, and styles
folder.
# other files and folders, .gitignore, package.json... - pages - api - hello.js - _app.js - index.js - public - favicon.ico - vercel.svg - styles - globals.css - Home.module.css
Pages
In a Next.js app, pages is one of the Next-specific folders you get. Here are some things you need to know about pages
:
- Pages are React components
Each file in it is a page and each page is a React component.
// Location: /pages/homepage.js // <HomePage/> is just a basic React component export default HomePage() { return <h1>Welcome to Next.js</h1> }
-
Custom pages
These are special pages prefixed with the underscore, like_app.js
._app.js
: This is a custom component that resides in the pages folder. Next.js uses this component to initialize pages._document.js
: Like_app.js
,_document.js
is a custom component that Next.js uses to augment your applications<html>
and<body>
tags. This is necessary because Next.js pages skip the definition of the surrounding document’s markup.
-
File-based routing system based on pages
Next.js has a file-based routing system where each page automatically becomes a route based on its file name. For example, a page atpages/profile
will be located at/profile
, andpages/index.js
at/
.
# Other folders - pages - index.js # located at / - profile.js # located at /profile - dashboard - index.js # located at /dashboard - payments.js # located at /dashboard/payments
Routing
Next.js has a file-based routing system based on pages
. Every page created automatically becomes a route. For example, pages/books.js
will become route /book
.
- pages - index.js # url: / - books.js # url: /books - profile.js # url: /profile
Routing has led to libraries like React Router and can be daunting and quite complex because of the sheer number of ways you might see fit to route section of your pages in your Next.js app. Speaking about routing in Next.js is fairly straightforward, for the most part of it, the file-based routing system can be used to define the most common routing patterns.
Index Routes
The pages
folder automatically has a page index.js
which is automatically routed to the starting point of your application as /
. But you can have different index.js
s across your pages, but one in each folder. You don’t have to do this but it helps to define the starting point of your routes, and avoid some redundancy in naming. Take this folder structure for example:
- pages - index.js - users - index.js - [user].js
There are two index routes at /
and /users
. It is possible to name the index route in the users
folder users.js
and have it routed to /users/users
if that’s readable and convenient for you. Otherwise, you can use the index route to mitigate the redundancy.
Nested Routes
How do you structure your folder to have a route like /dashboard/user/:id
.
You need nested folders:
- pages - index.js - dashboard - index.js - user - [id].js # dynamic id for each user
You can nest and go deeper as much as you like.
Dynamic Route Segments
The segments of a URL are not always indeterminate. Sometimes you just can’t tell what will be there at development. This is where dynamic route segments come in. In the last example, :id
is the dynamic segment in the URL /dashboard/user/:id
. The id
determines the user that will be on the page currently. If you can think about it, most likely you can create it with the file-system.
The dynamic part can appear anywhere in the nested routes:
- pages - dashboard - user - [id].js - profile
will give the route /dashboard/user/:id/profile
which leads to a profile page of a user with a particular id.
Imagine trying to access a route /news/:category/:category-type/:league/:team
where category
, category-type
, league
, and team
are dynamic segments. Each segment will be a file, and files can’t be nested. This is where you’d need a catch-all routes where you spread the dynamic parts like:
- pages - news - [...id].js
Then you can access the route like /news/sport/football/epl/liverpool
.
You might be wondering how to get the dynamic segments in your components. The useRouter
hook, exported from next/router
is reserved for that purpose and others. It exposes the router
object.
import { useRouter } from 'next/router'; export default function Post() { // useRouter returns the router object const router = useRouter(); console.log({ router }); return <div> News </div>; }
The dynamic segments are in the query
property of the router
object, accessed with router.query
. If there are no queries, the query property returns an empty object.
Linking Between Pages
Navigating between pages in your apps can be done with the Link component exported by next/link
. Say you have the pages:
- pages - index.js - profile.js - settings.js - users - index.js - [user].js
You can Link
them like:
import Link from "next/link"; export default function Users({users) { return ( <div> <Link href="/">Home</Link> <Link href="/profile">Profile</Link> <Link href="/settings"> <a> Settings </a> </Link> <Link href="/users"> <a> Settings </a> </Link> <Link href="/users/bob"> <a> Settings </a> </Link> </div> ) }
The Link component has a number of acceptable props, href — the URL of the hyperlink — been the only required one. It’s equivalent to the href
attribute of the HTML anchor (<a>
) element.
Other props include:
Prop | Default value | Description |
---|---|---|
as |
Same as href |
Indicates what to show in the browser URL bar. |
passHref |
false | Forces the Link component to pass the href prop to its child./td> |
prefetch |
true | Allows Next.js to proactively fetch pages currently in the viewport even before they’re visited for faster page transitions. |
replace |
false | Replaces the current navigation history instead of pushing a new URL onto the history stack. |
scroll |
true | After navigation, the new page should be scrolled to the top. |
shallow |
false | Update the path of the current page without re-running getStaticProps , getServerSideProps , or getInitialProps , allows the page to have stale data if turned on. |
Styling
Next.js comes with three styling methods out of the box, global CSS, CSS Modules, and styled-jsx.
There’s an extensive article about Styling in Next.js that has been covered in Comparing Styling Methods in Next.js
Linting And Formatting
Linting and formatting I suspect is a highly opinionated topic, but empirical metrics show that most people who need it in their JavaScript codebase seem to enjoy the company of ESLint and Prettier. Where the latter ideally formats, the former lints your codebase. I’ve become quite accustomed to Wes Bos’s ESLint and Prettier Setup because it extends eslint-config-airbnb, interpolate prettier formatting through ESLint, includes sensible-defaults that mostly works (for me), and can be overridden if the need arises.
Including it in your Next.js project is fairly straightforward. You can install it globally if you want but we’d be doing so locally.
- Run the command below in your terminal.
# This will install all peer dependencies required for the package to work npx install-peerdeps --dev eslint-config-wesbos
- Create a
.eslintrc
file at the root of your Next.js app, alongside thepages
,styles
andpublic
folder, with the content:
{ "extends": [ "wesbos" ] }
At this point, you can either lint and format your code manually or you can let your editor take control.
- To lint and format manually requires adding two npm scripts lint, and
lint:fix
.
"scripts": { "dev": "next dev", "build": "next build", "start": "next start" "lint": "eslint .", # Lints and show you errors and warnings alone "lint:fix": "eslint . --fix" # Lints and fixes },
- If you’re using VSCode and you’d prefer your editor to automatically lint and format you need to first install the ESLint VSCode plugin then add the following commands to your VSCode settings:
# Other setting "editor.formatOnSave": true, "[javascript]": { "editor.formatOnSave": false }, "[javascriptreact]": { "editor.formatOnSave": false }, "eslint.alwaysShowStatus": true, "editor.codeActionsOnSave": { "source.fixAll": true }, "prettier.disableLanguages": ["javascript", "javascriptreact"],
Note: You can learn more on how to make it work with VSCode over here.
As you work along you most likely will need to override some config, for example, I had to turn off the react/jsx-props-no-spreading rule which errors out when JSX props are been spread as in the case of pageProps
in the Next.js custom page component, _app.js
.
function MyApp({ Component, pageProps }) { return <Component {...pageProps} />; }
Turning the rule off goes thus:
{ "extends": [ "wesbos" ], "rules": { "react/jsx-props-no-spreading": 0 } }
Static Assets
At some or several points in your Next.js app lifespan, you’re going to need an asset or another. It could be icons, self-hosted fonts, or images, and so on. To Next.js this is otherwise known as Static File Serving and there is a single source of truth, the public folder. The Next.js docs warns: Don’t name the public
directory anything else. The name cannot be changed and is the only directory used to serve static assets.
Accessing static files is straightforward. Take the folder structure below for example,
- pages profile.js - public - favicon.ico #url /favicon.ico - assets - fonts - font-x.woff2 - font-x.woff # url: /assets/fonts/font-x.woff2 - images - profile-img.png # url: /assets/images/profile-img.png - styles - globals.css
You can access the the profile-img.png
image from the <Profile/>
component:
// <Profile/> is a React component export default function Profile() { return { <div className="profile-img__wrap"> <img src="/assets/images/profile-img.png" alt="a big goofy grin" /> </div> } }
or the fonts in the fonts folder in CSS:
/* styles/globals.css */ @font-face { font-family: 'font-x'; src: url(/assets/fonts/font-x.woff2) format('woff2'), url(/assets/fonts/font-x.woff) format('woff'); }
Data Fetching
Data fetching in Next.js is a huge topic that requires some level of undertaken. Here, we’ll discuss the crux. Before we dive in, there’s a precursory need to have an idea of how Next.js renders its pages.
Pre-rendering is a huge part of how Next.js works as well as what makes it fast. By default, Next.js pre-renders every page by generating each page HTML in advance alongside the minimal JavaScript they need to run, through a process known as Hydration.
It is possible albeit impractical for you to turn off JavaScript and still have some parts of your Next.js app render. If you ever do this, consider doing it for mechanical purposes alone to show that Next.js truly Hydrates rendered pages.
That being said, there are two forms of pre-rendering:
- Static Generation (SG),
- Server-side Rendering (SSR).
The difference between the two lies in when data is been fetched. For SG, data is fetched at build time and reused on every request (which makes it faster because it can be cached), while in SSR, data is fetched on every request.
What they both have in common is that they can be mixed with Client-side Rendering wit fetch, Axios, SWR, React Query etc.
The two forms of pre-rendering isn’t an absolute one-or-the-other case; you can choose to use Static Generation or Server-side Rendering, or you can use a hybrid of both. That is, while some parts of your Next.js app uses Static Generation, another can use SSR.
In both cases, Next.js offers special functions to fetch your data. You can use one of the Traditional Approach to Data Fetching in React or you can use the special functions. It’s advisable to use the special functions, not because they’re supposedly special, nor because they’re aptly named (as you’ll see) but because they give you a centralized and familiar data fetching technique that you can’t go wrong with.
The three special functions are:
getStaticProps
— used in SG when your page content depends on external data.getStaticPaths
— used in SG when your page paths depends on external data.getServerSideProps
— used in Server-side Rendering.
getStaticProps
getStaticProps
is a sibling to getStaticPaths
and is used in Static Generation. It’s an async function where you can fetch external data, and return it as a prop to the default component in a page. The data is returned as a props object and implicitly maps to the prop of the default export component on the page.
In the example below, we need to map over the accounts and display them, our page content is dependent on external data as we fetched and resolved in getStaticProps
.
// accounts get passed as a prop to <AccountsPage/> from getStaticProps() // Much more like <AccountsPage {...{accounts}} /> export default function AccountsPage({accounts}) { return ( <div> <h1>Bank Accounts</h1> {accounts.map((account) => ( <div key={account.id}> <p>{account.Description}</p> </div> ))} </div> ) } export async function getStaticProps() { // This is a real endpoint const res = await fetch('https://sampleapis.com/fakebank/api/Accounts'); const accounts = await res.json(); return { props: { accounts: accounts.slice(0, 10), }, }; }
As you can see, getStaticProps
works with Static Generation, and returns a props object, hence the name.
getStaticPaths
Similar to getStaticProps
, getStaticPaths
is used in Static Generation but is different in that it is your page paths that is dynamic not your page content. This is often used with getStaticProps
because it doesn’t return any data to your component itself, instead it returns the paths that should be pre-rendered at build time. With the knowledge of the paths, you can then go ahead to fetch their corresponding page content.
Think about Next.js pre-rendering your page in the aspect of a dynamic page with regards to Static Generation. For it to do this successfully at build time, it has to know what the page paths are. But it can’t because they’re dynamic and indeterminate, this is where getStaticPaths
comes in.
Imagine you have a Next.js app with pages States
and state
that shows a list of countries in the United States and a single state respectively. You might have a folder structure that looks like:
- pages - index.js - states - index.js # url: /states - [id].js # url /states/[id].js
You create the [id].js
to show a single state based on their id
. So, it the page content (data returned from getStaticProps
) will be dependent on the page paths (data returned from getStaticPaths
).
Let’s create the <States/>
components first.
// The states will be passed as a prop from getStaticProps export default function States({states}) { // We'll render the states here } export async function getStaticProps() { // This is a real endpoint. const res = await fetch(https://sampleapis.com/the-states/api/the-states
); const states = await res.json(); // We return states as a prop to <States/> return { props: { states } }; }
Now let’s create the dynamic page for a single state. It’s the reason we have that [id].js
so that we can match the path /states/1
, or /states/2
where 1 and 2 are the id
in [id].js
.
// We start by expecting a state prop from getStaticProps export default function State({ state }) { // We'll render the states here } // getStaticProps has a params prop that will expose the name given to the // dynamic path, in this case, id
that can be used to fetch each state by id. export async function getStaticProps({ params }) { const res = await fetch( https://sampleapis.com/the-states/api/the-states?id=$ {params.id}
); const state = await res.json(); return { props: { state: state[0] } }; }
If you try to run the code as it is, you’d get the message: Error: getStaticPaths
is required for dynamic SSG pages and is missing for /states/[id]
.
// The state component // getStaticProps function // getStaticPaths export async function getStaticPaths() { // Fetch the list of states const res = await fetch("https://sampleapis.com/the-states/api/the-states"); const states = await res.json(); // Create a path from their ids: /states/1
, /states/2
... const paths = states.map((state) => /states/$ {state.id}
); // Return paths, fallback is necessary, false means unrecognize paths will // render a 404 page return { paths, fallback: false }; }
With the paths
returned from getStaticPaths
, getStaticProps
will be made aware and its params
props will be populated with necessary values, like the id
in this case.
Extras
Absolute Imports
There’s support for absolute import starting from Next.js 9.4 which means you no longer have to import components relatively like:
import FormField from "../../../../../../components/general/forms/formfield"
instead you can do so absolutely like:
import FormField from "components/general/forms/formfield";
To get this to work, you will need a jsconfig.json
or tsconfig.json
file for JavaScript and TypeScript respectively, with the following content:
{ "compilerOptions": { "baseUrl": "." } }
This assumes that the
components
folder exists at the root of your app, alongside pages, styles, and public.
Experimental ES Features
It is possible to use some experimental features like Nullish coalescing operator (??) and Optional chaining (?.) in your Next.js app.
export default function User({user) { return <h1>{person?.name?.first ?? 'No name'}</h1> }
Conclusion
According to the Next.js team, many of the goals they set out to accomplish were the ones listed in The 7 principles of Rich Web Applications, and as you work your way in and deep into the ecosystem, you’d realize you’re in safe hands like many other users who have chosen to use Next.js to power their websites/web applications. Give it a try, if you haven’t, and if you have, keep going.
Resources
- Official Next.js docs
- Create a Next.js app
create-next-app
- Next.js pages
next/link
next/head
- Next.js routing
- Next.js styling
- Static assets
- Data fetching
- Next.js FAQs
- Comparing Styling Methods in Next.js
- 7 Principles of Rich Web Applications
Articles on Smashing Magazine — For Web Designers And Developers