Weâre going to learn all the key features that make up Next.js: how it pre-renders pages, helps with search engine optimization, implements data fetching, works with API routes, and 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 the 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, production-ready React apps easier. Letâs first focus on the framework part. Isnât React itself already a framework or a library? You can use React to build complex user interfaces much easier than with just JavaScript. Now Next.js is labeled a framework that builds upon 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 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 and 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 built-in server-side rendering support. Server-side rendering is all about preparing the content of a page on the server instead of on the client. If that page is pre-rendered on the server and data fetching can be done on the server too, when the request hits the server, the finished page is served to users and search engine crawlers. Users wonât have flickering loading states and search engines will 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 right and requires extra setup. With Next.js, that becomes much 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, and 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:
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 like images.
The 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 the index.js file inside the pages folder; you will see a standard React component. The index.js file will be the root page. So if a request reaches our https://yourdomain.com/, then index.js will be loaded. So if you want to make a URL path for example https://yourdomain.com/news, then you need to create a news folder. Inside the 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 like pages/news/index.js. Or you can create a new file called news.js and place it inside the pages folder.
We can start the development server with npm run dev. Then you can visit localhost:3000 through your browser.
But what 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 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.
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 have a 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.
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>;}Link Component
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 in the 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. The first thing is that it has an href prop, which is very similar to an href on an anchor tag, except it actually doesnât take a full URL. So youâre not going to put a full URL here. Remember, this is client-side routing, so everythingâs going to be pretty much relative here. Itâs relative to the pages directory.
For example, if I want to navigate to the news page from the home page, then inside the Home page, I call the Link component and pass â/newsâ to the href prop.
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.
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.
import Layout from "../components/layout";
export default function MyApp({ Component, pageProps }) { return ( <Layout> <Component {...pageProps} /> </Layout> );}