Next.js Core Concepts and Features

M. K. Bughowi

August 2022, 3

13 min read

Next.js Core Concepts and Features

We’re going to learn all the key features that make up Next.js. How it pre-renders pages, helps us with search engine optimization, implement data fetching, work with api routes, and many many other important features.

Let’s start with the most important question first, what exactly is Next.js and why do we need it?

What is Next.js

If we visit official Next.js website, we learn that next js is the React framework. Next.js offers a lot of features that make building large scale ready React apps easier. Let’s first of all focus on the framework part. Isn’t React itself already a framework or a library? You can use React to build complex user interfaces way easier than it would be with just javascript. Now Next.js is labeled a framework that builds up on React. The difference between a framework and a library is that a framework is bigger. It has more features than a library. It’s also giving you clear guidance on how you should write your code, structure your files and so on. And all these are things which Next.js does.

Next.js has the goal to make the life easier for you as a React developer. It enhances React by adding many core features which you have to add to React apps. Once you start using Next.js, you don’t have to add as many third-party libraries to solve common problems which you need in bigger apps and which you typically need to solve when building real production React apps.

You still write React code, build react components, but it’s a framework for production because it adds all those missing features which you typically add to React apps to really prepare them for production.

Key Features

Let’s now take a closer look at the actual features added by Next.js.

Server-side Rendering

I would say the most important key feature Next.js adds is the built-in Server-side rendering support. Server-side rendering is all about preparing the content of a page on the server instead on the client. If that page would be pre-rendered on the server and data fetching somehow could be done on the server too, when the request hits that server and then the finished page would be served to users and to the search engine crawlers. Then users would not have flickering loading state and search engines would see our page content.

React actually has built-in features that allow you to add server-side rendering, but it can be tricky to get that right and it requires extra setup. With Nextjs that becomes way easier.

File Based Routing

Another very nice feature added by Next.js is file based routing. With Next.js, you define pages and routes with files and folders. You have a special pages folder in Next.js, then your structure in that folder defines the routes and paths your page supports. Next.js still supports all the features we might want like nested routes or dynamic routes with dynamic parameters

Built-in Backend API Routes

Here is the feature which is most important for me labeling Next.js a full stack framework because Next.js also makes it easy to add back-end code to our React project. With Next.js, it’s very easy to add our own backend api using Node.js code. We don’t have to build a standalone rest api project but instead we can work on one project.

Create Next.js Project

Let’s get started and create a first Next.js project. For this we just need to run one simple command :

Terminal window
npx create-next-app <project-name>

The three important folders here are pages, public, and styles. Pages is the most important one. Styles holds some css style files. And public simply holds public resources that our page might use likes images.

Pages folder will be the most important folder because that is where we set up file based routing. This folder is important for us to define different pages that should make up our application.

File Routing

Open index.js file inside pages folder, you will see a standard react component. The index.js file will be root page. So if a request reaches our https://yourdomain.com/, then index.js will be loaded. So if you want to make url path for example https://yourdomain.com/news, then you need to create a news folder. Inside news folder, create index.js and write your react component code that you want to display when a request comes to https://yourdomain.com/news. The folder structure will be likes pages/news/index.js. Or you can create a new file called news.js and place it inside pages folder. In pages/index.js.

We can start the development server with npm run dev. Then you can visit localhost:3000 through browser.

But how if we want to implement dynamic routes in Next.js?. For that, we change the file name to [dynamicPath].js if it’s a file, or [dynamicPath] if it’s a folder. You can replace dynamicPath with any identifier that you want for your dynamic routing. This tells Next.js that this will be a dynamic page so that should be loaded for different values in your path.

When a user visits this page, to extract the concrete value entered in the url when this page is loaded, Next.js gives us a special hook. You can import useRouter from next-router. We can call this hook inside of the page simply like this.

pages/[news].js
import useRouter from 'next-router';
export default function NewsDetail() {
const router = useRouter();
return <div>Your react component</div>;
}

If we do that we get access to our router object. On that router object we get certain pieces of data and certain methods which we can call. For example we got query property which gives us access to a nested object. On that query object, we then have the identifier which we chose between the [] as a property name.

pages/[news].js
import useRouter from 'next-router';
export default function NewsDetail() {
const router = useRouter();
const path = router.query.news; // get value for visited dynamic path
console.log(path);
return <div>Your react component</div>;
}

So basically, Next.js gives us a Link component. This Link component allows us to route between pages. The Link component is only used to navigate for client-side transitions. And when I say client side, I mean literally using the HTML5 push state browser, and it’s navigating purely on the client side, there’s no request to the server. So if you’re trying to get that SPA-like fast navigation behavior, you use the Link component.

And let’s just talk about it a little bit. So it’s a little different than what you might have used before. So the first thing is that it has href prop, which is very similar to an href on anchor tag, except it actually doesn’t take an href. So you’re not gonna put a full URL here. Remember, this is client-side routing, so everything’s gonna be pretty much relative here. It’s relative to the pages directory.

For example I want to navigate to news page from home page. Then inside Home page, call Link component and pass “/news” to href props.

import Link from 'next/link';
export default function Home() {
return (
<div>
<h1>This is HomePage</h1>
<Link href="/news">Go to news page</Link>
</div>
);
}

Page Pre-Rendering

Next.js gives us two forms of pre-rendering which we can use for controlling how the pages should be rendered. It has something which is called Static Generation. And it has an alternative which is called Server-side Rendering. They run at different points of time.

1. Static Generation

When using static generation, page component is pre-rendered when you build your application. By default your page is not pre-rendered on the server when a request reaches the server, but instead it is pre-rendered when you build your site for production. That means after it was deployed, the pre-rendered page does not change, at least not by default. If you then updated the data you know that the pre-rendered page needs to change, you need to start build process again and redeploy.

If you need to add data fetching to a page component, you can do so by exporting a special function inside page component file. In there you can export a function a function called getStaticProps(). It’s job is to prepare props for this page. getStaticProps() is allowed to be asynchronous. In getStaticProps() you can execute any code that would executed during the build process. You can do whatever you want, for example fetch data from an api, or from a database. Whatever you did to get data, you need to return an object. In this object you can configure various things, but most importantly you typically set a props property here that holds another object which will be the props object you receive in your component function.

export async function getStaticProps() {
// fetch data from api
return {
props: {
news: res.result,
},
};
}

One pretty big problem is that the data could be outdated. When our data change, we have to rebuild and redeploy our site. For some websites like personal blogs, this is a great because data doesn’t change too frequently. But if data does change more frequently there is an extra property which we can add to this returned object and that’s the revalidate property.

When we add this property, we unlock a feature called Incremental Static Generation (ISR). revalidate wants a number. This number is the number of seconds Next.js will wait until it regenerates this page for an incoming request.

export async function getStaticProps() {
// fetch data from api
return {
props: {
news: res.result,
revalidate: 10,
},
};
}

If we want to fetch data inside dynamic page, then we also need to specify how many pages we want to fetch it’s data. For that, we use the getStaticPaths(){:js} function to fetch something that will be unique for each newly created page. For example, we are going to fetch slug from url.

export async function getStaticPaths() {
const response = await fetch('/api/news'); // fetch data from api
const paths = response.map((slug) => ({
params: { slug },
}));
return {
paths: paths,
fallback: false,
};
}

This function has to be return a prop called paths. We add the fallback option as false, with this, any route or path that wasn’t returned by getStaticPaths() will result in a 404 page.

2. Server-side Rendering

Sometimes you really want to regenerate page for every incoming request to server. Then there is getServerSideProps() function. This function will now run always on the server after deployment. You will still return an object here. An object with a props property because after all this function still getting the props for page component. You can still fetch data from an api here or from the file system, whatever you want to do. Any code you write in here will always run on the server.

export async function getServerSideProps() {
// fetch data from api
return {
props: {
news: res.result,
},
};
}

You can’t set revalidate property, because this getServerSideProps() function runs for every incoming request. You can work with a context parameter. With this context parameter, you can get access to the request and response object.

export async function getServerSideProps(context) {
// fetch data from api
const request = context.req;
const respose = context.res;
return {
props: {
news: res.result,
},
};
}

API Routes

API routes are special routes pages which don’t return html code, but accepting incoming http requests, post, patch, put, delete, or whatever you need with json data attached to them. Then it might do whatever you need to do. For example, store data in a database and then return json data. So you could say api routes allow you to build your own api endpoints as part of this Next.js project and they will be served by the same server.

To add api routes, you add a special folder in your pages named api. Next.js will pick up any javascript files stored in there and turn those files into endpoints that can be targeted by requests. And that should receive json and return json.

In this api folder you can add javascript files where the file names will act as path segments in the url. For example, you add news.js file inside pages/api. It will look like pages/api/news.js. Whenever a request is sent to /api/news. It will trigger the function which we have to define in news.js.

export default function handler(req, res) {
if (req.method === 'POST') {
// Process a POST request
} else {
// Handle any other HTTP method
}
}

Head Element

Adding head elements to our Next.js pages is very simple. To do that we can import a special component offered by Next.js that is Head compoenent from next/head. This component allows you to add head elements to the head section of your page. For example, I want to add title and description for my NewsDetail page.

import Head from 'next/head';
export default function NewsDetail() {
return;
<div>
<Head>
<title>My Page Title</title>
<meta name="description" content="This is my page description for My Page Title" />
</Head>
<p>Hello World!</p>
</div>;
}

Image Component

We can use the img tag in React component in the Next.js application. How do you optimize the images based on device type? So you need to write code to reduce image sizes and optimize images. To fix all those issues, Next.js provides an Image component(next/image) with extra below features.

  • Automatic optimization of local or CDN URL images: It reduces the size of an image without losing image quality.
  • Detects browser-supported formats and serves the image format accordingly.
  • Serve images with modern formats based on device type
  • Reduce Cumulative Layout Shift and improves Core Web Vitals
  • Overall page performance is improved with images
  • Dynamic image resizes for local and remote CDN paths
import Image from 'next/image';
import logo from '../public/logo.svg';
export default function PostImage() {
return (
<>
<Image src="/photo.svg" fill />
<Image src={logo} width="100px" height="200px" />
</>
);
}

Image component must use either layout or width and height, Otherwise, It gives the following exception Error: Image with src “vertical.SVG” must use “width” and “height” properties or “layout=‘fill’” property.

If you use images url from other website such as unsplash, then you must add the unsplash image domain inside Next.js configuration file.

next.config.js
const nextConfig = {
images: {
domains: ['images.unsplash.com'],
},
};
export default nextConfig;

Layout

Mostly when we build web interface, there are some components that always appear on every page. For example navbar and footer components. Instead of we code this two components in every pages, we can make a layout then place this navbar and footer components inside this layout. Then we can use this layout to be applied on all pages that we want.

First, let’s create a Layout.jsx components. Place Navbar and Footer components inside it.

import Navbar from 'components/header/Navbar';
import Footer from 'components/footer/Footer';
function Layout({ children }) {
return (
<div className="flex min-h-screen w-full flex-col">
<Navbar />
<div className="container mx-auto flex-1 px-4 2xl:max-w-7xl">{children}</div>
<Footer />
</div>
);
}
export default Layout;

If you only have one layout for your entire application, you can customize _app.js and wrap your application with the layout.

pages/_app.js
import Layout from '../components/layout';
export default function MyApp({ Component, pageProps }) {
return (
<Layout>
<Component {...pageProps} />
</Layout>
);
}
Edit this page Tweet this article