Create A Simple Production Website With Material-UI v5 & Nextjs.

We will learn how to build and deploy a simple restaurant website with Material-UI v5 & Nextjs in this tutorial.

Create A Simple Production Website With Material-UI v5 & Nextjs.
Nextjs + Material-UI

This tutorial will teach us how to use Material-UI version 5 & Nextjs to build a simple & responsive restaurant website.

Prerequisite

πŸ“Œ I'll write mui5 to reduce the repetition of typing Material-UI v5.

πŸ“Œ This tutorial is updated to the MUI5 stable version

πŸ“Œ Material-UI 5 stable version had been released. MUI5 has a new website address now.

MUI: The React component library you always wanted
MUI provides a simple, customizable, and accessible library of React components. Follow your own design system, or start with Material Design. You will develop React applications faster.

Let's get started~

Set up MUI5 for Nextjs Project

What do we want to achieve?

  • Set up a template to use the two frameworks.
  • Set up the MUI theme context
  • Add CssBaseline to reset/normalize the browser's User Agent Style sheets.
  • Set up server code to render the MUI5 component when the data reach the client.

Open the terminal of your choice,

cd toFolder/youWantToCreateTheProject
npx create-next-app mui5-next
cd mui5-next

πŸ“Œ "mui5-next" is the name I choose for the project. You can name it as you like.

npm install @mui/material@next @mui/icons-material@next @emotion/cache @emotion/react @emotion/server @emotion/styled 

πŸ“Œ Please note that the MUI team rename the package to @mui/material for version 5 stable.

MUI5 is built on top of the emotion styled component library. The emotion's cache and server packages are required for the Nextjs project.

  "dependencies": {
    "@emotion/cache": "^11.4.0",
    "@emotion/react": "^11.4.0",
    "@emotion/server": "^11.4.0",
    "@emotion/styled": "^11.3.0",
    "@mui/icons-material": "^5.0.0-rc.1",
    "@mui/material": "^5.0.0-rc.1",
    "next": "^11.1.0",
    "react": "17.0.2",
    "react-dom": "17.0.2"
  }

Open the project on VS Code,

Right-click to open the file or,

Type the following bash command in your terminal,

code .

Run the project

Start the local development server,

ctrl + ` to open the terminal

Visit http://localhost:3000/

You shall see:

If you are having trouble, start the project in development mode.

ctrl + c to stop the development server,

npm update

And start the development server again,

npm run dev

You shall run the development as usual.

Customize _app.js for the project

Open pages/_app.js

Add the code:

import Head from 'next/head';
import { ThemeProvider } from '@mui/material/styles';
import CssBaseline from '@mui/material/CssBaseline';
import { CacheProvider } from '@emotion/react';
import createEmotionCache from '../styles/createEmotionCache';

import "/styles/globals.css";
import theme from '../styles/theme';


// Client-side cache, shared for the whole session of the user in the browser.
const clientSideEmotionCache = createEmotionCache();

export default function MyApp(props) {
  const { Component, emotionCache = clientSideEmotionCache, pageProps } = props;

  return (
    <CacheProvider value={emotionCache}>
      <Head>
        <title>MUI5 Nextjs</title>
        <meta name="viewport" content="initial-scale=1, width=device-width" />
      </Head>
      <ThemeProvider theme={theme}>
        {/* CssBaseline kickstart an elegant, consistent, and simple baseline to build upon. */}
        <CssBaseline />
        <Component {...pageProps} />
      </ThemeProvider>
    </CacheProvider>
  );
}
  • Change the title to "MUI5 Nextjs." or the title suits your project.
  • Remain styles/global.css. We will add some global styles later.

Next, create the emotion cache file,

touch styles/createEmotionCache.js

Add the code:

import createCache from '@emotion/cache';

export default function createEmotionCache() {
  return createCache({ key: 'css' });
}

πŸ“Œ This code is from the MUI example on GitHub. I modified it a bit cause I not using the src file.

πŸ“Œ I put the createEmotionCache.js in the styles directory because I don't want to create an addition lib (library) folder to store a file. (as the MUI official example below)

material-ui/_app.js at master Β· mui-org/material-ui
Material-UI is a simple and customizable component library to build faster, beautiful, and more accessible React applications. Follow your own design system, or start with Material Design. - materi...

Nextjs is a hybrid framework that involves server rendering. The setup is different compared to a static site generation framework like Gatsbyjs.

In case you want to know more about the server-side rendering, visit.

Server rendering - Material-UI
The most common use case for server-side rendering is to handle the initial render when a user (or search engine crawler) first requests your app.

Customize _document.js for the project

Create the file,

touch pages/_document.js

Add the code:

import * as React from 'react';
import Document, { Html, Head, Main, NextScript } from 'next/document';
import createEmotionServer from '@emotion/server/create-instance';
import createEmotionCache from '../styles/createEmotionCache';

import theme from '../styles/theme';

export default class MyDocument extends Document {
  render() {
    return (
      <Html lang="en">
        <Head>
          {/* PWA primary color */}
          <meta name="theme-color" content={theme.palette.primary.main} />
          <link
            rel="stylesheet"
            href="https://fonts.googleapis.com/css?family=Roboto:300,400,500,700&display=swap"
          />
        </Head>
        <body>
          <Main />
          <NextScript />
        </body>
      </Html>
    );
  }
}

// `getInitialProps` belongs to `_document` (instead of `_app`),
// it's compatible with static-site generation (SSG).
MyDocument.getInitialProps = async (ctx) => {
  // Resolution order
  //
  // On the server:
  // 1. app.getInitialProps
  // 2. page.getInitialProps
  // 3. document.getInitialProps
  // 4. app.render
  // 5. page.render
  // 6. document.render
  //
  // On the server with error:
  // 1. document.getInitialProps
  // 2. app.render
  // 3. page.render
  // 4. document.render
  //
  // On the client
  // 1. app.getInitialProps
  // 2. page.getInitialProps
  // 3. app.render
  // 4. page.render

  const originalRenderPage = ctx.renderPage;

  // You can consider sharing the same emotion cache between all the SSR requests to speed up performance.
  // However, be aware that it can have global side effects.
  const cache = createEmotionCache();
  const { extractCriticalToChunks } = createEmotionServer(cache);

  ctx.renderPage = () =>
    originalRenderPage({
      enhanceApp: (App) => (props) => <App emotionCache={cache} {...props} />,
    });

  const initialProps = await Document.getInitialProps(ctx);
  // This is important. It prevents emotion to render invalid HTML.
  // See https://github.com/mui-org/material-ui/issues/26561#issuecomment-855286153
  const emotionStyles = extractCriticalToChunks(initialProps.html);
  const emotionStyleTags = emotionStyles.styles.map((style) => (
    <style
      data-emotion={`${style.key} ${style.ids.join(' ')}`}
      key={style.key}
      // eslint-disable-next-line react/no-danger
      dangerouslySetInnerHTML={{ __html: style.css }}
    />
  ));

  return {
    ...initialProps,
    // Styles fragment is rendered after the app and page rendering finish.
    styles: [...React.Children.toArray(initialProps.styles), ...emotionStyleTags],
  };
};
  • Feel free to delete all the comments. I left them here so that you can read them.
  • MUI5 is using Roboto font. It's essential to include the Roboto font in _document.js.
  • If you're not building a progressive web application, feel free to delete the "theme-color" meta tag and the theme import module.
  • The _document.js code is from the MUI team example:
material-ui/_document.js at master Β· mui-org/material-ui
Material-UI is a simple and customizable component library to build faster, beautiful, and more accessible React applications. Follow your own design system, or start with Material Design. - materi...

Create the theme file

Create a file,

touch styles/theme.js

Add the code:

import { createTheme, responsiveFontSizes } from "@mui/material/styles";
import { deepPurple, amber } from "@mui/material/colors";

// Create a theme instance.
let theme = createTheme({
  palette: {
    primary: deepPurple,
    secondary: amber,
  },
});

theme = responsiveFontSizes(theme);

export default theme;
  • Set up the theme to use deep purple as the primary colour and amber as the secondary colour.
  • I randomly pick an MUI's colour to show how we can use it as the theme colour.
  • Set up MUI responsive font based on the screen size.

Initial The Homepage

Open pages/index.js, replace the code with:

import Container from "@mui/material/Container";

const Homepage = () => {
  return (
    <Container maxWidth="sm">
    <h1>Home Page</h1>
    <p>lorem*15</p>
    </Container>
  );
};

export default Homepage;
  • Add 15 lines of lorem ipsum to the <p> tag. We will use it for scrolling purposes later.
  • I am using Emmet to insert 15 lines of lorem ipsum with the shortcode, as you can see above.
import Container from "@mui/material/Container";

const Homepage = () => {
  return (
    <Container maxWidth="sm">
      <h1>Home Page</h1>
      <p>
        Lorem ipsum dolor sit amet consectetur, adipisicing elit. Eos,
        cupiditate! Odio, aliquam soluta vel, eum illum corrupti incidunt nobis
        porro mollitia itaque reiciendis. Aut, minus dolore! Delectus pariatur
        praesentium dolorem? In at, quibusdam vero eligendi provident veritatis
        ipsam suscipit nisi similique nulla est magni harum. Cumque maiores eos
        alias, aperiam ea deleniti voluptatem culpa a perferendis accusantium,
        necessitatibus velit laborum. Molestias, reprehenderit accusantium. Ad
        ipsa maiores, animi labore est voluptate eos aperiam iste adipisci sed
        dolor consequatur dolore provident tenetur ipsum! Velit laudantium
        excepturi accusantium numquam, similique nemo repellat impedit.
        Reiciendis illo quibusdam atque possimus aliquid. Illo in voluptatum
        nostrum quia nam fugit ipsa enim hic, eius sit qui recusandae dolorem
        quis dolor ut deleniti est possimus. Fugiat, aliquam quam. Itaque
        officiis culpa laborum voluptatum vel exercitationem temporibus, eos ad,
        ab eum inventore, hic quaerat ea cumque dignissimos dolorem maxime.
        Adipisci illo aspernatur eum. Minus, amet pariatur. Culpa, ratione
        expedita. Nemo doloribus dignissimos, deleniti sit veniam quae deserunt
        a nam sed, voluptas officia est recusandae consectetur exercitationem
        omnis? Neque impedit aspernatur tempora iusto distinctio nam amet
        recusandae inventore culpa eligendi! Sapiente accusantium aut animi eos
        iusto officia ipsum exercitationem illo quae, cumque quidem, nemo amet
        ad dicta fugit laboriosam mollitia soluta ratione corrupti maxime odit.
        Ipsum tenetur architecto vel ullam. Hic at, veritatis dolore, beatae
        totam amet alias unde odit veniam dolorum eos molestias quidem quo
        explicabo asperiores libero assumenda nulla velit nihil. Rerum error
        culpa cum voluptatibus beatae magnam? Repudiandae ratione iure,
        similique rerum dolore consectetur animi vitae qui delectus pariatur
        sapiente atque eos quaerat sed consequatur, quo suscipit optio harum
        aliquam adipisci vero. Nihil neque nisi eius. Laborum. Voluptatem
        voluptate officia numquam ut quae. Quod facilis pariatur in nostrum cum
        quidem at nesciunt fugiat est unde enim commodi, animi nihil, recusandae
        rerum ducimus sint qui assumenda distinctio id! Natus ea fugit molestiae
        nostrum! Obcaecati nisi itaque harum. Officia temporibus nobis
        repellendus beatae dolorem tempore accusamus! Quas debitis quos incidunt
        dolore aliquam, quam assumenda alias, totam omnis aspernatur saepe? Non
        debitis ab quis inventore vero. Possimus dicta labore quibusdam
        consequatur incidunt ea molestias fugit tenetur facere autem reiciendis
        animi amet, eligendi nobis impedit sunt consectetur deserunt doloremque
        nisi! Minus. Accusamus nam a soluta porro repudiandae quis at cupiditate
        ad impedit aperiam recusandae blanditiis ut enim repellendus velit qui
        cumque alias doloribus, accusantium voluptas quam eius! Consequuntur
        velit commodi minima. Saepe magnam fugit est et! Vel cumque modi itaque
        maiores vitae id, esse autem provident necessitatibus eius porro fuga,
        quod laborum, veritatis eum repellendus voluptatem aliquam ad
        perferendis quasi corrupti? Voluptates blanditiis earum optio omnis
        consectetur sapiente? Aliquid, expedita distinctio enim quos quis autem
        totam blanditiis ducimus laborum temporibus non. Repellat eaque aliquid
        perspiciatis fuga iste nam! Ut, iusto suscipit.
      </p>
    </Container>
  );
};

export default Homepage;

Remove unused style

Remove the file,

rm styles/Home.module.css

Remove the API file

rm pages/api/hello.js

Well, we successfully initial a Nextjs & MUI5's project.

You can commit and push to your GitHub and use it as a template for your future MUI5 & Nextjs' project.

ctrl + c to stop the development server,

Take a rest if needed.

Next~

We are going to build a Header component.


Header Component With Navbar

The header component is an essential component of a website. You need to provide a section with a navigation list for the visitor to explore your website.

What do we want to achieve?

  • 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 an active routing class.
  • A side drawer for mobile devices.

πŸ“Œ You can use any name you want. E.g., MNLink, theLink, YourLink, Link, and so on.

This component is a lifesaver. We don't need to consider whether we should use NextLink without MUI styles or the MUILink without the Nextjs routing feature.

MUI team combine them into one component.

ctrl + ` to open VS Code's terminal,

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:

/* eslint-disable jsx-a11y/anchor-has-content */
import * as React from 'react';
import clsx from 'clsx';
import { useRouter } from 'next/router';
import NextLink from 'next/link';
import MuiLink from '@mui/material/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 Link = 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 Link;

This component comes with:

  • The MuiNextLink component comes with an active class setup.
  • We need to provide the active style in styles/globals.css
  • You can skip nesting the <a> tag like typical Next Link.
  • You can use it as <a> tag to link externally.

You can refer to the example code:

material-ui/Link.js at master Β· mui-org/material-ui
Material-UI is a simple and customizable component library to build faster, beautiful, and more accessible React applications. Follow your own design system, or start with Material Design. - materi...

Define The Active Style.

Open styles/global.css

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:

Routing libraries - MUI
By default, the navigation is performed with a native <code>&lt;a&gt;</code> element. You can customize it to use your own router. For instance, using Next.js’s Link or react-router.

The Header Component

Create the file,

touch components/Header.jsx

You can use the JS extension. The reason I use JSX extension:

  • It's a React Component.
  • JSX in VS Code come with the Emmet features.

If you prefer to use JS extension with the Emmet features, you can configure it on VS Code by following this setup.

Add the code:

import AppBar from "@mui/material/AppBar";
import Container from "@mui/material/Container";
import Toolbar from "@mui/material/Toolbar";
import { styled } from "@mui/system";

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;

What do we do?

  • We are setting the <AppBar /> position to fixed cause the content is hidden behind the AppBar. The AppBar will cover the top part if we use a hero image. The Offset styled component sets an invisible height equivalent to the AppBar height to push the page down.
  • The < Container /> centres content horizontally and apply left and right margin consistently to all screen sizes.
  • The < Container />separate the logo and navigation list later.

Absolute Imports and Module path aliases

Let's setup

ctrl + c to stop the development server,

touch jsconfig.json

Add the code:

{
  "compilerOptions": {
    "baseUrl": ".",
    "paths": {
      "@components/*": ["components/*"]
    }
  }
}

Add The Header Component to _app.js

Open ./pages/_app.js

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

Wait, if you setup path aliases, as shown above, you can import like this:

import Header from "@components/Header";

Add <Header /> below <CssBaseline />

Save the files,

npm run dev

Start the development server and visit http://localhost:3000/.

You shall see:

  • We add it to _app.js so that every page has the <Header /> component. We don't need to add it manually to all the pages.

Open ./components/Header.jsx

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

Import the packages to the <Header /> component

import IconButton from "@mui/material/IconButton"
import Home from "@mui/icons-material/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 color code is redundant. I want to show you how to use the MUI theme in the sx prop, and you can just set color to white.

Save the files, you shall see:

In ./components/Header.jsx

Create the navigation link constant,

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

We can just declare the navigation link constant in the <Header /> component for a small website.

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

Initial All The Pages

Open ./pages/about-us.js

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

export default AboutUsPage;

Open ./pages/menu.js

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

export default MenuPage;

Open ./pages/catering.js

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

export default CateringPage;

Open ./pages/contact.js

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

export default ContactPage;

After, check the route and page by navigating to:

  • http://localhost:3000/about
  • http://localhost:3000/menu
  • http://localhost:3000/catering
  • http://localhost:3000/contact

Did you see the header appears on every page?

Add The Navbar

Create the file,

touch components/Navbar.jsx

Add the code:

import Stack from "@mui/material/Stack";
import Toolbar from "@mui/material/Toolbar";
import MuiNextLink from "./MuiNextLink";

const Navbar = ({ navLinks }) => {
  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 Navbar;
  • We hide the Navbar for "xs" and "sm" screen sizes to display the hamburger menu later.
  • We display the Navbar as flex for screen size "md" and above.
  • We set the Navbar opacity to 0.7, and it will turn to 1 when it's active.

Add The Navbar Component To Header

Open ./components/Header.jsx

import Navbar from './Navbar'

Add the component and pass the props,

<Navbar navLinks={navLinks} />

Save the file, and you shall see:

Click all the links to check the pages, and active class is working or not.

πŸ“’ I left an error here with intention. One of the links shall get an error 404 page. Could you fix it?

The Side Drawer

Create the file,

touch components/SideDrawer.jsx

Add the code:

import Box from "@mui/material/Box";
import Typography from "@mui/material/Typography";
import Drawer from "@mui/material/Drawer";
import IconButton from "@mui/material/IconButton";
import Menu from "@mui/icons-material/Menu";
import MuiNextLink from "./MuiNextLink";
import { useState } from "react";

const SideDrawer = ({ navLinks }) => {
  const [state, setState] = 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: 5,
            my: 2,
            textTransform: `uppercase`,
          }}
        >
          <MuiNextLink sx={{ color: "common.white" }} href={path}>
            {title}
          </MuiNextLink>
        </Typography>
      ))}
    </Box>
  );

  return (
    <>
      <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>
    </>
  );
};

export default SideDrawer;
  • Display the hamburger menu for "xs""an" "sm""an" hide it for screen size "md""an" above.
  • Slide in the side drawer from the right side when click.

Add Sidebar To Header

Open ./components/Header.jsx

import SideDrawer from "./SideDrawer";

Add the component and pass the props.

<SideDrawer navLinks={navLinks} />

Save the file,

Resize the screen down to 959px and below. You shall see the hamburger menu show up:-

Click the hamburger menu, you shall see

Hide on Scroll

It is optional. I recommend adding hide on scroll feature to the header for the text-rich website so that your visitor will have more space to read on a mobile phone.

Create the file,

touch components/HideOnScroll.jsx

Add the code:

import useScrollTrigger from "@mui/material/useScrollTrigger";
import Slide from "@mui/material/Slide";


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

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

export default HideOnScroll;

Add HideOnScroll to Header

Open ./components/Header.jsx

import HideOnScroll from "./HideOnScroll";

Wrap the < AppBar /> with <HideOnScroll />

Save the file,

Scroll down to check the header is hide or not.

Back To Top Button

It is optional. I recommend adding this button for long page website so that your visitor can scroll back to the top quickly.

touch components/BackToTop.jsx

Add the code:

import useScrollTrigger from "@mui/material/useScrollTrigger";
import Zoom from "@mui/material/Zoom";
import Box from "@mui/material/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;

Add BackToTop To Header

Open ./components/Header.jsx

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

Add the id to <Offset />

<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 on the homepage. You shall see the floating BackToTop button shows up.

We successfully create the header with modern features.

ctrl + c to stop the development server,

Take a rest!

Next~

We'll build A hero section.


Hero Section

The hero section gives an excellent impression to the visitor when they first visit your website, and you never have a second chance to show your visitor the first impression. Hence, the hero section is one of the essential elements of a website.

What do we want to achieve?

  • A beautiful image to attract the visitor.
  • A big and stand out's title to tell visitors what is the website.
  • A subtitle to elaborate and ensure that the visitor land on the right page.

We'll use Next Image for better image optimization.

Let's roll

Initial The Hero Component

Create the file:

touch components/Hero.jsx

Add the code:

const Hero = ( ) => {
    return ( );
}
 
export default Hero;

Define The Hero Container

import Grid from "@mui/material/Grid";

Add the code:

<Grid
  component="section"
  container
  sx={{
    position: `relative`,
    height: "100vh",
    width: `100vw`,
    overflow: `hidden`,
    zIndex: -100,
    mb: 15,
  }}
></Grid>
  • We set the position to "relative" to add a filter layer and set its position later.
  • Set to z index to negative so that the hero section will not block the back to top button.
  • We set the image's overflow to be hidden in case it flows out from the container.

The Hero Image

I am going to use an image by Pablo MerchΓ‘n Montes from Unsplash. If you want to use the same image, you can download it with this link.

  1. Download the medium size will do.
  2. Rename it to "home-hero."
  3. Ad" to "the public folder.

The Image Props

De-structure the props we want to use for the Hero component.

{imgSrc, imgAlt}

πŸ“Œ You can name it whatever you want,

Render Hero Image With Next Image

import Image from "next/image";

Add the code:

<Image src={imgSrc} alt={imgAlt} layout="fill" objectFit="cover" />
  • The image container's relative position is important to use "Next image". The Next Image uses absolute position by default. Without the relative position, the image will flow out of the container. Typically, appear on top of the page.

Add The Hero Component to Homepage

Open ./pages/index.js

import Hero from '@components/Hero'

Wrap the Hero & Container component with React fragment.

Add the code:

<Hero
  imgSrc="/homepage-hero.jpg"
  imgAlt="satified woman eating in restaurant"
/>

Save the files, start the development server.

npm run dev

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

You shall see:

Add A Filter Layer

<Grid
  container
  sx={{
    position: "absolute",
    inset: 0,
    backgroundColor: "rgba(0,0,0, .7)",
  }}
></Grid>
  • Use the inset property to set the background covering the whole image container.
  • Set the background to black colour with 30% transparency.

Add the code below < Image />

Save the file, and you shall see:

Add Title & Subtitle

import Typography from "@mui/material/Typography";

De-structure the title & subtitle props:

Add the code:

<Grid
  container
  item
  flexDirection="column"
  justifyContent="center"
  alignItems="center"
>
  <Typography
    variant="h1"
    align="center"
    gutterBottom
    sx={{
      color: "secondary.main",
      fontWeight: 400,
    }}
  >
    {title}
  </Typography>
  <Typography
    component="p"
    variant="h3"
    align="center"
    color="common.white"
    sx={{
      mb: 10,
    }}
  >
    {subtitle}
  </Typography>
</Grid>
  • The Grid container set the flex-direction to column to display the content vertically.
  • If you are not sure what is align-items, check out this flexbox cheat sheet from CSS-Trick.
  • Setting colour in sx prop and Typography API shows that we can use two different methods to style the colour.

Add Title & Subtitle Content

Open ./pages/index.js

Add title & subtitle to <Hero />

<Hero
  imgSrc="/home-hero.jpg"
  imgAlt="satified woman eating in restaurant"
  title="De West Sakura"
  subtitle="Best Western &amp; Japanese Fusion Restaurant In Town"
/>

Save all the files, you shall see:

A Hint To Scroll Down

Setting the hero section height to "100vh" cover up the whole device's screen. The visitor might not know they can scroll down, especially for mobile devices. Hence, we need to add a text and icon to tell the visitor there are content below.

import ArrowDownward from "@mui/icons-material/ArrowDownward";

Add the code:

<Typography component="p" variant="h6" color="secondary" gutterBottom>
  Scroll
</Typography>
<ArrowDownward fontSize="large" color="secondary" />

Save the file, check the hero section.

We successfully create a hero section with Material-UI & Nextjs.

ctrl + c to stop the development server,

Take a rest!

Next~

We're going to create the brief about section on the homepage.


Short About Section On Homepage

This section provides a short introduction about the restaurant, chef or other value proposition. Then, the visitor can click a call to action button to navigate to the about page if they're interested and would like to know more about us.

What do we want to achieve?

  • Two-column layouts with one image column and a paragraph column.
  • On mobile devices, the image column will be on top and the paragraph column on the bottom.

Create The Component

Create the file,

touch components/SectionAbout.jsx

Ya, This might be redundant. But this is one of the ways you can make the page file cleaner. Coding is flexible, and you can always adapt the way you like the most.

You can code this on ./pages/index.js directly.

Initial The Component

import { Container, Grid } from "@mui/material";

const SectionAbout = () => {
  return (
    <Container component="section" maxWidth="md" sx={{ mb: 15 }}>
      <Grid container spacing={3}>
        <Grid item xs={12} sm={6}></Grid>
        <Grid item xs={12} sm={6}></Grid>
      </Grid>
    </Container>
  );
};

export default SectionAbout;

πŸ“Œ Did you found out that we're using name import instead of default import in this tutorial?

Again, coding is flexible. You can use the type of import that works best for you.

Add To Homepage

Open pages/index.js

import SectionAbout from "@components/SectionAbout";

Add <SectionAbout /> below the <Hero />.

unDraw SVG image

If you want to use the same photo:

  1. Visit unDraw
  2. Search for "chef."
  3. Change the colour #673AB7 to match the theme
  4. Download the SVG version
  5. Rename it to "chef."
  6. Add to "public" directory

The Image Column

import Image from "next/image";

Add the code inside the first <Grid />:

<Image
  src="/chef.svg"
  alt="A Chef"
  layout="responsive"
  width={800}
  height={600}
/>
  • In this tutorial, I use the Next image responsive layout. Using a "layout responsive" is overkill for the image not changing dramatically. However, I want to show the example of using the Next image with another approach.
  • W800px x H600px is to set the image ratio at 4:3. You can use a smaller figure (W400px xH300px) in this case or a different ratio.

Save the file,

npm run dev

Start the development server and check the homepage,

You shall see the chef image

The Paragraph Column

import Typography from "@mui/material/Typography";

Add the code to second <Grid />:

  <Typography component="h2" variant="h4" textAlign="center" gutterBottom>
    A Japanese Chef Who Love Western Food
  </Typography>
     <Typography textAlign="center">
            {`We mix Japanese and Western ingredients and cooking methods. Provide you
    with a different tasting dimension with the fusion food in our restaurant.
    Don't miss the chance to surprise your tongue!`}
          </Typography>
  • We use template string {` your paragraph`} to escape the quote.

Save the file and check the homepage

The Call To Action (CTA) Button

import MuiNextLink from "@components/MuiNextLink";
import Button from "@mui/material/Button";
<MuiNextLink href="/about-us">
  <Button variant="outlined" size="large">
    About Us
  </Button>
</MuiNextLink>

Save the file,

You shall see:

  • Ops~ the button is not position centre.
  • There is an underline on the CTA.

Adjust Positioning

Add these attributes to the second <Grid />:

container flexDirection="column" justifyContent="center" alignItems="center"

Remove the underline

Set underline to none on <MuiNextLink />

<MuiNextLink href="/about-us" underline="none">
  <Button variant="outlined" size="large">
    About Us
  </Button>
</MuiNextLink>

Adjust The White Space

Add a margin-bottom to <Typography /> for the following paragraph,

<Typography textAlign="center" sx={{ mb: 5 }}>
  {`We mix Japanese and Western ingredients and cooking methods. Provide you
    with a different tasting dimension with the fusion food in our restaurant.
    Don't miss the chance to surprise your tongue!`}
</Typography>

Save the file, you shall see:

Try resizing the window and the responsiveness in action.

Cool~ We build the short about section on the homepage.

ctrl + c to stop the development server,

Take a rest if you need,

Next

Let's build an image section


An Image Transition Section

This tutorial is short as it's almost similar to build a hero section.

What is an image transition section?

Just imagine you visit a website full of the paragraph. How do you feel?

The visitor either force themselves to read painfully or leave your website. But, I believe 95% of the visitor will drop your website immediately. Why read with pain when there are thousands of similar websites.

Hence, we need a beautiful and relevant image section to separate your content.

What do we want to achieve?

  • An image section to fill the entire width of all devices.
  • The image can be framed but not shrink.

Let's get started!

Initial The Component

Create the file,

touch components/SectionImage.jsx

Add the code:

import { Box } from "@mui/material";

const SectionImage = () => {
  return (
    <Box
      component="section"
      container
      sx={{
        position: "relative",
        width: "100%",
        height: "60vh",
        overflow: "hidden",
        zIndex: -100,
      }}
    ></Box>
  );
};

export default SectionImage
  • Since we don't need to centre the title and subtitle, we'll use the Box component.
  • You could adjust the height with "vh" to suit your use case.

The Section Image

I'll use an image by Jay Wennington on Unsplash. If you want to use the same image, you can download it with this link.

  • Download the medium size
  • Rename it to "fusion-food."
  • Add to the public directory.
import Image from "next/image";

De-structure the props, and add the Next image:

{imgSrc, imgAlt}

Add the image inside <Box />

 <Image src={imgSrc} alt={imgAlt} layout="fill" objectFit="cover" />

Add Image Section To Homepage

Open ./pages/index.js

import SectionImage from "@components/SectionImage";

Remove the container code and add the code below <SectionAbout />:

 <SectionImage imgSrc="/fusion-food.jpg" imgAlt="fusion food" />

Save the file,

npm run dev

Start the development server and scroll to the image transition section, and check the image:

  • Open the Chrome developer tools (Hit F12)
  • Toggle device toolbar (ctrl + shift + m)
  • No matter how you resize the width, the image is always attached to the edge

πŸ“Œ If you view the image section on the desktop, you will find that the top and bottom images are cut off. This is the result we want instead of shrink the image to fit the image container.

Add The Filter Layer

It's optional. Since we don't have the title and subtitle, we don't need a dark layer to increase the contrast ratio.

My perspective is the image is too bright. Hence, I apply a light black layer to darken the image.

Add the code below the <Image />:

<Box
  sx={{
    position: "absolute",
    inset: 0,
    backgroundColor: "rgba(0,0,0, .2)",
  }}
/>
  • Save and check out the image in your browser.
  • Adjust the alpha value of RGBA to find the best value you like the most.

We successfully build the image transition section.

ctrl + c to stop the development server,

Take a rest if you need,

Next~

We're going to build a customer review section.


Customer Review Section

You often hear about DRY (Don't Repeat Yourself) while learning to code. Many Master, Pro, Ninja, Barbarian, Magician, Monk, Code Fu Martial Artists, and some senior coder will tell you not to repeat your code in their video or tutorial.

(Forgive me for the long cold joke 😏)

You might worry people criticize your code is noobie. You keep finding solution after solution (after solution x N), tutorial after tutorial (You read and watch N articles and videos) to write your code clean, pro, dynamic and programming. Six months later, you can't even launch your first website - You're wasted! ~ Penta kill by the philosophy.

Should you care about your DRY code?

Not at all! Especially you just started to learn code, and you can always update your code later. As long as the code work, launch the website that brings you the leads, sales and incomes.

Today, I will show off my "NOT DRY" code by building a customer review section with Nextjs and Material-UI v5.

Let's get started~

touch components/SectionReview.jsx	

Initialize The Component

import { Container, Grid, Typography } from "@mui/material";

const SectionReview = () => {
  return (
    <Container maxWidth="md" sx={{ my: 15 }}>
      <Typography variant="h2" textAlign="center" sx={{ mb: 10 }}>
        Customer Review
      </Typography>
      <Grid container spacing={2}>
        <Grid
          container
          item
          justifyContent="center"
          xs={12}
          sm={6}
          md={4}
        ></Grid>
      </Grid>
    </Container>
  );
};

export default SectionReview;
  • Don't skip the justify-content attribute, and it'll centre the child probably for mobile screen size.

Add Review Section To Homepage

  • You might already notice, my naming convention is weird. It's a personal preference, and the reason I name like this is for file sorting purposes. Choose the naming convention work best for you.

Import the component to the homepage:

import SectionReview from "@components/SectionReview";

Save the file.

npm run dev

Start the development server,

You shall see:

Review Card Component

Create the file:

mkdir components/ReviewCard
touch components/ReviewCard/Alex.jsx

Add the code:

import {
  Avatar,
  Card,
  CardContent,
  CardHeader,
  Rating,
  Typography,
} from "@mui/material";
import { red } from "@mui/material/colors";

const AlexReview = () => {
  return (
    <Card sx={{ maxWidth: 345 }}>
      <CardHeader
        avatar={
          <Avatar sx={{ bgcolor: red[500] }} aria-label="Alex profile letter">
            AL
          </Avatar>
        }
        title="Alex"
      />
      <CardContent>
        <Rating value={5} readOnly />

        <Typography variant="body2" color="text.secondary">
          I never taste something like this before. Japanese mix Western
          cuisine. Some good, some weird taste to me. Overall the cooking tastes
          good.
        </Typography>
      </CardContent>
    </Card>
  );
};

export default AlexReview;
  • Ya, I hardcoded the Avatar colour, Avatar letter, Rating, and description.
  • I am going to create two more similar review components.
  • Suppose your customer allows you to use their profile picture. You can add it to the MUI avatar component.

Just swatch the <Avatar /> code with:

<Avatar alt="Alex" src="/profile/folder.jpg" />

Add Alex Review To Review Section

Open ./components/SectionReview.jsx

import AlexReview from "./ReviewCard/Alex";

Add the component:

Save the file, you shall see:

Add More Review

Create the files:

touch components/ReviewCard/Mona.jsx
touch components/ReviewCard/Shanen.jsx

Add Mona review code:

import {
  Avatar,
  Card,
  CardContent,
  CardHeader,
  Rating,
  Typography,
} from "@mui/material";

import { blueGrey } from "@mui/material/colors";

const MonaReview = () => {
  return (
    <Card sx={{ maxWidth: 345 }}>
      <CardHeader
        avatar={
          <Avatar
            sx={{ bgcolor: blueGrey[500] }}
            aria-label="Mona profile letter"
          >
            MO
          </Avatar>
        }
        title="Mona"
      />
      <CardContent>
        <Rating value={5} readOnly />
        <Typography variant="body2" color="text.secondary">
          Sushi with gravy mushroom sauce, fried chicken with teriyaki sauce,
          watermelon with green tea ice cream. Cool! New fresh taste to me.
        </Typography>
      </CardContent>
    </Card>
  );
};

export default MonaReview;

Add Shanen review code:

import {
  Avatar,
  Card,
  CardContent,
  CardHeader,
  Rating,
  Typography,
} from "@mui/material";
import { orange } from "@mui/material/colors";

const ReviewShanen = () => {
  return (
    <Card sx={{ maxWidth: 345 }}>
      <CardHeader
        avatar={
          <Avatar
            sx={{ bgcolor: orange[500] }}
            aria-label="shanen profile letter"
          >
            SH
          </Avatar>
        }
        title="Shanen"
      />
      <CardContent>
        <Rating value={4.5} precision={0.5} readOnly />
        <Typography variant="body2" color="text.secondary">
          Wasabi with steak, huh? What a fusion! Taste a bit weird but
          acceptable. I reserve my last star until my next visit.
        </Typography>
      </CardContent>
    </Card>
  );
};

export default ReviewShanen;
  • We need to add 0.5 precision to <Rating /> to set the value to 4.5.

Add Them To Review Section

Open ./components/SectionReview.jsx

import MonaReview from "./ReviewCard/Mona";
import ShanenReview from "./ReviewCard/Shanen";

Add the code:

  <Grid container item justifyContent="center" xs={12} sm={6} md={4}>
    <MonaReview />
  </Grid>
  <Grid container item justifyContent="center" xs={12} sm={6} md={4}>
    <ShanenReview />
  </Grid>

Save the file, you shall see:

You can continue to add more review cards.

We successfully build the customer review section with Nextjs & Material-UI v5.

ctrl + c to stop the development server,

Take a rest if you need,

Next~

We are going to build a navigation card


This section act like secondary navigation with brief information about the core business page. I recommend inserting your copywriting here to attract the visitor to click to view the core page.

Before starting the section, I would like to add an image section between the review and navigation sections.

It's easy. We can reuse the "SectionImage" component.

I use a sushi image from Unsplash if you want to use the same image. Click Unsplash's link below:

A Sushi photo by Jakub Dziubak on Unsplash.

  • Click Unsplash and Download the medium size
  • Rename it to "sushi".
  • Add it to the public folder.

Add To Homepage

Open ./pages/index.js

Add the <SectionImage /> below <SectionReview />

<SectionImage imgSrc="sushi.jpg" imgAlt="fusion sushi" />

Start the file,

npm run dev

Check your homepage. You shall see the new image section.

Create the file,

touch components/NavigationCard.jsx

Initialize The Component

const NavigationCard = ({ imgSrc, imgAlt, title, desc, pagePath, ctaText }) => {
  return ();
};

export default NavigationCard;
  • Destructure "imgSrc", "imgAlt", "title" and "desc" (description), "pagePath" and "ctaText" (call to action) props to use in the component later.

Initially, I want to use the "next image" component, but after some trial and error. I still can't figure out how to implement "next image" with the MUI5 card component.

Hence, I will use the MUI5 card native component to code this card.

(If you know how to use "next image" with the MUI5 card component, or you come across an article or tutorial about it. Please share with me on Twitter @AnsonLowZF).

The Card Image

I'll use these images from Unsplash if you want to use the same image. Click Unsplash's link below:

A food menu photo by Josh Bean on Unsplash.

  • Download
  • Rename it to "menu"
  • Add it to the public folder.

A catering photo by NeONBRAND on Unsplash.

  • Download
  • Rename it to "catering"
  • Add it to the public folder.

πŸ“Œ For this part, download the small size will do.

import Card from "@mui/material/Card";
import CardMedia from "@mui/material/CardMedia";

Add the code:

<Card sx={{ maxWidth: 480 }}>
  <CardMedia sx={{ height: 270 }} image={imgSrc} title={imgAlt} />
</Card>

How do I get the CardMedia height?

I want to use a ratio of 16/9 for the image. So If Β the width is 480px, I use 480/16 * 9 = 270px.

The Card Content

import CardContent from "@mui/material/CardContent";
import Typography from "@mui/material/Typography";

Add the code:

<CardContent>
  <Typography component="h3" variant="h5" gutterBottom>
    {title}
  </Typography>
  <Typography variant="body2" color="text.secondary">
    {desc}
  </Typography>
</CardContent>
  • Define title and description inside "Card Content".
  • Tell the "Typography" component to use the "h3" tag with the "h5" variant for the title.
  • Then add a gutter bottom below the title to create white space.
  • The body2 variant will use a "p" tag, so no need to specify it.
  • Set the text colour to "text.secondary" is to have lighter black colour text.

The Card Actions

import CardActions from "@mui/material/CardActions";
import Button from "@mui/material/Button";
import MuiNextLink from "./MuiNextLink";

Add the code:

<CardActions>
  <MuiNextLink href={pagePath} underline="none">
    <Button variant="contained" size="large">
      {ctaText}
    </Button>
  </MuiNextLink>
</CardActions>

Define Container on Homepage

Open ./pages/index.js,

import Container from "@mui/material/Container";
import Grid from "@mui/material/Grid";

Add the code:

<Container maxWidth="md" sx={{ my: 15 }}>
  <Grid container spacing={2}>
    <Grid container item justifyContent="center" xs={12} md={6}></Grid>
    <Grid container item justifyContent="center" xs={12} md={6}></Grid>
  </Grid>
</Container>
  • Tell the Container to has a max-width of "md" size.
  • Set 120px white space on margin-top and margin-bottom. (15 x 8px)
  • Define a 16px gap on the Grid component.
  • Define 2 columns in the "md" breakpoint, and 1 column is "xs" and "sm" breakpoint.
  • Use the container attribute in the "Grid" item to centre the card.

Add Navigation Card To Container

import NavigationCard from "@components/NavigationCard";

Add the code to the first container:

<NavigationCard
  imgSrc="/menu.jpg"
  imgAlt="food menu"
  title="Menu"
  desc="Lorem ipsum dolor sit amet, consectetur adipisicing elit. Similique, minus."
  pagePath="./menu"
  ctaText="Check Out"
/>

Add the code to the second container:

<NavigationCard
  imgSrc="/catering.jpg"
  imgAlt="catering"
  title="Catering"
  desc="Lorem ipsum dolor sit amet, consectetur adipisicing elit. Similique, minus."
  pagePath="./catering"
  ctaText="Find Out"
/>

Save the file,

npm run dev

Start the development server,

You shall see:

We successfully build the customer review section with Nextjs & Material-UI v5.

ctrl + c to stop the development server,

Next~

We going to build the last part of our homepage - the footer


We will keep this website as simple as possible with "just enough" information for our visitors. Hence, I'll not add a lot of information in the footer section such as categories, site links, sitemap, contact form and others to the footer section.

We'll add a copyright statement with a started year and social media icon link.

touch components/Footer.jsx
import Box from "@mui/material/Box";

const Footer = () => {
  return <Box component="footer" sx={{ py: 5, bgcolor: "primary.main" }}></Box>;
};

export default Footer;
  • Box component is a wrapper component with the MUI theme and system styling utilities.
  • Since we can't style the native HTML footer tag with sx prop, we have to use the Box component.
  • The py prop refers to vertical padding (padding-top & padding-bottom)

Add To _app.js

We want every page to have the footer, we have to add it to _app.js

Open ./pages/_app.js

import Footer from "@components/Footer";

Add the code,

Save the file,

npm run dev

Start the development server,

You shall see the empty footer section:

Go back to ./components/Footer.jsx

import Typography from '@mui/material/Typography'

Add the code:

<Typography align="center" color="common.white">
  Β© 1994 - {new Date().getFullYear()}, De West Sakura Restaurant
</Typography>

Save the file, check the footer

You shall see the copyright statement.

Add Social Media Icon

Go back to ./components/Footer.jsx

import Stack from "@mui/material/Stack";
import { Facebook, Instagram, Twitter } from "@mui/icons-material";
import MuiNextLink from "@components/MuiNextLink";

Define the social icon container,

Add the code:

<Stack
  direction="row"
  justifyContent="center"
  spacing={4}
  sx={{ mb: 5 }}
></Stack>

Add Facebook Social Icon with Link to the container,

<MuiNextLink
  sx={{ textDecoration: "none", color: "common.white" }}
  href="https://YourFacebookLink/"
  target="_blank"
  rel="noopener noreferrer"
>
  <Facebook fontSize="large" />
</MuiNextLink>
  • You must include the HTTPS so that MuiNextLink will automatically detect it as an external link.

Save the file,

You shall see the Facebook icon appear.

Change the href to https://www.google.com/, save the file and click the Facebook icon.

Add The Remaining Social Icon

<MuiNextLink
  sx={{ textDecoration: "none", color: "common.white" }}
  href="https://YourInstagramLink/"
  target="_blank"
  rel="noopener noreferrer"
>
  <Instagram fontSize="large" />
</MuiNextLink>
<MuiNextLink
  sx={{ textDecoration: "none", color: "common.white" }}
  href="https://YourTwitterLink/"
  target="_blank"
  rel="noopener noreferrer"
>
  <Twitter fontSize="large" />
</MuiNextLink>

Save the file,

You shall see:

We built a simple footer,

We complete the homepage, the about, catering, menu and contact page will leave it to your creativity.

ctrl + c to stop the development server,

Thank you for following the tutorial until the end.

Thank you 3000


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