DEV Community

loading...
Cover image for Next.js Trash Course - Part 3/3

Next.js Trash Course - Part 3/3

vinicius77 profile image Vinicius Cerqueira Bonifácio Updated on ・7 min read

Hi again, devs.

Are you having a nice weekend there? I really hope so. 🙏

I should have released this final part yesterday, and my apologies for being late, but finally and after almost 3 months of coldness and darkness we have had few hours of sun shinning here so I decided to enjoy it. ☀️

If you are new here and have no idea what Trash Course means, please check 👇:
HINT: I am not ditching Next.js or anything.

But if you have been following along until here, please receive all my thankfulness. 🏆 So glad in having you aboard.

What will be covered in this part 3/3? 🤩

  • Static Assets, Custom Page Title and Metadata
  • Fetching Data
  • Dynamic Routes

Part 9 - Static assets, custom page title and metadata 📕

We are likely use to static assets (e.g. favicons, images etc.) in web applications. In Next.js we can achieve it by placing them in the public folder.

I grab from the internet an Ash Ketchum image just to use as an example and placed it on the public folder (public/ash.png). We will use it as our logo in the Navbar component.

import Link from 'next/link';
import Image from 'next/image'; // we import it and use below as following

export const Navbar = () => {
  return (
    <nav>
      <div className="brand">
        {/** Here 👇 */}
        <Image src="/ash.png" width={60} height={60} /> 
      </div>
      <Link href="/">Home</Link>
      <Link href="/about">About</Link>
      <Link href="/dev/">Dev</Link>
    </nav>
  );
};
Enter fullscreen mode Exit fullscreen mode

We could also have used the classic img tag if we want to: <img src="/ash.png" alt="ash ket" />.

Some points to consider when using the Next.js Image component are described below:

  • We need to specify its width and height properties explicitly otherwise an error will be thrown.
  • It automatically makes the image responsive based on the properties provided.
  • It utilizes the lazy loading design pattern. it is only loaded when it needs to be rendered, for example, if the image is placed in our footer, Next.js would only load it when scrolling down the page reaches the footer.

Talking about adding metadata and customized title to different pages it can be as simple as the following example in our Home:

import Link from 'next/link';
import Head from 'next/head'; // We import the Next.js' Head component

export default function Home() {
  return (
    <>
      {/** 👇👇👇 */}
      <Head>
        {/** Add the title and metadata for the page as shown below*/}
        <title>Creep Home Page</title>
        <meta name="keywords" content="next,random design,trash course" />
      </Head>
      {/** 👆👆👆 */}
      <div className="container">
        <h1>Hello Next.js</h1>
        <div>
          <Link href="/about">About</Link>
        </div>
      </div>
    </>
  );
}
Enter fullscreen mode Exit fullscreen mode

Remember to wrap all the components using only one parent element otherwise an error about having multiple parent elements will be thrown. I have used empty tags <></> but it could be a React Fragment, a <div> etc.

Part 10 - Fetching Data ⬇️ 💽

Normally in an application the data we fetch comes from the server-side, for example, a database, a web server and so on.

In order to make it simple, let's fetch some mock data from JSON API Placeholder

In a React application we would fetch data using the useEffect hook and the request would be made in the browser.

In Next.js it differs a little bit because all the components are first pre-rendered by the time they reach the browser. In other words we need to fetch the data in advance so the rendered components will have already the data in their templates.

Here is where the Next.js' getStaticProps function comes to the stage. I will use our dev's homepage (pages/dev/index.js) to fetch data from https://jsonplaceholder.typicode.com/users.

In our dev's index page (pages/dev/index.js) we must create the getStaticProps function and export it.

export const getStaticProps = async () => {
  const response = await fetch('https://jsonplaceholder.typicode.com/users');
  const data = await response.json();

  return { props: { users: data } };
};
Enter fullscreen mode Exit fullscreen mode

⚠️ DON'T write any code inside of the getStaticProps function that you expect to run in the browser. ⚠️

  • getStaticProps is a special async function because it runs at build time. Inside of this function we add our fetch requests in order to, guess what?, fetch any data we want to render in our component. 😉

The data we have fetched from the API endpoint is now attached to the props ({ users }) of our component:

export default function Home({ users }) {
  return (
    <div>
      <h1>Hello Devs</h1>
      <ul>
        {users.map((user) => (
          <li key={user.id}>
            <p>{user.username}</p>
            <p>{user.email}</p>
          </li>
        ))}
      </ul>
      <Button>Dev Button</Button>
    </div>
  );
}
Enter fullscreen mode Exit fullscreen mode

And that's it. Of course it is just a basic implementation but as a starting point it works pretty well.

I also know I should add some styles to it but this article is becoming longer than I thought so see it as a homework for you. 🤜 🤛

Part 11 - Dynamic Routes 🛣️ 🛣️

It would be nice if from the users list we fetched data we could see more information about a specific user when clicking on it. There are some steps to be followed in order to achieve that but nothing complicated at all.

We need to: ✏️

  • Generate dynamic routes for each user,
  • Create a component to hold the user details.

Inside of pages/dev folder we will create a file called [id].js so we can have routes at /dev/id where the id is whatever user's id we pass to the component.

The [id] syntax you saw before is a way to tell: " - Hey Next.js, I will be passing some route parameters to this component so be aware of that.".

Our pages/dev/[id].js component initially will look like the following:

import React from 'react';

const UserInfo = () => {
  return <div>Boom!</div>;
};

export default UserInfo;
Enter fullscreen mode Exit fullscreen mode

If now you go to the route http://localhost:3000/dev/2 or whatever value you pass as a route parameter you should see Boom! rendered there. It is not dynamic yet so let´s make some changes to make it happen.

  • Let's create a link in every user in the list so when we click on it we use its id as a parameter to fetch his / her individual data. (dev/index.js).
import { Button } from '../../components/Button';
import Link from 'next/link';

export const getStaticProps = async () => {
  const response = await fetch('https://jsonplaceholder.typicode.com/users');
  const data = await response.json();

  return { props: { users: data } };
};

export default function Home({ users }) {
  return (
    <div>
      <h1>Hello Devs</h1>
      <ul>
        {users.map((user) => (
          {/** 👇👇👇 */}
          <Link href={`/dev/${user.id}`} key={user.id}>
            {/** LAZY styling 😅 🙈 */}
            <li style={{ cursor: 'pointer' }}>
              <p>{user.username}</p>
              <p>{user.email}</p>
            </li>
          </Link>
        ))}
      </ul>
      <Button>Dev Button</Button>
    </div>
  );
}
Enter fullscreen mode Exit fullscreen mode

And finally we need to make a call to the endpoint using the user's id in order to fetch a user's individual info. (pages/dev/[id].js).

export const getStaticPaths = async () => {
  const response = await fetch('https://jsonplaceholder.typicode.com/users');
  const data = await response.json();

  const userPaths = data.map((user) => {
    return { params: { id: user.id.toString() } };
  });

  return {
    paths: userPaths,
    fallback: false,
  };
};

export const getStaticProps = async (context) => {
  const userID = context.params.id;
  const response = await fetch(
    `https://jsonplaceholder.typicode.com/users/${userID}`
  );
  const data = await response.json();

  return { props: { user: data } };
};

const UserInfo = ({ user }) => {
  return (
    <div>
      <h2>User Info</h2>
      <p>username: {user.username}</p>
      <p>email: {user.email}</p>
      <p>
        address: {user.address.street} - {user.address.city}
      </p>
      <p>phone: {user.phone}</p>
      <p>website: {user.website}</p>
    </div>
  );
};

export default UserInfo;
Enter fullscreen mode Exit fullscreen mode

Don't be scary! Most of the content there we are already familiar with. The new concepts I am going to try to explain now.

  • The getStaticPaths function: ✔️
export const getStaticPaths = async () => {
  const response = await fetch('https://jsonplaceholder.typicode.com/users');
  const data = await response.json();

  const userPaths = data.map((user) => {
    return { params: { id: user.id.toString() } };
  });

  return {
    paths: userPaths,
    fallback: false,
  };
};
Enter fullscreen mode Exit fullscreen mode

It is also a special function. It fetches the data and returns an array containing all the static paths of every single user as shown below.

//...
const userPaths = data.map((user) => {
  return { params: { id: user.id.toString() } };
});

return {
  paths: userPaths,
  fallback: false,
};
//...
Enter fullscreen mode Exit fullscreen mode

Remember that all the pages are build at run time. Think that it returns an array like this: [{ /dev/1 }, { /dev/2}, ... , { /dev/10 }].
The fallback: false option, is out of scope for now, but if you remove it will throw an error.

  • The getStaticProps function (same as before but slightly different): ✔️
export const getStaticProps = async (context) => {
  const userID = context.params.id;
  const response = await fetch(
    `https://jsonplaceholder.typicode.com/users/${userID}`
  );
  const data = await response.json();

  return { props: { user: data } };
};
Enter fullscreen mode Exit fullscreen mode

It has now access to the props returned from the getStaticPaths function through the context object.

From this object it can access the id of each user, fetch their individual information and send it to the UserInfo component (/pages/dev/[1].js).


And that's all, devs! I think we made it, guys! 🏅 🥇 🎉 🍾

Carlton Dance


Indeed, there is a lot of stuff to be explored but my aim was just to make a quick introduction to Next.js.

I am planning to release an extra post introducing the api folder (pages/api) and also how to deploy our application on Vercel but it depends a lot of how my week goes so I cannot promise anything, unfortunately. 😓

I hope you guys have enjoyed our time together and also have learned something from it. 😃 If you have created your own version using the content of this series, please share with us.

We would be glad to see the results of your hard work. 💪 💎

Thanks a ton and be safe everybody! 🙇‍♂️

Discussion (5)

pic
Editor guide
Collapse
bmangust profile image
Макс

There's some mismatch. You said:
«At the top of the file (pages/dev/index.js) we must import it.»
And then just create this function in the same file.
I think these "special" function need a bit more explanation. As I can understand, is fired just before rendering component, and - before that call.
Are there any rules in syntax, props, return values, ordering them in file? I guess only the name is important? And how is passed from one to another? Any other similar functions exist?

Thanks for the course, though)

Collapse
vinicius77 profile image
Vinicius Cerqueira Bonifácio Author

Good catch, Макс! Thanks for pointing it out.

I have just correct that. I meant export instead import since ´Next.js´ uses those function internally. :) But indeed, the sentence was a little bit confusing and mismatching, sorry for that.

Answering (Or trying to )


  • "... As I can understand, is fired just before rendering component, and - before that call ..."

    If you export an async function called getStaticProps from a page, Next.js will pre-render this page at build time using the props returned by getStaticProps.

    Source: Next.js Docs

  • "... I think these "special" function need a bit more explanation ..."
    I totally agree with you. I really tried hard to fit all the basics concepts in only three posts but as I have mentioned before unfortunately it was not enough.
    I am planning to release one or two more extra posts to explain things more in deep. 🙏🏻

  • Are there any rules in syntax, props, return values, ordering them in file? I guess only the name is important? And how is passed from one to another?

Yes, there are some rules, for example:

  • When you use getStaticProps on a page with dynamic route parameters, you must use getStaticPaths;
  • getStaticPaths should be used together with getStaticProps;
  • You cannot use getStaticPaths with getServerSideProps.
  • etc ...

As you have pointed out previously, the function name is also important because Next.js will define the order of each function call based on their names. Where they are placed inside of the file doesn't matter for that reason.

  • "... Any other similar functions exist? ..." Yes, e.g. getInitialProps and getServerSideProps.

I really hope I could clarify some of your doubts, Макс.

I am not an expert in Next.js by any means.

The concepts I brought in this series are from the Next.js Official Documentation . I would really recommend you to give a glance there. 😎

Again, thanks for participating.

Collapse
bmangust profile image
Макс

Thanks for the reply, Vinicius. I will definitely try Next.js out with one of next projects.

Collapse
kevinmons1 profile image
Kevin Monsieur

Nice topic !

Collapse
vinicius77 profile image
Vinicius Cerqueira Bonifácio Author

Nice to see you again, Kevin. 👍