Frontend Serverless with React and GraphQL

Start at the beginning and work your way through this project. The code for each step as well as the finished project can be found in the Github repository.


9. Add MainNavbar

Objective: Create a navigation bar for the top of the webapp.

Now that we have a footer, let's make our navbar which will be the primary way that users can switch between different pages. Our navbar will have to sections that we divide with a flexbox, the left side will have a logo, title, and description while the right side will have a set of menu buttons that will take us to different pages.

We import our <Header> component from the <Layout> component just like we did with the <Footer>. We created a styled version of our header that we call <StyledHeader>. We use flexbox so that the title and menu sections of our navbar will dynamically stretch with the browser size and we also set a bottom border to create a nice visual divide between the navbar and the content section of our page.

Our <TitleContainer> component contains the logo image, title and description. We use @media queries so that when the size gets too small, it will disappear to leave room for the menu bar to take up the entire space.

The <StyledMenu> component has a single menu item that goes to the home page when clicked. We can add most of our styles there, but due to some issues I had with getting the menu line-height property to work in the styled component, I was forced to add it as a style prop to the component itself.

For any links I use which are internal to the site, I use the next/link package instead of using an <a> tag. This ensures that we can get nice client-side navigation without any hard refreshes or page flashes.

components/layout/MainNavbar.tsx

import styled from 'styled-components';
import { Layout, Menu } from 'antd';
import Link from 'next/link';
const { Header } = Layout;
const TitleContainer = styled.div`
${({ theme }) => `
background-color: ${theme['header-color']};
width: 50%;
display: flex;
align-items: center;
@media (max-width: 890px){
visibility: hidden;
width: 0;
}
`}
`;
const Title = styled.div`
${({ theme }) => `
text-align: left;
display: flex;
line-height: 50px;
div {
width: 100%;
padding-left: ${theme['padding-small']};
}
h2 {
display: inline;
color: inherit;
}
img {
width: 64px;
}
p {
line-height: 0;
}
`}
`;
const StyledHeader = styled(Header)`
${({ theme }) => `
background-color: ${theme['header-color']};
border-bottom-color: ${theme['header-border-color']};
border-bottom-right: 1px;
border-bottom-style: solid;
text-align: right;
display: flex;
li {
font-size: ${theme['font-size-md']};
}
`}
`;
const StyledMenu = styled(Menu)`
border-bottom-width: 0px;
width: 50%;
@media (max-width: 890px) {
width: 100%;
}
`;
export const MainNavbar = () => (
<StyledHeader>
<TitleContainer>
<Title>
<img src="/logo.svg" alt="Next Chop Logo" />
<div>
<h2>The Next Chop</h2>
<p>A recipe discovery app powered by Next.js</p>
</div>
</Title>
</TitleContainer>
<StyledMenu theme="light" mode="horizontal" style={{ lineHeight: '64px' }}>
<Menu.Item key="/">
<Link href="/">
<a>Home</a>
</Link>
</Menu.Item>
</StyledMenu>
</StyledHeader>
);

Now that we've created the MainNavbar we need to import it in the MainLayout like so:

components/layout/MainLayout.tsx

import { ThemeProvider } from 'styled-components';
import { Component, ReactNode } from 'react';
import { theme } from '../../utils/theme';
import { GlobalStyle } from '../../utils/globalStyle';
import Head from 'next/head';
import { Layout } from 'antd';
import { MainFooter } from './MainFooter';
import { MainNavbar } from './MainNavbar';
const { Content } = Layout;
const MainHead = ({ title }: { title: string }) => (
<Head>
<title>{title}</title>
<meta charSet="utf-8" />
<meta name="viewport" content="initial-scale=1.0, width=device-width" />
<meta
name="description"
content="A recipe discovery app powered by Next.js."
/>
<meta name="author" content="codemochi" />
<meta name="keyword" content="next, react, typescript, js" />
<meta property="og:type" content="website" />
<meta property="og:title" content={title} />
<meta property="og:url" content="https://next-chop.codemochi.com" />
<meta property="og:image" content="/logo.svg" />
<meta
property="og:description"
content="A recipe discovery app powered by Next.js."
/>
<link
rel="apple-touch-icon"
sizes="180x180"
href="/favicon/apple-touch-icon.png"
/>
<link
rel="icon"
sizes="32x32"
type="image/png"
href="/favicon/favicon-32x32.png"
/>
<link
rel="icon"
sizes="16x16"
type="image/png"
href="/favicon/favicon-16x16.png"
/>
<link rel="manifest" href="/favicon/site.webmanifest" />
</Head>
);
type Props = {
children: ReactNode;
title?: string;
};
export class MainLayout extends Component<Props> {
render() {
const { title, children } = this.props;
return (
<ThemeProvider theme={theme}>
<MainHead title={title} />
<GlobalStyle />
<Layout>
<MainNavbar />
<Content>{children}</Content>
<MainFooter />
</Layout>
</ThemeProvider>
);
}
}