Build Restaurant Website with Next.js and Cosmic
Tags: webdev, javascript, react, nextjs
Published: 2022-04-27 18:04:15

In this tutorial, I will show you how to build a fully mobile responsive restaurant website using [Next.js](https://nextjs.org/) and [Cosmic](https://www.cosmicjs.com/) Headless CMS. The Figma template can be found on [ui8.net](https://ui8.net/iqonicdesign/products/gericht-restaurant-website-ui-in-figma).

Bonus features include deploying to [Vercel](https://vercel.com/) and setting up automatic static content revalidation using Cosmic Webhooks. Let’s get started.

## Tools we’ll be using

[Next.js](https://nextjs.org/) - A React framework for production that makes it easy to spin up a full-stack application.
[Cosmic](https://www.cosmicjs.com) - A Headless CMS enables the independence of the data (content) layer and gives us the ability to quickly manage website content.
[Sass](https://sass-lang.com) -  A stable, and powerful professional-grade CSS extension language.

## TL;DR
[Check out the code](https://github.com/cosmicjs/nextjs-restaurant-website-cms)
[View the live demo](https://nextjs-restaurant-website-cms.vercel.app/)
[Install the App Template](https://www.cosmicjs.com/apps/nextjs-restaurant-website)

![https://imgix.cosmicjs.com/b94c8f60-c0ec-11ec-bf80-e74645a81647-template.gif](https://imgix.cosmicjs.com/b94c8f60-c0ec-11ec-bf80-e74645a81647-template.gif)

## Quick Introduction

**Next.js** is a complete suite to build blazing fast React apps. It is developer-friendly and intuitive to use. With the release of [Next.js 12.1](https://nextjs.org/blog/next-12-1?utm_source=next-site&utm_medium=banner&utm_campaign=next-website), things are only going to get better with new features including performance optimization, middleware, React 18 support, on-demand ISR, expanded support for SWC, and more.
**Cosmic** is a great headless CMS that gives us the ability to fully manage and store our website content and media and update them quickly.

### Explore 4 new killer features of Next.js and use them to template

Let's [install a new Next.js](https://nextjs.org/docs) app that includes tooling and configurations. For this tutorial, you'll need [Node.js](https://nodejs.org/en/) 12.22.0 or a later version. Open the terminal, paste or type

```js
npx create-next-app@latest nextjs-restaurant-website-cms
# or
yarn create next-app nextjs-restaurant-website-cms
```

Install dependencies from the app folder:

```js
cd nextjs-restaurant-website-cms
npm i
# or
cd nextjs-restaurant-website-cms 
yarn
```

You can now start the app in the development mode with:

```js
npm run dev
# or
yarn dev
```

Open *[http://localhost:3000/](http://localhost:3000/)* in your browser to see the ascetic home page.

### 1. Rust compiler

One of the key features of Next.js 12 is performance optimization. To boost performance, Next.js replaced the Babel compiler with an extensible Rust compiler and enabled **it by default using** Next.js 12,  the [compiler](https://nextjs.org/docs/advanced-features/compiler) is built on top of — [SWC](http://swc.rs/) — which stands up for `Speedy Web Compiler`. It does consume TypeScript/JavaScript and emits JavaScript code that can be executed on old browsers.

*SWC is 20x faster than Babel on a single thread and 70x faster on four cores.*

![https://imgix.cosmicjs.com/92a157c0-be93-11ec-bf80-e74645a81647-Rust.png](https://imgix.cosmicjs.com/92a157c0-be93-11ec-bf80-e74645a81647-Rust.png)

### 2. Middleware

This is one of the most exciting features. Middlewares enable us to use code over configuration. This means you can run code before a request is completed, and based on the request, you can modify the response by rewriting, redirecting, adding headers, setting cookies, etc. With middlewares, you can implement things like [authentication, bot protection, redirects and rewrites, server-side analytics, logging, and handling unsupported browsers](https://github.com/vercel/examples/tree/main/edge-functions), and more.

![https://imgix.cosmicjs.com/d926ee80-be93-11ec-bf80-e74645a81647-Middleware.png](https://imgix.cosmicjs.com/d926ee80-be93-11ec-bf80-e74645a81647-Middleware.png)

Middleware is created in `/pages/_middleware.ts`  and it will run on all routes within the `/pages` directory. What does a `_middleware.js` file look like? Let’s see that through our template for example.

```js
// pages/_middleware.js

import { NextResponse } from 'next/server';

export async function middleware( request ) {
  // create an instance of the class to access the public methods.
  //This uses next(),

  let response = NextResponse.next();

  const country = request.geo.country || 'US';
  const city = request.geo.city || 'San Francisco';
  const region = request.geo.region || 'CA';

  // get the cookies from the request
  let cookieFromRequest = request.cookies['location-cookie'];

  if(!cookieFromRequest) {
    // set the `cookie`
    response.cookie('location-cookie', `${country|city|region}`);
  }

  return response;
}
```

### 3. On-demand Incremental Static Regeneration [ISR](https://nextjs.org/docs/basic-features/data-fetching/incremental-static-regeneration)

Next.js now exposes a function `unstable_revalidate()` allowing you to revalidate individual pages that use `getStaticProps`. Inside `getStaticProps`, you don’t need to specify revalidate to use on-demand revalidation, only revalidate the page on-demand when `unstable_revalidate()` is called.

```js
// pages/api/revalidate.js

export default async function handler(req, res) {
  try {
    await res.unstable_revalidate('/menu/' + req.body.data.slug)
    return res.json({ revalidated: true })
  } catch (err) {
    // If there was an error, Next.js will continue
    // to show the last successfully generated page
    return res.status(500).send('Error revalidating')
  }
}
```

### 4. Faster Image Optimization and Smaller images using AVIF

The built-in [Image Optimization API](https://nextjs.org/blog/next-12-1?utm_source=next-site&utm_medium=banner&utm_campaign=next-website#faster-image-optimization) has been updated to support the same pattern as [ISR](https://nextjs.org/docs/basic-features/data-fetching/incremental-static-regeneration) pages, where images are served stale and revalidated in the background. It also supports AVIF images enabling 20 percent smaller images compared to WebP.

This feature is opt-in and can be enabled by modifying the image. format property in the `next.config.js` file:

```js
// next.config.js

const nextConfig = {
  reactStrictMode: true,
  images: {
        formats: ['image/avif', 'image/webp'],
        domains: ['imgix.cosmicjs.com'],
    },
}

module.exports = nextConfig
```

## Cosmic Features overview

- **Customizable API**: Build out the schema, models, and controllers for API from the editor. Cosmic offers both a **REST and GraphQL** API for our convenience.
- **Globally fast and secure** content management system and API toolkit.
- **Webhooks** Call back anywhere you need, to get the functionality you want, out of the box with Cosmic API.
- **[Imgix](https://imgix.com/) Integration** is included which allows you to do powerful image processing for dynamic apps optimized for cross-platform experiences.

### Cosmic integration

The first step making a [free Cosmic account](https://app.cosmicjs.com/signup). Let's select the "Start from scratch" option.

![https://imgix.cosmicjs.com/cb3c32c0-be94-11ec-bf80-e74645a81647-cosmic1.png](https://imgix.cosmicjs.com/cb3c32c0-be94-11ec-bf80-e74645a81647-cosmic1.png)

Great! Now let's organize our content into groups, that share the same content model using Object Types. For example, you have sections with similar properties like section name, title, introduction, and picture and want to reuse this module to create content for different sections.

![https://i.gyazo.com/ccef552e9b9a0161606c076163808316.png](https://i.gyazo.com/ccef552e9b9a0161606c076163808316.png)

Create Section Object Type and add section properties to define the "Metafields" in the "Content Model".

![https://imgix.cosmicjs.com/72804b50-c0ef-11ec-bf80-e74645a81647-cosmic3.gif](https://imgix.cosmicjs.com/72804b50-c0ef-11ec-bf80-e74645a81647-cosmic3.gif)

Now you could create an Object Type model for sections and you can fill content like this.

![https://imgix.cosmicjs.com/c91c93a0-c0f0-11ec-bf80-e74645a81647-cosmic5.gif](https://imgix.cosmicjs.com/c91c93a0-c0f0-11ec-bf80-e74645a81647-cosmic5.gif)

In a similar way, you can define modules and creates Object Type following the current data model, schema design

- Singleton for a unique model,
- Multiple reusable models.

### Time to get values for the Next.js app

Install the Cosmic module to your Next.js app.

```js
npm i cosmicjs
# or
yarn add cosmicjs
```

Then go to Cosmic Dashboard `Your Bucket > Settings > API Access` and find your Bucket slug and API read key.

![https://imgix.cosmicjs.com/dabe7910-c0f1-11ec-bf80-e74645a81647-cosmic6.gif](https://imgix.cosmicjs.com/dabe7910-c0f1-11ec-bf80-e74645a81647-cosmic6.gif)

Awesome! Add this Bucket slug and API read key into your Next.js app `.env`

```js
//.env
COSMIC_BUCKET_SLUG=your_cosmic_slug
COSMIC_READ_KEY=your_cosmic_read_key
```

To use the template UI you need to clone it in [GitHub](https://github.com/cosmicjs/nextjs-restaurant-website-cms). Open the terminal, paste or type this code to install all dependencies, and run it.

```js
git clone https://github.com/cosmicjs/nextjs-restaurant-website-cms.git
cd nextjs-restaurant-website-cms

npm install
#or
yarn install

npm run dev
#or
yarn dev
```

Function `getDataFromBucket` request to the `bucket` that we create earlier in Cosmic Dashboard and get our created content from the Cosmic by the `params` type.

```js
// src/lib/api.js

import Cosmic from 'cosmicjs';

const BUCKET_SLUG = process.env.COSMIC_BUCKET_SLUG
const READ_KEY = process.env.COSMIC_READ_KEY

const bucket = Cosmic().bucket({
  slug: BUCKET_SLUG,
  read_key: READ_KEY,
});

export async function getDataFromBucket(preview) {
  const params = {
    type: 'header',
    props: 'title,slug,metadata,created_at',
    sort: '-created_at',
    ...(preview && { status: 'all' }),
  }
  const data = await bucket.getObjects(params)
  return data.objects
}
```

Let's display our content, integrate it with our UI, and render some elements to the home page. For that, you need to add this to `index.js`.

```js
// pages/index.js

import Head from 'next/head';
import Home from 'components/Home';
import Layout from 'components/Layout';
import Footer from 'components/Footer';
import AboutUs from 'components/AboutUs';
import SpacialMenu from 'components/Menu';
import Introduction from 'components/Introduction';
import VideoIntro from 'components/VideoIntro';
import Gallery from 'components/Gallery';
import Contacts from 'components/Contact';

import { getDataFromBucket } from 'lib/api';
import chooseByType from 'utils/chooseValueByType';

function Template({ data }) {
  return (
    <>
      <Head>
        <title>Next.js Restaurant CMS</title>
        <meta name="description" content="Create template using cosmic.js CMS" />
        <link rel="icon" href="/favicon.ico" />
      </Head>
      <Layout navbar={chooseByType(data, 'navigation')}>
        <Home info={chooseByType(data, 'header')}/>
        <AboutUs info={chooseByType(data, 'about')}/>
        <SpacialMenu info={[chooseByType(data, 'drink'), chooseByType(data, 'food')]}/>
        <Introduction info={chooseByType(data, 'history')}/>
        <Gallery info={[chooseByType(data, 'gallery'), chooseByType(data, 'food')]}/>
      </Layout>
      <Footer>
        <VideoIntro url={chooseByType(data, 'video')}/>
        <Contacts info={chooseByType(data, 'contact')}/>
      </Footer>
    </>
  )
}

export async function getStaticProps({ preview }) {
  const data = (await getDataFromBucket(preview)) || [];
  return {
    props: { data },
  }
}

export default Template;
```

The `chooseByType` function filters data by Object Type `slug`, which we created in the Cosmic Dashboard.

```js
// src/utils/chooseValueByType.js

const chooseByType = (data, slugName) => {
  if( data && slugName ) {
    const chooseBySlug = data?.filter(content => Object.values(content).includes(slugName));
    return chooseBySlug ? chooseBySlug[0] : [];
  }
}

export default chooseByType;
```

Congrats you're almost there!

### Making Menu Item Page

![https://imgix.cosmicjs.com/cd50f1b0-c0f4-11ec-bf80-e74645a81647-cosmic7.gif](https://imgix.cosmicjs.com/cd50f1b0-c0f4-11ec-bf80-e74645a81647-cosmic7.gif)

In Next.js you can create a dynamic route. For creating individual menu item page and dynamic route, consider the following page `pages/menu/[slug].js`:

```js
// pages/menu/[slug].js

import Head from 'next/head';
import { useRouter } from 'next/router';

import Layout from 'components/Layout';
import Footer from 'components/Footer';
import Contacts from 'components/Contact';
import MenuIntro from 'components/MenuIntro';
import VideoIntro from 'components/VideoIntro';
import Gallery from 'components/Gallery';

import { getAllDataWithSlug,getDataFromBucket } from 'lib/api';
import chooseByType from 'utils/chooseValueByType';

function Menu({ data }) {
  const {
    query: {slug},
  } = useRouter();

  return (
    <>
      <Head>
        <title>Next.js Restaurant CMS</title>
        <meta name="description" content="Create template using cosmic.js CMS" />
        <link rel="icon" href="/favicon.ico" />
      </Head>
      <Layout navbar={chooseByType(data, 'navigation')}>
        <MenuIntro info={[chooseByType(data, 'food'), chooseByType(data, 'drink')]} slug={slug} />
        <Gallery info={[chooseByType(data, 'gallery'), chooseByType(data, 'food')]}/>
      </Layout>
      <Footer>
        <VideoIntro url={chooseByType(data, 'sushi')}/>
        <Contacts info={chooseByType(data, 'contact')}/>
      </Footer>
    </>
  )
}

export async function getStaticProps({ params, preview = null }) {
  const data = (await getDataFromBucket(preview)) || [];
  return {
    props: { data },
  }
}

export async function getStaticPaths() {
  const dataWithSlug = (await getAllDataWithSlug()) || [];

  return {
    paths: dataWithSlug.map((menu) => `/menu/${menu.slug}`),
    fallback: true,
  }
}

export default Menu;
```

`getServerSideProps` function is used to get the data from Cosmic each time this route is called. In `pages/api/revalidate.js` we use `unstable_revalidate` function to revalidate the page on-demand when `unstable_revalidate()` is called and if there was an error, Next.js will continue to show the last successfully generated page. 

After deploying your codebase on [Vercel](https://nextjs.org/docs/deployment) you can enable the revalidation of content updates by going to the Cosmic Dashboard and navigating to *Bucket Settings > Webhooks*. The event to be triggered when editing content is `object.edited.published` . The Webhook URL endpoint will look like `${YOUR_VERCEL_DEPLOYMENT_URL}/api/revalidate`.

This also makes it easier to update your site when content from your headless CMS is created or updated.

![https://imgix.cosmicjs.com/422a0160-c1a0-11ec-bf80-e74645a81647-revalidate.gif](https://imgix.cosmicjs.com/422a0160-c1a0-11ec-bf80-e74645a81647-revalidate.gif)

Time to test it out, edit your content in Cosmic Dashboard, and see the static content updated instantly!

![https://imgix.cosmicjs.com/6ca59130-c1a4-11ec-bf80-e74645a81647-revalidate.gif](https://imgix.cosmicjs.com/6ca59130-c1a4-11ec-bf80-e74645a81647-revalidate.gif)

## Conclusion

Congrats! Now you have a dynamic, customizable, and fully integrated template with new Next.js and Cosmic features, you can customize it for other types of businesses and use it how you prefer.

![https://imgix.cosmicjs.com/b94c8f60-c0ec-11ec-bf80-e74645a81647-template.gif](https://imgix.cosmicjs.com/b94c8f60-c0ec-11ec-bf80-e74645a81647-template.gif)