IzioDev

Go to English language
Published on

You saw mistakes in this article? please report at contact@izio.fr

How to understand Next.js when coming from React?

Built on top of React, Next.js is a full-stack web framework created in 2016 by Vercel. Its goal is to offer the best developer experience with all the needed production features.

Image illustrating How to understand Next.js when coming from React?

You already built great web applications with React, you love this framework so much but you realized it doesn't fit all your needs.. and this makes you sad 😿. Sometimes you want:

  • static and server rendering
  • route pre-fetching
  • automatic routing system
  • built-in image optimization
  • smooth serverless deployments
  • SEO optimization
  • And many more...

Well, this is your lucky day 🍀. Next.js adds these missing features.

⚠️ Disclamer

If you never used React before, this article might not be for you. I would rather recommend giving React a try. In fact, Next.js is built on top of this framework and some basics React concepts are needed in order to focus on Next.js.

Setup

The simple way to create a new Next.js project is by running npx create-next-app, followed by the flag --typescript, it will create the new project with Typescript file.

If you want to create the project manually, please refer to the official documentation. For example, maybe you don't want the project to be created with yarn. Once the project got installed, here is what you should have (I simplified it a bit):


┣ pages/
┃ ┣ api/
┃ ┃ ┗ hello.ts
┃ ┣ index.tsx
┃ ┗ _app.tsx
┣ public/
┃ ┣ favicon.ico
┃ ┗ vercel.svg
┣ styles/
┃ ┣ globals.css
┃ ┗ Home.module.css
┣ next.config.js
┣ package.json

Except for the configuration file next.config.js, the Next.js project is divided into four parts:

  • pages/api: see it as an HTTP server for now. This is where the server exposes HTTP routes. In some cases, you don't need an HTTP server and hence, you don't need this part.
  • pages/: every file in this folder that exports a React component as a default export is a page in the Next.js router. In fact, in our case, index.tsx export a default Home function component. the default page must be in index.tsx.
  • public/: the static served files. this is where we may want to store our images, fonts, etc.
  • styles/: contains CSS files. globals.css for global styles and [name].module.css for scoped CSS style. Learn more about the CSS module feature on the official documentation.

You can start the application by running the dev script command defined inside package.json (npm run dev or yarn dev).

In development mode, the website isn't statically built, instead, it has an HMR (hot module replacement) that gives us a really enjoyable developer experience thanks to webpack.

Create a new page

In a React application, you would probably create a new component About inside a sub-folder components. Then, you need to add this a Route (if using react-router-dom) to a Switch as a child. And finally, you would have to create a Link component in order to link it under other pages.

Let's create a new About page and link it to our Home (index) page. Create a new about.tsx file under /pages, and add these basic lines in it:

1// pages/about.tsx 2import React from "react"; 3import { NextPage } from "next"; 4 5const About: NextPage = () => { 6 return <p>Hello World from About!</p>; 7}; 8 9export default About;

We can test this page by manually going to http://localhost:3000/about (if you didn't change the webserver port). It's pure magic 🎉!

You can now either remove all the Home content or partially update it by adding a Link to the freshly created About page:

1// pages/index.tsx 2import type { NextPage } from "next"; 3import Link from "next/link"; 4 5const Home: NextPage = () => { 6 return ( 7 <> 8 <h1>Hello from Home</h1> 9 <Link href="/about">Go to About</Link> 10 {/* Or using passHref if you need custom styling on the link */} 11 <Link passHref href="/about"><a>Go to about</a></Link> 12 </> 13 ); 14}; 15 16export default Home;

That's it, you've created a page that is linked from Home! 👏

Image optimization

By default, in React, images don't have any optimizations. Next.js simplifies our life by adding a utility component Image exported from next/image. This makes sure that:

  • Images have cache headers so the browser knows he can keep it in the client cache
  • Images size is not too big for the client. For instance, if a container is 900x700 px, the client asks for this dimension even if originally the image was uploaded with a bigger size. You can read this more on the official Next.js documentation
  • Images are served as .webp, if you don't know this image format, read more about .webp on MDN

Let's try to include a static image on the About page to see how to use an image in Next.js. For the example, I will use this image. If you want to use this image too, you can right-click on it and save it on the Next.js project /public/images directory (/images doesn't exist yet, you can create it) test Note: Cat 🐱 images are not allowed for this example. Feel free to move on to the next paragraph.

All static assets (fonts, images, etc) can be referenced by the code starting from the base URL /${asset_path}. In order to reference our image, let's import the image in the file.

1import dogImage from "../public/images/dog.jpg";

And just use the <Image/> component that Next gives us.

1<Image src={dogImage} alt="Super cute dog!" />

See the whole About page code
1import React from "react"; 2import { NextPage } from "next"; 3import Image from "next/image"; 4 5import dogImage from "../public/images/dog.jpg"; 6 7const About: NextPage = () => { 8 return ( 9 <> 10 <p>Hello World from About!</p> 11 <Image src={dogImage} alt="Super cute dog!" /> 12 </> 13 ); 14}; 15 16export default About;

By default, the Image fits the parent size restriction, here, there is no restriction. Hence, Next.js will use the whole width (1080px in my case). Try to add a <div> around the <Image/> tag and restrict the width to 600px. It will use a different image source. This is the size optimization we were talking about.

Adding dynamic data

Our application looks great but it lacks some dynamic data. Next.Js have three kinds of data-fetching, and they can be confusing at first. The data fetching logic is coupled to a component, passing the result of the fetching to the component as a prop.

The first one is classic, getServerSideProps is making a dynamic request each time the coupled component is mounted, this is the only method that makes requests not at build time but instead at runtime. Here is an example that is strongly typed.

1import { GetServerSideProps, InferGetServerSidePropsType } from "next"; 2 3export default function MyComponent({}: InferGetServerSidePropsType< 4 typeof getServerSideProps 5>) { 6 return <div>Hello world</div>; 7} 8 9export const getServerSideProps: GetServerSideProps = (context) { 10 const res = await fetch(`https://...`) 11 const data = await res.json() 12 13 return { 14 props: {}, 15 } 16}

Have you noticed that the function is taking an argument context? It would be populated by another data fetching method (if implemented) getStaticPaths. This new fetching method is useful when you want multiple pages of a same type. For instance, I use this method on this website for displaying all different article pages. In my case this logic is inside a [slug].tsx file inside pages/blog

1export const getStaticPaths: GetStaticPaths = async () => { 2 const slugsResponse = await apiClient.get<string[]>("/post/slugs", { 3 params: { status: "published" }, 4 }); 5 6 const paths: GetStaticPathsResult["paths"] = slugsResponse.data.map( 7 (slug) => ({ 8 params: { slug }, 9 }) 10 ); 11 12 return { 13 paths, 14 fallback: false, 15 }; 16};

It's the logic that is defined all the paths under an article. So that the coupled component knows about which slug we are talking about (taking it as a prop).

The last method is getStaticProps and it is useful to retrieve data at build time (for static generation), for instance, I use this method for getting data about an article, given its slug that we fetched before, within the getStaticPaths, like its title, description, and content. Here is a sample code is taken from my website

1export const getStaticProps: GetStaticProps<{ article: PostType }> = async ( 2 context 3) => { 4 const { params } = context; 5 6 const articleResponse = await apiClient.get<PostType>("/post", { 7 params: { slug: params?.slug }, 8 }); 9 const article = articleResponse.data; 10 11 return { 12 props: { 13 article, 14 }, 15 }; 16};

You can go deeper into each of these fetching strategies by looking at the Next.js documentation

Introducing the HTTP server

📝 Update: Starting from NexT.js version 13, they have introduced middlewares, functions that run before any other requests, this could be useful to implement a global authentication or logging for example.

Let's create a contact.ts file inside pages/api. Remember that api sub-folder contains logic that will be under a listening HTTP server (it's a backend basically), and add this content

1import type { NextApiRequest, NextApiResponse } from "next"; 2 3// Optional, it's just to type response object 4type Data = { 5 success?: boolean; 6 error?: string; 7}; 8 9export default async (req: NextApiRequest, res: NextApiResponse<Data>) => { 10 const customerEmail = req.body.email; 11 const customerName = req.body.name; 12 const messageContent = req.body.content; 13 14 if (!customerEmail) { 15 return res.status(500).json({ error: "Missing email." }); 16 } 17 18 console.log(req.body); 19 return res.status(200).json({ success: true }); 20};

The HTTP server is running on the same port of the website, meaning we can call our endpoint programmatically with this line of code for example

1const value = { 2 email: "test@test.com", 3 name: "Test Name", 4 content: "Test Content", 5}; 6 7await axios.post("/api/contact", values);

Note that the route we're calling is /api/contact, and that is because of the filename contact.ts. You can nest folders inside API as you would do with pages of the front-end application. You can read more about built-in API on the documentation

Environment variables

I'm taking a bit more of your time because this was confusing to me at first. Environment variables are read by default in a Next.js application. They also have a logic where you can have multiple .env files that get loaded based on which environment (prod, dev, beta...) you're running the application, but I don't recommend this. Let's create a .env file at the project root directory.

1VARIABLEA=a 2NEXT_PUBLIC_VARIABLEB=b

There is two kinds of variable, the public one that gets exposed to the browser and the API, the default one that only gets exposed to the API. ⚠️ So be careful when adding the NEXT_PUBLIC prefix because that's what makes a variable public.

Conclusion

You have everything you need to create your next Next.js application with your React background.

In this article, we've:

  • Setup a new Nest.js project
  • Explained project architecture
  • Create a new page
  • Created links between pages
  • See the image module that optimizes image rendering
  • Explained the huge data fetching system
  • Created API endpoints
  • Used environment variables

If you liked this article and you want to go further, please reach out, and let's see together what could be interesting to discuss on!

Share