9 min read

Build Header Component With Nextjs & Material-UI v5

Build Header Component With Nextjs & Material-UI v5

The header component is an essential component of a website. In addition, you need to provide a component for the visitor to explore your website.

Build the menu navigation list in the header is the best option.

Let's build a header component with Nextjs & Material-UI v5.

Tutorial  2: Top Navigation Header

We're building a top navigation bar with some modern features:-

  • Hide the navigation bar on scrolling down and appear again on scrolling up.
  • A back to top button on every page.
  • A navigation menu with active class depends on the routes.
  • It is mobile responsive with a side drawer.

If you direct land on this page from searches, you may want to visit:-

  • Tutorial 1: Set up MUI5 for Nextjs Project
  • Tutorial 2: You're Here!

Let's get started!

📌 You can name it as you like. E.g.: MNLink, theLink, YourLink, and so on.  

This component is a lifesaver. We don't need to think about whether should we use NextLink without MUI styles or use the MUILink without the route features.

MUI team combine them into one component.

npm install clsx

"clsx" is a tiny condition checker's package.

Create the directory and the file:

mkdir components
touch components/MuiNextLink.jsx

Add the code:

import * as React from "react";
import clsx from "clsx";
import { useRouter } from "next/router";
import NextLink from "next/link";
import MuiLink from "@material-ui/core/Link";

export const NextLinkComposed = React.forwardRef(function NextLinkComposed(
  props,
  ref
) {
  const {
    to,
    linkAs,
    href,
    replace,
    scroll,
    passHref,
    shallow,
    prefetch,
    locale,
    ...other
  } = props;

  return (
    <NextLink
      href={to}
      prefetch={prefetch}
      as={linkAs}
      replace={replace}
      scroll={scroll}
      shallow={shallow}
      passHref={passHref}
      locale={locale}
    >
      <a ref={ref} {...other} />
    </NextLink>
  );
});

// A styled version of the Next.js Link component:
// https://nextjs.org/docs/#with-link
const MuiNextLink = React.forwardRef(function Link(props, ref) {
  const {
    activeClassName = "active",
    as: linkAs,
    className: classNameProps,
    href,
    noLinkStyle,
    role, // Link don't have roles.
    ...other
  } = props;

  const router = useRouter();
  const pathname = typeof href === "string" ? href : href.pathname;
  const className = clsx(classNameProps, {
    [activeClassName]: router.pathname === pathname && activeClassName,
  });

  const isExternal =
    typeof href === "string" &&
    (href.indexOf("http") === 0 || href.indexOf("mailto:") === 0);

  if (isExternal) {
    if (noLinkStyle) {
      return <a className={className} href={href} ref={ref} {...other} />;
    }

    return <MuiLink className={className} href={href} ref={ref} {...other} />;
  }

  if (noLinkStyle) {
    return (
      <NextLinkComposed className={className} ref={ref} to={href} {...other} />
    );
  }

  return (
    <MuiLink
      component={NextLinkComposed}
      linkAs={linkAs}
      className={className}
      ref={ref}
      to={href}
      {...other}
    />
  );
});

export default MuiNextLink;
  • The MuiNextLink component checks the path you visit is match the route then add the active style.
  • You can skip nesting the <a> tag like typical Next Link.

In case you want to know where I got the code, check out the MUI example

Define the active style.

Open styles/global.css and replace all the code with:

.active {
  opacity: 1;
}

You'll know why we use opacity as an active style later.

📌 In case you want to know more about MUI routing customization, check out MUI Routing.

Create & Initial the Header component

Create the file:

touch components/Header.jsx

You can use the JS extension as you like. The reason I use JSX extension:-

  • Explicitly tell that this file is a React Component
  • JSX in VS Code come with the Emmet features

If you prefer to use JS extension with Emmet features, you can configure it by following this setup here.

Add the code:

import * as React from "react";
import AppBar from "@material-ui/core/AppBar";
import Container from "@material-ui/core/Container";
import Toolbar from "@material-ui/core/AppBar";
import { experimentalStyled as styled } from "@material-ui/core/styles";

const Offset = styled("div")(({ theme }) => theme.mixins.toolbar);

const Header = () => {
  return (
    <>
      <AppBar position="fixed">
        <Toolbar>
          <Container
            maxWidth="lg"
            sx={{ display: `flex`, justifyContent: `space-between` }}
          >
            This is a header
          </Container>
        </Toolbar>
      </AppBar>
      <Offset />
    </>
  );
};

export default Header;
  • We are setting the AppBar position fixed cause some of the content hidden behind the AppBar. The Offset styled component sets an invisible height equivalent to the AppBar to push content to the proper position.
  • The Container component centres content horizontally and apply left and right margin consistently to all screen sizes.
  • The Container "sx" prop styling separate the logo and navigation list later.

Add the Header component to _app.js

Open ./pages/_app.js

import Header from "../components/Header";

Add the component:

Save all the files and:

npm run dev

Start the development server and visit http://localhost:3000/, you shall see:

Add logo to the Header component

Open ./components/Header.jsx

Since I don’t have any logo for this project, I will use a home icon to act as a logo.

Import the packages to Header component

import IconButton from "@material-ui/core/IconButton"
import Home from "@material-ui/icons/Home"
import MuiNextLink from "../components/MuiNextLink";

Replace "This is a header" with the following code:

<IconButton edge="start" aria-label="home">
  <MuiNextLink activeClassName="active" href="/">
    <Home
      sx={{
        color: (theme) => theme.palette.common.white,
      }}
      fontSize="large"
    />
  </MuiNextLink>
</IconButton>
  • The code shows how to use MUI theme in the sx prop.

Save the files, you shall see:

Add navigation list

mkdir const & touch const/nav-links.js

Add the code:

export const navLinks = [
  { title: `home`, path: `/` },
  { title: `about us`, path: `/about-us` },
  { title: `menu`, path: `/menu` },
  { title: `catering`, path: `/catering` },
  { title: `contact`, path: `/contact` },
];

📝 const = constant.

I prefer to declare "seldom change" data in the constant: E.g. FAQ, main-routes, sub-routes, price list & more.

When I need to change the data, I only need to change it in one place.

For a simple website, you also declare the "navLinks" variable in the Header component like so:

Add relative pages

touch pages/about-us.js
touch pages/menu.js
touch pages/catering-js
touch pages/contact.js

Initial the relative pages

Open ./pages/about-us.js

import * as React from "react";

const AboutUsPage = () => {
  return <h1>This is about us page</h1>;
};

export default AboutUsPage;

Open ./pages/menu.js

import * as React from "react";

const MenuPage = () => {
  return <h1>This is menu page</h1>;
};

export default MenuPage;

Open ./pages/catering.js

import * as React from "react";

const CateringPage = () => {
  return <h1>This is catering page</h1>;
};

export default CateringPage;

Open ./pages/contact.js

import * as React from "react";

const ContactPage = () => {
  return <h1>This is contact page</h1>;
};

export default ContactPage;

The main navigation component

Create the file:

touch components/MainNav.jsx

Add the code:

import Stack from "@material-ui/core/Stack";
import Toolbar from "@material-ui/core/Toolbar";
import * as React from "react";
import MuiNextLink from "./MuiNextLink";
import { navLinks } from "../const/nav-links";

const MainNav = () => {
  return (
    <Toolbar
      component="nav"
      sx={{
        display: { xs: `none`, md: `flex` },
      }}
    >
      <Stack direction="row" spacing={4}>
        {navLinks.map(({ title, path }, i) => (
          <MuiNextLink
            key={`${title}${i}`}
            href={path}
            variant="button"
            sx={{ color: `white`, opacity: 0.7 }}
          >
            {title}
          </MuiNextLink>
        ))}
      </Stack>
    </Toolbar>
  );
};

export default MainNav;
  • For screen size "xs" and "sm", we set the display to none to hide the navigation list. So that, we can display the burger menu icon for "md" screen size and below later.
  • The main navigation list opacity is set to 0.7. When it is active, the opacity will be 1 as describe in ./styles/global.css earlier.

Open ./components/Header.jsx

import MainNav from "../components/MainNav";

Add the component:

Save the file and you shall see:

  • Try to navigate between the pages. You can see that whichever page you visit, the corresponding link list will change from opacity 0.7 to 1.

The Side Drawer

Create the file:

touch components/SideDrawer.jsx

Add the code:

import Box from "@material-ui/core/Box";
import Typography from "@material-ui/core/Typography";
import Drawer from "@material-ui/core/Drawer";
import IconButton from "@material-ui/core/IconButton";
import Menu from "@material-ui/icons/Menu";
import * as React from "react";
import { navLinks } from "../const/nav-links";
import MuiNextLink from "./MuiNextLink";

const SideDrawer = () => {
  const [state, setState] = React.useState({
    right: false,
  });

  const toggleDrawer = (anchor, open) => (event) => {
    if (
      event.type === "keydown" &&
      (event.key === "Tab" || event.key === "Shift")
    ) {
      return;
    }

    setState({ ...state, [anchor]: open });
  };

  const list = (anchor) => (
    <Box
      sx={{ width: 250, marginTop: `auto`, marginBottom: `auto` }}
      role="presentation"
      onClick={toggleDrawer(anchor, false)}
      onKeyDown={toggleDrawer(anchor, false)}
    >
      {navLinks.map(({ title, path }, i) => (
        <Typography
          variannt="button"
          key={`${title}${i}`}
          sx={{
            ml: (theme) => theme.spacing(5),
            my: (theme) => theme.spacing(2),
            textTransform: `uppercase`,
          }}
        >
          <MuiNextLink sx={{ color: "common.white" }} href={path}>
            {title}
          </MuiNextLink>
        </Typography>
      ))}
    </Box>
  );

  return (
    <React.Fragment>
      <IconButton
        edge="start"
        aria-label="menu"
        onClick={toggleDrawer("right", true)}
        sx={{
          color: `common.white`,
          display: { xs: `inline`, md: `none` },
        }}
      >
        <Menu fontSize="large" />
      </IconButton>
      <Drawer
        anchor="right"
        open={state.right}
        onClose={toggleDrawer("right", false)}
        sx={{
          ".MuiDrawer-paper": {
            bgcolor: "primary.main",
          },
        }}
      >
        {list("right")}
      </Drawer>
    </React.Fragment>
  );
};

export default SideDrawer;
  • Display the menu icon for "xs" and "sm", hide the menu for "md" screen size and above.
  • Slide in the side drawer from the right side.

Complete the Header

Open ./components/Header.jsx

import SideDrawer from "./SideDrawer";

Add the component:

Save the file,  resize the screen down to 959px and below. You shall see the burger menu show up:-

Click the burger menu, the side drawer slide in from the right:


Hide on Scroll

Create the file:

touch components/HideOnScroll.jsx

Add the code:

import * as React from "react";
import useScrollTrigger from "@material-ui/core/useScrollTrigger";
import Slide from "@material-ui/core/Slide";


const HideOnScroll = ({ children }) => {
  const trigger = useScrollTrigger();

  return (
    <Slide appear={false} direction="down" in={!trigger}>
      {children}
    </Slide>
  );
};

export default HideOnScroll;

Open ./components/Header.jsx

import HideOnScroll from "./HideOnScroll";

Wrap the AppBar:-

Save the file, navigate to homepage and scroll down.

Back To Top Button

touch components/BackToTop.jsx

Add the code:

import * as React from "react";
import useScrollTrigger from "@material-ui/core/useScrollTrigger";
import Zoom from "@material-ui/core/Zoom";
import Box from "@material-ui/core/Box";

const BackToTop = ({ children }) => {
  const trigger = useScrollTrigger();

  const handleClick = (event) => {
    const anchor = (event.target.ownerDocument || document).querySelector(
      "#back-to-top-anchor"
    );

    if (anchor) {
      anchor.scrollIntoView({
        behavior: "smooth",
        block: "center",
      });
    }
  };

  return (
    <Zoom in={trigger}>
      <Box
        onClick={handleClick}
        role="presentation"
        sx={{ position: "fixed", bottom: 16, right: 16 }}
      >
        {children}
      </Box>
    </Zoom>
  );
};

export default BackToTop;

Open ./components/Header.jsx and:-

import Fab from "@material-ui/core/Fab";
import KeyboardArrowUp from "@material-ui/icons/KeyboardArrowUp";
import BackToTop from "./BackToTop";

Add the id to Offset component

<Offset id="back-to-top-anchor" />

Add the code:

<BackToTop>
  <Fab color="secondary" size="large" aria-label="back to top">
    <KeyboardArrowUp />
  </Fab>
</BackToTop>;
  • Add the id="back-to-top-anchor" to the Offset styled component
  • Add the BackToTop Component below it
  • Add a floating action button (FAB) with a keyboard up icon

Save the file and scroll down, you'll see the floating back to top icon

We successfully create the the Header component with Nextjs & Material-UI v5

ctrl + c to close the development server

Take a rest!

Next~

We'll build a hero section:

Build Hero Section With Nextjs & Material-UI v5
The hero section gives an excellent impression to the visitor when they first visit your website. You never have a second chance to give your visitor a first impression. Hence, the hero section is one of the most element of a website. We need a relevant and beautiful image to

Feel free to tweet me if you have any feedback, improvement, suggestion or error.

Something to say about this tutorial? Comment on the Tweet below: