NextJS Course (2/9) – Navigation, routing, and layouts in Next.js

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 the emerald part to any other color you like. As for the 700, this is the shade of the color, where 100 is the lightest and 900 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, where 4 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. πŸš€

Leave a Comment