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>
);
};
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
andheight
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>
</>
);
}
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 } };
};
⚠️ DON'T write any code inside of the getStaticProps
function that you expect to run in the browser. ⚠️
-
getStaticProps
is a specialasync function
because it runs at build time. Inside of this function we add ourfetch 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>
);
}
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;
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>
);
}
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;
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,
};
};
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,
};
//...
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 } };
};
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! 🏅 🥇 🎉 🍾
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! 🙇♂️
Top comments (5)
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)
Good catch, Макс! Thanks for pointing it out.
I have just correct that. I meant
export
insteadimport
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:
getStaticProps
on a page with dynamic route parameters, you must usegetStaticPaths
;getStaticPaths
should be used together withgetStaticProps
;getStaticPaths
withgetServerSideProps
.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
andgetServerSideProps
.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.
Thanks for the reply, Vinicius. I will definitely try Next.js out with one of next projects.
Nice topic !
Nice to see you again, Kevin. 👍