Airbnb clone, CSS and navigation bar

Join the 2022 Full-Stack Web Dev Bootcamp!


This post is part of a new series where we build a clone of Airbnb with Next.js. See the first post here.

What we did up to now is pretty cool!

In this lesson we’ll add a navigation bar to the page, and we’ll also add some styling to make the app look nicer.

First, I’m going to add a logo image in public/img/logo.png. It reminds the Airbnb logo, just upside down and with a different color:

Feel free to use any image you want.

I’m going to include this image in the header, which will be present in every page.

Let’s create a new file in the components directory, called Layout.js.

In there, we’ll create the shell of our application: we’ll include it in every page component to provide common UI, including basic CSS and the heading.

In this component we take the props, and we render the content props in a main tag:

components/Layout.js

export default function Layout(props) {
  return (
    <div>
      <main>{props.content}</main>
    </div>
  )
}

Then in the pages/index.js and pages/houses/[id].js we import this Layout component, and we return that component, passing the JSX as its content prop:

pages/index.js

import houses from '../houses.js'
import House from '../components/House'
import Layout from '../components/Layout'

const content = (
  <div>
    <h2>Places to stay</h2>

    <div className="houses">
      {houses.map((house, index) => {
        return <House key={index} {...house} />
      })}
    </div>

    <style jsx>{`
      .houses {
        display: grid;
        grid-template-columns: 49% 49%;
        grid-template-rows: 300px 300px;
        grid-gap: 2%;
      }
    `}</style>
  </div>
)

export default function Home() {
  return <Layout content={content} />
}

See? We have wrapped our JSX in a content variable, and we pass that to the Layout component content prop, so we can access it in components/Layout.js.

Now we can do the same for the other page component we have, pages/houses/[id].js. In there, we do things a little differently: instead of creating a content variable, we directly put the component JSX inside the content prop:

pages/houses/[id].js

import Head from 'next/head'
import houses from '../../houses.js'
import Layout from '../../components/Layout'

export default function House(props) {
  return (
    <Layout
      content={
        <div>
          <Head>
            <title>{props.house.title}</title>
          </Head>
          <img src={props.house.picture} width="100%" alt="House picture" />
          <p>
            {props.house.type} - {props.house.town}
          </p>
          <p>{props.house.title}</p>
        </div>
      }
    />
  )
}

export async function getServerSideProps({ query }) {
  const { id } = query

  return {
    props: {
      house: houses.filter((house) => house.id === parseInt(id))[0]
    }
  }
}

Why? Because in this component JSX we access the props values, so we can’t define the JSX outside of the component. I could have also written

pages/houses/[id].js

export default function House(props) {
  const content = (
    <div>
      <Head>
        <title>{props.house.title}</title>
      </Head>
      <img src={props.house.picture} width="100%" alt="House picture" />
      <p>
        {props.house.type} - {props.house.town}
      </p>
      <p>{props.house.title}</p>
    </div>
  )

  return <Layout content={content} />
}

but it’s mostly the same, and I think a little bit more complicated to grasp at a first look.

Cool! So now we have both pages use the Layout component as their base.

We can now go back to it, and we add a little bit of CSS, using styled-jsx:

components/Layout.js

export default function Layout(props) {
  return (
    <div>
      <main>{props.content}</main>

      <style jsx>{`
        main {
          position: relative;
          max-width: 56em;
          background-color: white;
          padding: 2em;
          margin: 0 auto;
          box-sizing: border-box;
        }
      `}</style>
    </div>
  )
}

Now, I’m going to add a header to the page. I’ll do that in a separate component, which I call Header.js, and I place it into the components folder.

Here’s a start, we include the logo and a first nav container for a couple links we’re going to add later:

components/Header.js

export default function Header(props) {
  return (
    <div className="nav-container">
      <img src="/img/logo.png" alt="" />

      <nav></nav>

      <style jsx>{`
        .nav-container {
          border-bottom: 1px solid #eee;
          height: 50px;
        }

        img {
          float: left;
        }
      `}</style>
    </div>
  )
}

Now let’s add those links I was talking about. I add two links, one is Sign up and the other is Log in. We’ll use them soon. I also make the logo link to the home page. I also add some CSS to style them nicely:

components/Header.js

import Link from 'next/link'

export default function Header(props) {
  return (
    <div className="nav-container">
      <Link href="/">
        <a>
          <img src="/img/logo.png" alt="" />
        </a>
      </Link>

      <nav>
        <ul>
          <li>
            <Link href="/register">
              <a>Sign up</a>
            </Link>
          </li>
          <li>
            <Link href="/login">
              <a>Log in</a>
            </Link>
          </li>
        </ul>
      </nav>

      <style jsx>{`
        ul {
          margin: 0;
          padding: 0;
        }

        li {
          display: block;
          float: left;
        }

        a {
          text-decoration: none;
          display: block;
          margin-right: 15px;
          color: #333;
        }

        nav a {
          padding: 1em 0.5em;
        }

        .nav-container {
          border-bottom: 1px solid #eee;
          height: 50px;
        }

        img {
          float: left;
        }

        ul {
          float: right;
        }
      `}</style>
    </div>
  )
}

Now in Layout.js, import and include the Header component:

import Header from './Header'

export default function Layout(props) {
  return (
    <div>
      <Header />
      <main>{props.content}</main>
      <style jsx>{`
        main {
          position: relative;
          max-width: 56em;
          background-color: white;
          padding: 2em;
          margin: 0 auto;
          box-sizing: border-box;
        }
      `}</style>
    </div>
  )
}

Awesome! This should be the end result so far:

See the code on GitHub

Next part: Start with the date picker

Want to become a better Web Developer? Join the 2022 Web Development Bootcamp!

⭐️⭐️⭐️ Join the 2022 Web Development Bootcamp ⭐️⭐️⭐️