Table of contents
Introduction
If you've been paying attention to the web development world, you've probably heard a lot about Next.js. Today I'm going to tell you all I know about Next.js,probably not all of them but something to base your journey into Next.js on.
Next.js is an opinionated open-source web development framework built on top of Node.js that enables server-side rendering and the generation of static websites in React-based web applications. React is a JavaScript library used to create web applications that are rendered in the client's browser using JavaScript.
Next.js was created by Vercel and allows you to create client-side and server-side applications. The framework has a zero-config approach and allows you to write scalable, performant React applications. Next.js provides with a variety of opinionated features out of the box, including:
- Routing using Next's built-in router making use of React Hooks.
- Image Optimization using Next's
Image
component built upon htmlimg
tags. - Fast Refresh to update the page without reloading.
- Hosting of Static Files and Assests in a
public
folder. - Built-in CSS/Sass Support.
- API Routes for easier API management. For any file added to the pages/api directory is treated as an API endpoint with a corresponding API route that can be used for backend functionality and can also be used to run serverless functions.
- Internationalization Routing
- File-System Based Routing. Organizing files and subdirectories within a
pages
directory automatically results in corresponding routes. - TypeScript Support
- Code Splitting and Bundling to load code on demand.
- Server Side Rendering to render the page on the server.
In this tutorial, we’ll learn the basics of Next.js. In the process, you’ll see how simple it can be to create a web application powered by Next.js and hosted on Vercel.
Our simple Next.js web app has:
- Navigation between pages
- Dynamic Routes
- API Routes
- Styling, Assets and Metadata for SEO.
For this tutorial, you should already be familiar with JavaScript. It’s okay if you’re not familiar with Next.js as the tutorial begins with an introduction to the framework.
Installation and Setup
The create-next-app
CLI tool is the simplest way to create a new Next.js application. It can be installed using npm. You can create a new Next.js application using the following command:
npx create-next-app my-next-app
Once the installation is finished, you can navigate to the directory where the application was created and open it in your prefered code editor. The default project structure is as follows:
my-next-app
├── pages
├── public
├── pages
│ ├── api
│ │ └── hello.js
│ ├── _app.js
│ └── index.js
├── styles
│ ├── global.css
│ └── Home.module.css
├── .env.local
├── .eslintrc.json
├── .gitignore
├── README.md
├── next.config.js
├── package.json
└── yarn.lock
Once everything is in place, you can start the application by opening the terminal and navigating to the directory where the application was created in the terminal. You can then run the following command to start your application.
yarn dev
# or
npm run dev
We are running our application in the development server. This means that the application will be served from the localhost:3000 port locally on our machine. To preview the application running, you can open a browser and navigate to localhost:3000.
We should get a preview of the application like this:
If everything is setup correctly, you should see the page on the browser without any issues. Next, we locate the index.js
file in the pages
directory. This file is the entry point for the application. We are going to replace all the context of the index.js
with a simple react component.
export default function HomePage() {
return <div>Thank u, next</div>;
}
This is how simple Next.js works. Whenever you put a JavaScript file in the pages
directory, it is automatically a route, no configuration needed (so a about.js
will become localhost:3000/about
). Along with this, you can include dynamic routes (as in, ones with variable names, and do shallow routing (meaning you can change the URL without calling data fetching methods again).
Your new change once saved should reflect in the browser automatically using React Fast Refresh. No need to refresh the page and no configuration is needed. It all works out of the box!
Navigation Between Pages
In Next.js, a page is a React Component exported from a file in the pages directory. The Next.js app we built so far only has one page. Many different pages can be found on most websites and web apps. Let's look at how we can expand our app with more pages. It's simple to create a new page. Just create a file in the pages
directory to get a route based on the file name.
Lets begin by replacing our pages/index.js
file with the following:
export default function HomePage() {
return <h1>Home Page</h1>;
}
In the pages
directory, create two new files called about.js
and contact.js
. Add the following to the files respectively:
// about.js
export default function AboutPage() {
return <h1>About Page</h1>;
}
// contact.js
export default function ContactPage() {
return <h1>Contact Page</h1>;
}
In the browser, go to either the /about
or /contact
route. The pages should display without issue. The next thing is to navigate between the pages on the browser.
The <a>
HTML element is used to link between pages on a website. In Next.js, you use the Link Component from next/link
to wrap the <a>
tag. Link
enables client-side navigation to another page inside the application. Unlike the native <a>
element, the Link
component does not refresh the page when the page is navigated, allowing client-side navigation between two pages in the same Next.js project.
To start navigation, let's first import the Link
component. At the top of your pages/index.js
route, import the Link
with the following:
import Link from "next/link";
Then replace the content of the HomePage
component with the following:
export default function HomePage() {
return (
<div>
<h1>Home Page</h1>
<div>
<Link href="/about">
<a>About</a>
</Link>
</div>
<div>
<Link href="/contact">
<a>Contact</a>
</Link>
</div>
</div>
);
}
Now let's add a Link
component on the about.js
and contact.js
pages to link back to the home component. Import it first then replace the <h1>
in both pages with the following respectively:
// about.js
export default function AboutPage() {
return (
<div>
<h1>About Page</h1>
<Link href="/">
<a>Home</a>
</Link>
</div>
);
}
// contact.js
export default function ContactPage() {
return (
<div>
<h1>Contact Page</h1>
<Link href="/">
<a>Home</a>
</Link>
</div>
);
}
How the Link
component works is that it wraps around the <a>
tag and adds a href
attribute to it. The href
attribute is the URL or the page route that the link will navigate to. In the /about
and /contact
pages, the href
attribute on the Link
component is set to /
. The router will automatically route files named index
to the root of the directory. pages/index.js
will be associated with the /
route.
We can now preview the index.js
page in the browser and see the Link
component working in action.
Styling, Assets and Metadata
Assets
Next.js has support for static public folders and serves all static site images, CSS, Google Site Verification,robots.txt, and favicon.ico. In the root directory with the public folder, you access everything in production or built time.
Let's test it out by rendering a static image on a page. In the pages
directory, create a new pages/image.js
page. We are simply going to render a static image stored in the public folder on the page.
In the public folder, add a new image file and mind the name of the image and file extension. In the image.js
file, let's add the following:
export default function ImagePage() {
return (
<div>
<h1>Next.js Logo</h1>
<img src="/logo.png" alt="Logo" width={128} height={128} />;
</div>
);
}
The page should render as above.
If everything is set up correctly, the image should display correctly in the browser. You notice that the image's src
is not a relative path, but rather a route. This is a unique Next.js feature. The static images route-path allows the same image to be referenced in the <img>
tag code on several pages utilizing the root URL. For example, src=/logo.png
.
Image Optimization
Next.js 10 welcomed a built-in image optimization API, next/image
, as a canonical form for native automatic image optimization. Since we are using an <img>
tag here and we are going all in Next.js, let's replace the <img>
tag with the Image
component from next/image
.
At the top of the image.js
file, import the Image
component:
import Image from "next/image";
Replace the <img>
tag with the following:
<Image src="/logo.png" alt="Logo" width={128} height={128} />
That's it. Next.js takes it from here. Our images will be optimized and will be loaded lazily by default.
Metadata
There is a Head
component with Next.js that can be imported from next/head
. This Head
component is basically a built-in component that Next.js provides to append elements, such as title and meta tags, to the <head>
element to improve SEO. The advantage of this component is that, it is on a per page basis, and not a global level meaning each page can have specific SEO information related to that page!
Let's improve the pages/index.js
file with the Head
component. Import the Head
component first:
import Head from "next/head";
Let's improve the HomePage component:
export default function HomePage() {
return (
<div>
<Head>
<title>Home</title>
<meta name="description" content="Next.js is a React Framework" />
</Head>
...
</div>
);
}
Try accessing localhost:3000. The browser tab should now say “Home”. By using your browser’s developer tools, you should see that the title and meta tags were added to . You can go ahead and add the Head
component to the about.js
and contact.js
pages as well.
Styling
Next.js offers many ways to support CSS in your application. Whether you prefer utility CSS with its classes or prefer CSS-in-JS, Next.js has you covered. We are going to implement two styling options. Global CSS for the whole application and component-specific styling.
We want to style the <h1>
tags in the pages but first let's make into a resuable React component and use it across different pages. In the root directory, create a components
directory. Create a new components/Header.js
file and add the following:
export default function Header({ title }) {
return <h1>{title}</h1>;
}
We can refactor the pages/index.js
file to use the Header
component. Import the Header
component first:
import Header from "../components/Header.js";
In the pages/index.js
file, replace the <h1>
with the following:
<Header title="Home Page" />
We can save our file and the component will render correctly. Now that we have abstracted the <h1>
tag, we can use it in the about.js
and contact.js
pages as well. Let's focus on styling the component. Next.js support CSS Modules out of the box.
CSS modules allow you to isolate your CSS by creating files for style-specific components. They are very easy to use, as they are simple CSS but have .module.css
as the extension.
In the components
directory, add a new Header.module.css
file and add the following:
.title {
font-size: 48px;
color: red;
}
In the Header components file, we can import the style as:
import styles from "./Header.module.css";
Replace the <h1>
tag with the following:
<h1 className={styles.title}>{title}</h1>
We are using the styles from the Header.module.css
file as a class on the <h1>
tag. We can see the styling in action on the browser.
Next, we are going to implement global styling for our application. The styles created in global.css
will then apply to your entire application. To implement global styling, we add the styles to a global.css
file in the styles
directory and import it in the _app.js
file as the App component initializes all the pages in your Next.js pages.
Let's change the background color of the application. In your styles/global.css
, add the following:
html {
background-color: #63cad6;
}
In the _app.js
file, import the stylesheets from the styles
directory as follows:
import "../styles/globals.css";
We can also navigate between the pages to see that the style is affecting the whole application snd view the changes from browser. Everything is working 🎉. You can take a break now, you have come a really long way.
Pre-Rendering and Data Fetching
If you took a break, welcome. If you have stayed this far, keep on, I believe in you. Let's get back to business.
Rendering in modern web applications is the conversion of the JSX into the HTML representation of our UI. Where and when rendering happens determines the type of rendering method.
In a simple React application the browser receives an empty html shell along with the javascript instructions to construct the UI, this is called Client-side Rendering.
Next.js pre-renders every page by default. Pre-rendering means that the HTML is created in advance on a server rather than on the user's device using Javascript. This means that while the rendering work is being done on a Client-side Rendered app, the user will see a blank page, whereas in a Pre-rendered app, the user will view the completed UI. Because of the consequences for SEO and social media sharing, it's typically suggested that you utilize pre-rendering unless you have a specific use case for client-side rendering, so let's have a look at the Next.js pre-rendering methods.
- Static Generation: Generates the full HTML for the page content to be rendered in response to every user request.
- Server-side Rendering: For each route that the user can access, a static HTML file is prepared ahead of time. These static HTML files may be stored on a server or a CDN and retrieved by the client as needed.
- Incremental Static Regeneration: Extends the power of static sites by adding some server-side rendering (SSR) goodies on top. It allows the regeneration of static pages during runtime.
- Client-side Rendering: Rendering the HTML of the page on the user's device.
Next.js supports data fetching with both SSR and Static generation. Following functions provided by Next.js make this possible.
getStaticProps
: Used with Static generation to render datagetStaticPaths
: Used with Static generation to render dynamic routesgetServerSideProps
: Applicable to SSR
Check out this site next-usecase.thcl.dev to see practical use cases of each of the functions.
Static Generation
Let's see how we can use the functions provided by Next.js to fetch data. In the pages
directory, create a new static.js
file. We are going to implement Static Generation in this file. Let's add the following:
import Header from "../components/Header.js";
export default function StaticPage() {
return (
<div>
<Header title="Static Generation" />
<p>This is a static page</p>
</div>
);
}
The getStaticProps
method can be used inside a page to fetch data at build time, e.g. when you run next build. Once the app is built, it won't refresh the data until another build has been run. To use the method, we export an async function in the page. The response from the data fetch is passed as a prop
to our page component. We are going to fetch a simple data from an external AP using fetch
export async function getStaticProps(context) {
const res = await fetch(`https://edge-functions-json-response.vercel.app/`);
const data = await res.json();
if (!data) {
return {
notFound: true,
};
}
return {
props: { data }, // will be passed to the page component as props
};
}
Once we have fetched the data, we can destructure the props and render the data in the page. Let's update our StaticPage
component to render the data:
import Header from "../components/Header.js";
export default function StaticPage({ data }) {
return (
<div>
<Header title="Static Generation" />
<p>{data.message}</p>
</div>
);
}
We can preview from our browser and see the data is being rendered.
During development, the data is fetched every time the page is rendered. During production, the data is fetched only once.
Server-Side Rendering
Once we have implemented Static Generation, let's move on to Server-side Rendering. In the same pages directory, create a new server.js
file. We are going to implement Server-side Rendering in this file. Let's add the following:
import Header from "../components/Header.js";
export default function ServerPage({ data }) {
return (
<div>
<Header title="Server-Side Rendering" />
<p>{data.message}</p>
</div>
);
}
export async function getServerSideProps(context) {
const res = await fetch(`https://edge-functions-json-response.vercel.app/`);
const data = await res.json();
if (!data) {
return {
notFound: true,
};
}
return {
props: { data }, // will be passed to the page component as props
};
}
The code for the getServerSideProps
method is similar to the code for the getStaticProps
method. The only difference is the mode of rendering. getServerSideProps
is used fetch data on runtime. getStaticProps
is used to fetch data at build time.
Incremental Static Regeneration
As I said earlier, Incremental Static Regeneration allows the regeneration of static pages during runtime. By adding a revalidate
option to the getStaticProps
method, the static data is regenerated once and server requests for new data will occure at certain time intervals.
export async function getStaticProps(context) {
const res = await fetch(`https://edge-functions-json-response.vercel.app/`);
const data = await res.json();
if (!data) {
return {
notFound: true,
};
}
return {
props: { data }, // will be passed to the page component as props
revalidate: 1, // revalidate after 1 second
};
}
Client-Side Rendering
Next.js supports client-side rendering but it is not recommended. To use client-side rendering in Next.js, you can use the useEffect
hook to make the request or the useSwr
custom hook made by Vercel engineers which implements stale-while-revalidate
.
We are going to use the useSwr
hook to make the request as it is the recommended by the Next.js team. For more information on using SWR, check out the SWR docs. Install swr
using your terminal with the following:
yarn add swr
# or
npm install swr
In the pages
directory, create a new client.js
file and add the following:
import useSWR from "swr";
import Header from "../components/Header.js";
const fetcher = (...args) => fetch(...args).then((res) => res.json());
export default function ClientPage() {
const { data, error } = useSWR("/api/client", fetcher);
if (error) return <div>Failed to load</div>;
if (!data) return <div>Loading...</div>;
return (
<div>
<Header title="Client-Side Rendering" />
<p>{data.message}</p>
</div>
);
}
Note that fetching data from an external source with localhost will result in a CORS error. To fix this, you can fetch the data on an API Route and fetch the data from the API route.
Dynamic Routes
Next.js defines routes based on the concept of pages. Every Next.js project comes with a pages
folder. The structure of the pages
folder determines the structure of your routes and every file inside that folder maps to a route in your application. For static routing, you can create a new route by adding new files to the pages
directory. For dynamic routes, it is a little bit different.
Dynamic routing refers to generating routes (URLs) to serve individual pages based on data which is subject to change. Next.js handles dynamic routes by supporting brackets around parameters (e.g [id]
) as a filename.
Let's add a new file to our pages
directory and name it [id].js
. We will use this file to generate a dynamic route. Now anytime we visit a route which is not already defined, the component on the [id].js
page will be rendered.
Let's add the following to our pages
directory:
import Header from "../components/Header";
export default function DynamicPage() {
return (
<div>
<Header title="Dynamic Page" />
</div>
);
}
Now let's visit localhost:3000/duncan. Since we haven't already defined a static route as duncan.js
in the pages directory, Next.js will fall back to [id].js
page and Next.js will get the route parameters passed in and then use it as a name for the route.
As everything is working, the let's try and access the route parameters on the page itself. Open the pages/[id].js
file and update it with the following:
import Header from "../components/Header";
import { useRouter } from "next/router";
export default function DynamicPage() {
const router = useRouter();
return (
<div>
<Header title="Dynamic Page" />
<p>{router.query.id}</p>
</div>
);
}
Here, we pull out the route parameters from the query object with the useRouter
hook. Next.js also supports dynamic nested routes. Learn more about the useRouter
from the Next.js docs
API Routes
In addition to creating page routes, which means pages are served to the browser as Web pages, Next.js can create API routes. This is an exciting feature since it means that Next.js may be used to develop a frontend for data that is stored and retrieved by Next.js, using fetch requests to transmit JSON. API routes are mapped to the /api
endpoint and are stored in the /pages/api/
subdirectory. When designing full-stack applications, this capability comes in handy. We write Node.js code in those routes (rather than React code). You move from the frontend to the backend, but it's a seamless transition with Next.js.
Create a /pages/api
directory if you don't already have it. Create a new hello.js
file in the directory. Add the following to the file:
const handler = (req, res) => {
return res.json({ hello: "world!" });
};
export default handler;
It's that simple. Now on your browser, access localhost:3000/api/hello. It should return a json response with the message { hello: "world!" }
. As a test, I want you to create an API route on /api/client
to provide data for the /client
page we wrote in the Client-Side Rendering section. The browser should return something like the image below.
On the client-side, we can fetch the data from the API endpoint and use it in our application. Let's create a new static page to use the data from the endpoint.
We send a GET
request when fetching the data. We can also customize the API endpoint to accept other forms of requests including PUT
, POST
and DELETE
Create a new /pages/use-api.js
file and add the following:
import Header from "../components/Header.js";
export default function StaticPage({ data }) {
return (
<div>
<Header title="Static Generation To Use API" />
<p>{data.hello}</p>
</div>
);
}
export async function getStaticProps(context) {
const res = await fetch("http://localhost:3000/api/hello");
const data = await res.json();
if (!data) {
return {
notFound: true,
};
}
return {
props: { data }, // will be passed to the page component as props
};
}
Now we are able to access the data from the API endpoint in our application. API routes also allow us to define dynamic path parameters in the same manner as pages. For example, using the structure pages/api/[id].js,
we may design a route to obtain a user by user ID. The /api/:id
route will be created, and the id
can be found in the query parameter.
Let's implement a dynamic API route. Create a new /pages/api/[id].js
file and add the following:
export default function handler(req, res) {
const {
query: { id },
} = req;
return res.json({ user: { id, name: "Duncan" } });
}
No need for next/router
this time, the routes can be accessed on the req
object provided internally by Next.js.
Visit localhost:3000/api/123 on the browser to see it in action.
Under the hood, this is all powered by Micro, a library that powers asynchronous HTTP microservices, made by the same team that built Next.js.
Deployment
Congratulations, you are ready to deploy your Next.js application to production. There are many hosting services online for applications. Seeing that the fantastic creators of Next.js created Vercel, it's the smartest choice to go with Vercel as your Next hosting.
Below are the steps in deploying your Next.js application to Vercel.
- Sign Up for a Vercel account.
- Push your project code to Github.
- Once you’re signed up, import your repository on Vercel. You can do so from here: vercel.com/import/git. You’ll need to Install Vercel for GitHub.
- Since it's a Next.js project, everything will be setup correctly. We have to choose a team and then deploy.
- Wait for your project to deploy and hurray!
PS: Don't deploy on Fridays. The weekends are supposed to be sacred.
Conclusion
Thank you for staying with me this far. In this tutorial, we’ve seen how it’s easy to create a web application powered by Next.js and hosted on Vercel. We’ve also shown how quickly we can set up pages in a Next.js app, how to style the application both on the component and global level, how to navigate between pages and also my favorite feature of Next.js, how to create API routes. Thank you one again. Till Next time.