Welcome back to part 2 of this tutorial series. In this part, we’ll first get started by looking at how to create and navigate between multiple pages in Next.js, as just having a single page is not very useful!
Routing in Next.js
Routing in Next.js is very easy and almost magical even. Where in every other framework you have to manually define routes, in Next.js, you just create a new folder with a specific file inside it and a new route will be created. So let’s have a look at how this works.
Our current π app
folder directory looks like this:
π app π favicon.ico π globals.css π layout.tsx π page.tsx
The page.tsx simply refers to the main page of the website. All we have to do to create a new route and page for that route is to add a new folder and make sure it also has a page.tsx
file inside, and then Next.js will automatically detect this and create a new route for us.
So add a new folder named blockbuster_chat
inside the app
folder and create a new file named page.tsx
inside it. The directory structure should look like this:
π app π blockbuster_chat π page.tsx π favicon.ico π globals.css π layout.tsx π page.tsx
Now open up the page.tsx
file inside the blockbuster_chat
folder and let’s create and export a React component in here. As this uses a bunch of boilerplate code which will be the same for every page, we can use the shortcuts extension we installed in part 1 and just type rafce
and hit enter to create a new React component.
Wait what?! rafce
? Ok, I’ll make it easier to remember. Rambo’s Army Fights Criminal Easterbunnies:
You’re welcome. π
So simply type rafce
and hit enter and you will have the following boilerplate code:
import React from 'react' const page = () => { return ( <div>page</div> ) } export default page
This is just the standard boilerplate code for a React component, importing React and exporting the component. The component itself is just a const
(constant, or a variable that can’t be changed after declaration) named page
.
This component uses an arrow function () => {}
which is a shorthand way of writing a function in JavaScript. As the ()
is empty, this function doesn’t take any arguments for now and simply returns a div
element with the text page
inside it. If the syntax still looks a bit weird to you, don’t worry, you’ll get used to it soon enough. Now just make some slight edits like this:
import React from 'react' const BlockBusterChat = () => { return ( <div>Welcome to BlockBuster Chat!</div> ) } export default BlockBusterChat
So all we return here is a single div
element with the text Welcome to BlockBuster Chat!
inside it. Make sure you still have your server running in the terminal or run npm run dev
(while making sure you are in the project folder) to start it up. Now save the file, go back to your browser, and navigate to http://localhost:3000/blockbuster_chat. You should see the text Welcome to BlockBuster Chat!
displayed on the page:
Tadaa! You just created a new page in Next.js. π Now I’ll be the first to admit that it doesn’t look very good right now, but we still have our http://localhost:3000/ page and have now added this separate route as well, without having to set up the usual server-side routing logic like you would have to do with frameworks like Django
etc. That is really powerful!
Creating a navigation bar
The next thing we want is to have a navigation bar to allow the user to actually click and navigate between these pages. Since we want this navigation to always be visible on all pages we can use our layout.tsx
file to define this, so it shows up everywhere.
We don’t want to make our layout.tsx
file too big and complex though, remember the Single Responsibility Principle
, or Separation of Concerns
? So let’s create a new file named navbar.tsx
inside the app
folder and add the following code to it:
π app π blockbuster_chat π page.tsx π favicon.ico π globals.css π layout.tsx π navbar.tsx β¨ New file π page.tsx
Now open up the navbar.tsx
file and start with the imports up top:
import React from 'react'; import Link from 'next/link';
Obviously, our navbar will be a React component, so we need to import React. What is the Link
import though? This is a special component provided by Next.js which allows us to create links between pages in our app.
If you’re familiar with HTML, you might know the <a href>
tag which is used to create links. The problem with this tag is that it reloads the entire page when you click on it, which is not very efficient. The Link
component provided by Next.js solves this problem by only loading the content that changes, and not the entire page. This gives us that SPA
(Single Page Application) feel, where only the content changes and not the entire page.
Now let’s create a Navbar
component. We’ll keep it simple and build one first by just listing what we need. If you notice the repetition, don’t worry, I’ll show you how to improve this afterward. Just like we’ve seen so far, we need a function that will return some JSX code containing the React elements we want to render:
const Navbar = () => { return ( <nav className="bg-emerald-700 text-white p-4"> <ul className="flex justify-between items-center"> <li className="mx-4"> <Link href="/" className="hover:text-gray-300">Home</Link> </li> <li className="mx-4"> <Link href="/blockbuster_chat" className="hover:text-gray-300">Blockbuster Chat</Link> </li> <li className="mx-4"> <Link href="/tbd" className="hover:text-gray-300">TBD</Link> </li> <li className="mx-4"> <Link href="/tbd2" className="hover:text-gray-300">TBD2</Link> </li> </ul> </nav> ); } export default Navbar
This is a simple Navbar
component using the = () => {}
arrow function syntax. It returns a nav
element which is basically just a standard HTML block element for navigation. The className
is why we installed tailwindcss
in part 1, as it allows us to use these classes to style our elements.
The bg-emerald-700
class, where bg is simply background, gives the navigation bar a green background color, text-white
makes the text white, and p-4
adds some padding around the content so it doesn’t go all the way to the edge.
These classes can be assigned to your liking by just changing the name, for instance:
bg-emerald-700
-> You can simply change theemerald
part to any other color you like. As for the700
, this is the shade of the color, where100
is the lightest and900
is the darkest.text-white
-> This one is obvious now, you can change the color to any other color you like!p-4
-> This is the padding, where4
is the amount of padding, with lower numbers giving less padding and higher numbers giving more.
Using these classes is a very quick and easy way to style your elements without having to write a lot of CSS, which saves us a bunch of time and shows why tailwindcss
is so popular.
Inside the nav
element, we have an HTML ul
element which stands for unordered list and serves to wrap the list of our navigation links together. The Tailwind classNames here are flex
to activate flexbox, justify-between
to space the items evenly with space in between, and items-center
to center the items vertically.
If you’re not familiar with this, you’ll see how it works on the page in a moment. CSS flexbox is too big of a topic to cover here but also not very important for this tutorial series, so if you’re not sure about this part don’t get hung up on this one too much.
Inside that we have multiple li
elements which stand for list items, using the mx-4
class to add some margin on the x-axis (left and right) to space the items out a bit. Inside each li
element we have the Link
component we imported at the top, which is used to create links between pages in our app.
The class hover:text-gray-300
is a Tailwind class that changes the text color to a light gray when you hover over the link. This is a nice little touch to give the user some feedback when they hover over the links.
As for the href
(hypertext reference) attribute, this is the path to the page we want to navigate to when the user clicks on the link. Note that the links are relative, e.g. /blockbuster_chat
, which means that they are relative to the root of the website. So if you’re on http://localhost:3000/
and click on the Blockbuster Chat
link, you will be taken to http://localhost:3000/blockbuster_chat
.
Notice throughout all tags in the file the basic HTML rules of opening and closing tags and the nesting of tags are followed, e.g. for every <Link>
tag there is a corresponding </Link>
tag that closes it again.
Including the Navbar in the layout
Go ahead and save this file. Now if you check out your site in the browser, you will see no changes yet. This is because we haven’t actually included the Navbar
component in our layout.tsx
file yet. So let’s do that now. Open up the layout.tsx
file and first add the import at the top:
import type { Metadata } from "next"; import { Inter } from "next/font/google"; import "./globals.css"; import Navbar from "./navbar"; // β¨ Add this line
We don’t have to change anything except the contents of the function’s return statement:
const inter = Inter({ subsets: ["latin"] }); export const metadata: Metadata = { title: "Create Next App", description: "Generated by create next app", }; export default function RootLayout({ children, }: Readonly<{ children: React.ReactNode; }>) { return ( <html lang="en"> // β¨ Changes start here // <body className={inter.className}> <Navbar /> <main className="p-4"> {children} </main> </body> // β¨ Changes end here // </html> ); }
Obviously, remove the emoticon comments. The only thing we changed here is that we split the <body>
tag into separate lines to give us a bit more space to work with and added a <main>
– </main>
tag around the {children}
prop which holds the content.
The main
tag is a semantic HTML5 element that represents the main content of the document. This is a good practice to follow as it helps with accessibility and SEO, but the tag itself doesn’t have any particular programmatic effect besides just being a container for the content.
Above the main content, we added the Navbar
component we created and imported. As this is now part of the layout, it will show up on every page that uses this layout. You might notice that instead of <Navbar>
– </Navbar>
we use <Navbar />
. This is a shorthand way of writing self-closing tags in JSX, and is equivalent to the former, so <Navbar />
is the same as <Navbar>
– </Navbar>
except we don’t get to put anything in between of course.
Before we go check out our page go to the globals.css
file and let’s remove some of the stuff we’re not using:
@tailwind base; @tailwind components; @tailwind utilities; :root { --foreground-rgb: 0, 0, 0; } /* @media (prefers-color-scheme: dark) { :root { --foreground-rgb: 255, 255, 255; --background-start-rgb: 0, 0, 0; --background-end-rgb: 0, 0, 0; } } */ body { color: rgb(var(--foreground-rgb)); }
I removed the unused --background-start-rgb
and --background-end-rgb
variables, and the padding
of 1rem
from the body
tag, as we don’t want a white padding border around our navbar, we want it to go all the way to the edges.
Save all files and check out your site in the browser. You should now see a navigation bar at the top of the page with the links we defined in the Navbar
component:
Clicking on the Blockbuster Chat
link should take you to the other page without refreshing the page itself by only switching out the content. This is the magic of Next.js routing:
Great! Now clicking on the other links will show a 404 page as we haven’t created those pages yet, but note that even there Next.js is taking care of us serving up a decent-looking 404 page.
We have basic navigation now, but we can do a lot better. We’re not really using the power of React here, as we’re repeating ourselves a lot in the Navbar
component, writing in old school HTML style.
Refactoring the Navbar component
Remember how in the layout.tsx
file we simply inserted <Navbar />
as an abstraction of the entire navigation bar? We can do the same thing inside the Navbar
component itself, by taking the repetitive pattern and putting it into its own function. Open back up the navbar.tsx
file and first remove all the repetitive <li>
items:
import React from 'react'; import Link from 'next/link'; const Navbar = () => { return ( <nav className="bg-emerald-700 text-white p-4"> <ul className="flex justify-between items-center"> </ul> </nav> ); } export default Navbar
Ok so let’s define a NavItem
. You can just do this in the same file above the Navbar
function. Note that you will see a red squiggly line, just ignore it for now:
const NavItem = (props) => ( <li className="mx-4"> <Link href={props.href} className="hover:text-gray-300"> {props.children} </Link> </li> );
So we have a NavItem
which takes a props
object as input. It will then return the repetitive structure we had before, but it fills in the link from {props.href}
and the text from {props.children}
. Remember you need the {}
around the props.href
and props.children
to tell React that this is a JavaScript expression/variable. Now typing props.href and props.children is a bit repetitive so React developers usually destructure the props object like this:
const NavItem = ({ href, children }) => ( <li className="mx-4"> <Link href={href} className="hover:text-gray-300"> {children} </Link> </li> );
Typescript interfaces
Notice you still have the red squiggly lines, but this is nicer to read as you can see that there are two input arguments, and you don’t have to type props.something
over and over again. The reason for the red squiggly lines is that we have TypeScript enabled for our project, and it wants to know what type the href
and children
are.
To fix this we create an interface
, which is basically like a TypeScript object that contains the types of the properties we want to use. Add this before your NavItem
function:
interface NavItemProps { href: string; children: React.ReactNode; }
This is an interface
named NavItemProps
which defines two properties, href
which is a string, and children
which is a React.ReactNode
. The React.ReactNode
type is a special type in TypeScript that can be any valid React node, like a string, a number, a component, etc. We’ll start by just using a string for the link text like Home
, Blockbuster Chat
, etc, but you could change this to another component of its own when the complexity gets higher for yet another inner level of abstraction.
Now we just need to tell our NavItem
function that it should use this interface as its input type:
const NavItem = ({ href, children }: NavItemProps) => ( <li className="mx-4"> <Link href={href} className="hover:text-gray-300"> {children} </Link> </li> );
And now your red squiggly lines are gone. If you’re not used to typed languages this may seem like overkill at first, but it is actually really nice to have this type checking in place as your project grows. It helps you catch bugs early and makes your code more readable and maintainable.
Now we can complete our menu by adding the NavItem
components to the Navbar
component. I’ll show the whole navbar.tsx
file here for reference:
import React from 'react'; import Link from 'next/link'; interface NavItemProps { href: string; children: React.ReactNode; } const NavItem = ({ href, children }: NavItemProps) => ( <li className="mx-4"> <Link href={href} className="hover:text-gray-300"> {children} </Link> </li> ); const Navbar = () => { return ( <nav className="bg-emerald-700 text-white p-4"> <ul className="flex justify-between items-center"> <NavItem href="/">Home</NavItem> <NavItem href="/blockbuster_chat">Blockbuster Chat</NavItem> <NavItem href="/tbd">TBD</NavItem> <NavItem href="/tbd2">TBD2</NavItem> </ul> </nav> ); } export default Navbar
Now we just have the <NavItem>
component in there with a string in between the opening and closing tags which has the text for the link. If you save this you will see your page works exactly as before, but now our code conforms to the DRY
(Don’t Repeat Yourself) principle for coding, which will totally save your ass many times over as large projects become more and more complex.
You might look at all this and think that it is more complex than what we started with, and you may even be right to some degree. But imagine these link items growing in complexity and in number, you have like 35 of them, with some of them being in subitems, etc. Every time you want to change something you would have to go through all these items and change them manually, one-by-one. With this new implementation, you can simply change the NavItem
and you’re done.
If you’re more advanced and notice that we could also map over an array of items to create the links, you’re absolutely right, but for the sake of this tutorial, we’ll stop here for now, to move on to other cool stuff. We will come back to the map
function later when we really need it.
Now that we have a good basic understanding of components and how we can nest them inside of each other and use a layout and navigation, it’s time to create something cool. I’ll see you in the next part where we’ll start building our Blockbuster Chat app. π