In this tutorial, I will show you how to build a fully mobile responsive restaurant website template using Next.js and Cosmic Headless CMS. The Figma template can be found on ui8.net.
Bonus features include deploying to Vercel and setting up automatic static content revalidation using Cosmic Webhooks. Let’s get started.
Tools we’ll be using
Next.js - A React framework for production that makes it easy to spin up a full-stack application.
Cosmic - A Headless CMS enables the independence of the data (content) layer and gives us the ability to quickly manage website content.
Sass - A stable, and powerful professional-grade CSS extension language.
TL;DR
Check out the code
View the live demo
Install the App Template
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, 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 app that includes tooling and configurations. For this tutorial, you'll need Node.js 12.22.0 or a later version. Open the terminal, paste or type
npx create-next-app@latest nextjs-restaurant-website-cms
# or
yarn create next-app nextjs-restaurant-website-cms
Install dependencies from the app folder:
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:
npm run dev
# or
yarn dev
Open 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 is built on top of — SWC — 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.
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, and more.
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.
// 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
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.
// 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 has been updated to support the same pattern as ISR 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:
// 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 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. Let's select the "Start from scratch" option.
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.
Create Section Object Type and add section properties to define the "Metafields" in the "Content Model".
Now you could create an Object Type model for sections and you can fill content like this.
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.
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.
Awesome! Add this Bucket slug and API read key into your Next.js app .env
//.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. Open the terminal, paste or type this code to install all dependencies, and run it.
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.
// 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
.
// 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.
// 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
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
:
// 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 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.
Time to test it out, edit your content in Cosmic Dashboard, and see the static content updated instantly!
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.
Top comments (0)