Building a modern landing page with Next.js and One Entry Headless CMS👑🌟
Tags: webdev, javascript, react, programming
Published: 2023-12-19 09:03:01


In this tutorial, you'll learn how to create a modern and fully responsive landing page using Next.js and One Entry Headless CMS. Upon completion, you'll be able to set up and retrieve digital content from One Entry CMS.

## What is One Entry CMS?

One Entry CMS is a headless content management system that stores and manages content for software applications. It separates your content from the application's logic, enabling you to reuse it across various web and mobile applications.

[One Entry CMS](https://oneentry.cloud/) is scalable and flexible. It allows you to create various types of web content,  such as forms, text, lists, strings and numbers, image uploads, and many others.

![https://media.giphy.com/media/1rSNDobiRYgxmxnVp6/giphy.gif](https://media.giphy.com/media/1rSNDobiRYgxmxnVp6/giphy.gif)

## Why you should use One Entry Headless CMS?

**Easy to use**

One Entry Headless CMS is a simple and user-friendly content management system that allows you to set up an account, create a project, and add content blocks easily.

In addition to its documentation, it provides a [series of tutorials](https://www.youtube.com/@headlesscms-oneentry) on how to carry out various tasks using the CMS. With One Entry, you can create pages, forms, and page attributes, such as images and text content, and start using them instantly.

**Fully secured for mobile apps and websites**

One Entry provides a safe way of retrieving contents from the server. It creates encrypted certificates or authentication tokens and ensures that only authenticated users can access the stored data. With One Entry, your content is safe from attackers and unauthorized users.

**Quick and efficient web servers**

Content retrieval and interaction with One Entry is extremely fast. Its web storage is built using reliable and modern tools to ensure 99.99% uptime, data reliability, and easy retrievals. With One Entry, your software application is always ready for use, and the support team is readily available to help you overcome any challenges.

**Multilingual Support**

One Entry CMS is available in multiple languages. You can interact with the application using your preferred language. With One Entry, language is not a barrier to your development, and you can retrieve content in any language of your choice.

**Scalable and Flexible**

One Entry Headless CMS has a free plan and a few affordable paid plans depending on your application needs. It incorporates the tools used by developers and content managers to enable you to build amazing products seamlessly. You can interact with the contents via REST APIs or the One Entry SDK library.

***
## How to set up One Entry CMS in a Next.js application

To set up One Entry in Next.js, visit the [website](https://oneentry.cloud/) and create an account. You'll be authenticated using your phone number.

Create a new project using the promo code provided to you. It gives you a 30-day free trial. 


![Headless CMS One Entry project](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/nfsym8pir74jgbqkzu8k.png)

Your project will be assigned to a subdomain. Then, navigate to the Access tab under Projects and deactivate your mLTS certificate to enable you to use the token for authenticating requests.

![Authentication token for Headless CMS](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/1bqdez74yg0evh3nrle7.png)

After creating the project, you'll receive an email notification containing the credentials needed to access the project. Click the project URL and sign in to access the newly created project.

![Credentials for content management system](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/g5qs8a8w2yvxu3pdrfdi.png)

Finally, create a Next.js project using the code snippet below and install the [One Entry SDK](https://www.npmjs.com/package/oneentry) package as its dependency.

```bash
npx create-next-app
npm install oneentry
```
*** 
## Building the landing page

Here, I'll guide you through building the landing page using Tailwind CSS, TypeScript, and Next.js page router.

Install the [React Icons](https://react-icons.github.io/react-icons/) and [React Scroll](https://www.npmjs.com/package/react-scroll) packages. React Icons enables us to use various icons within our application, and the React Scroll package is used for navigating smoothly between sections of a page.

```bash
npm install react-icons react-scroll
```

The landing page is divided into five components - the Header, Main, Products, Contact Form, and Footer. Create a `components` folder containing the page sections.

```bash
mkdir components
cd components
touch Header.tsx Main.tsx Products.tsx ContactForm.tsx Footer.tsx
```

![Landing page demo for the Headless CMS](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/1szz1070i1odh1ciz39t.gif)

### The Header Component

Copy the code snippet below into the `Header.tsx` file. The code snippet uses [React Scroll](https://www.npmjs.com/package/react-scroll) to navigate users between each section of the page.

```tsx
import React, { useEffect } from "react";
import Image from "next/image";
import header from "../images/header.jpg";
import { Link as ScrollLink } from "react-scroll";

const Header = () => {
	return (
		<header className='w-full md:h-screen min-h-screen bg-[#EEF0E5]  flex items-center justify-between'>
			<div className='md:w-[65%] w-full  h-full bg-[#EEF0E5] md:p-8 p-4'>
				<nav className='h-[10vh] flex items-center justify-between mb-8'>
					<h2 className='text-2xl font-bold'>OneShop</h2>
					<div className='flex items-center space-x-5'>
						<ScrollLink
							to='about'
							className='hover:text-black hover:font-semibold text-[#304D30] cursor-pointer'
							activeClass='active'
							spy={true}
							smooth={true}
							offset={-70}
							duration={500}
						>
							About
						</ScrollLink>
						<ScrollLink
							to='shop'
							className='hover:text-black hover:font-semibold text-[#304D30] cursor-pointer'
							activeClass='active'
							spy={true}
							smooth={true}
							offset={-70}
							duration={500}
						>
							Shop
						</ScrollLink>
						<ScrollLink
							to='contact'
							className='hover:text-black hover:font-semibold text-[#304D30] cursor-pointer'
							activeClass='active'
							spy={true}
							smooth={true}
							offset={-70}
							duration={500}
						>
							Contact Us
						</ScrollLink>
					</div>
				</nav>

				<section className='h-[90vh] flex flex-col justify-center px-2'>
					<h1 className='text-5xl font-extrabold mb-4'>
						Unleash Your Style with Premium Plain Tees
					</h1>
					<p className='mb-2 opacity-50'>
						Discover a world where simplicity meets sophistication. Our curated
						collection of basic tees transcends the ordinary, offering you a
						canvas to express your unique identity.
					</p>
					<p className='mb-2 opacity-50'>
						Whether you prefer a classic crew neck or a stylish V-neck, our
						versatile range is designed to seamlessly integrate into your
						wardrobe, providing the perfect foundation for any outfit.
					</p>
					<button className='bg-[#B6C4B6] hover:bg-[#163020] text-[#163020] cursor-pointer hover:text-white px-4 w-[200px] py-4 rounded-md my-4 block'>
						Shop Now
					</button>
				</section>
			</div>
			<div className='h-full max-w-[35%] md:flex hidden'>
				<Image src={header} alt='OnePlus Clothings' className='h-full w-auto' />
			</div>
		</header>
	);
};

export default Header;
```


![Header component](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/i6r05f0q6a78fncgvj2d.png)

### The Main Component

Update the `Main.tsx` file by copying the code snippet below into the file.

```tsx
import React from "react";
import { FaGaugeHigh, FaMoneyCheck } from "react-icons/fa6";
import { GiStrong, GiCommercialAirplane } from "react-icons/gi";

const Main = () => {
	return (
		<main
			className='w-full md:px-8 px-4 py-10 flex items-center justify-center flex-col'
			id='about'
		>
			<h1 className='text-[#304D30] text-center text-4xl font-bold mb-14'>
				Why OneShop?
			</h1>
			<div className='flex items-center sm:flex-row flex-col sm:space-x-4 sm:mb-6  justify-center sm:w-[80%] w-full'>
				<div className='sm:w-1/2 w-full mb-4 sm:mb-0 bg-[#EEF0E5] p-6 rounded-lg'>
					<FaGaugeHigh className='text-3xl mb-4 text-[#304D30]' />
					<h2 className='text-[#304D30] text-xl font-bold'>Premium Quality</h2>
					<p className='text-[#304D30]'>
						We use only the finest cotton to create our tees, ensuring they are
						soft, breathable and durable.
					</p>
				</div>
				<div className='sm:w-1/2 w-full mb-4 sm:mb-0 bg-[#EEF0E5] p-6 rounded-lg'>
					<GiStrong className='text-3xl mb-4 text-[#304D30]' />
					<h2 className='text-[#304D30] text-xl font-bold'>Sustainable</h2>
					<p className='text-[#304D30]'>
						We are committed to using sustainable materials and processes to
						reduce our environmental impact.
					</p>
				</div>
			</div>

			<div className='flex items-center sm:flex-row flex-col sm:space-x-4 mb-6 justify-center sm:w-[80%] w-full'>
				<div className='sm:w-1/2 w-full mb-4 sm:mb-0 bg-[#EEF0E5] p-6 rounded-lg'>
					<FaMoneyCheck className='text-3xl mb-4 text-[#304D30]' />
					<h2 className='text-[#304D30] text-xl font-bold'>Affordable</h2>
					<p className='text-[#304D30]'>
						We believe that everyone should have access to premium quality
						clothing at affordable prices.
					</p>
				</div>
				<div className='sm:w-1/2 w-full mb-4 sm:mb-0 bg-[#EEF0E5] p-6 rounded-lg'>
					<GiCommercialAirplane className='text-3xl mb-4 text-[#304D30]' />
					<h2 className='text-[#304D30] text-xl font-bold'>Free Shipping</h2>
					<p className='text-[#304D30]'>
						We offer free shipping for orders over $200, so you can shop from
						anywhere in the world.
					</p>
				</div>
			</div>
		</main>
	);
};

export default Main;
```


![Landing Page Main Component](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/tlifmxuai54s8l9tc1zk.png)

### The Products component

Copy the code snippet below into the `Products.tsx` file. It displays the available products.

```tsx
import React from "react";
import Image from "next/image";
import producta from "../images/producta.jpg";
import productb from "../images/productb.jpg";
import productc from "../images/productc.jpg";

const Products = () => {
	const products = [
		{
			id: 1,
			image: producta,
			name: "Coloured T-shirt",
			description:
				"Vibrant collection of colored t-shirts brings a burst of personality to your everyday look",
			price: "$50",
		},
		{
			id: 2,
			image: productb,
			name: "White T-shirt",
			description:
				"Timeless plain white t-shirts, a wardrobe essential for unparalleled versatility and style.",
			price: "$50",
		},
		{
			id: 3,
			image: productc,
			name: "Cotton Hoodies",
			description:
				"Wrap yourself in comfort and style with our cozy hoodies, perfect for staying warm.",
			price: "$50",
		},
	];

	return (
		<section
			className='w-full md:px-8 px-4 py-10 flex items-center justify-center flex-col bg-[#EEF0E5]'
			id='shop'
		>
			<h1 className='text-[#304D30] text-center text-4xl font-bold mb-14'>
				Latest Arrivals
			</h1>

			<div className='flex items-center sm:flex-row flex-col sm:space-x-4 sm:mb-6  justify-center sm:w-[90%] w-full'>
				{products.map((product) => (
					<div
						className='sm:w-1/3 w-[80%] mb-4 sm:mb-0 bg-white p-6 rounded-lg'
						key={product.id}
					>
						<Image
							src={product.image}
							alt={product.name}
							className='h-[250px] object-cover mb-4'
						/>
						<section className='w-full flex flex-col items-center justify-center'>
							<h3 className='text-lg text-[#163020] font-semibold'>
								{product.name}
							</h3>
							<p className='text-sm opacity-60 text-center mb-5'>
								{product.description}
							</p>
							<div className='flex items-center space-x-4'>
								<p className='text-lg font-bold text-[#304D30]'>
									{product.price}
								</p>
								<button className='bg-[#304D30] text-white p-3 cursor-pointer rounded-md'>
									Buy Now
								</button>
							</div>
						</section>
					</div>
				))}
			</div>
		</section>
	);
};

export default Products;
```


![Landing Page - Products Component](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/a0h84r73ffrosp04wlpd.png)

### The Contact Form component

This section displays the contact form and enables users to provide their details and messages.

Copy the code snippet below into the `ContactForm.tsx` file:

```tsx
import React, { useState } from "react";

const ContactForm = () => {
	const [name, setName] = useState("");
	const [email, setEmail] = useState("");
	const [message, setMessage] = useState("");

	const handleSubmit = (e: React.FormEvent<HTMLFormElement>) => {
		e.preventDefault();
		console.log({ name, email, message });
	};

	return (
		<div
			className='w-full md:px-8 px-4 py-10 flex items-center justify-center flex-col'
			id='contact'
		>
			<h1 className='text-[#304D30] text-center text-4xl font-bold'>
				Contact Us
			</h1>
			<p className='mb-8 opacity-50 text-center'>
				Always available to respond to your queries and provide details about
				your orders
			</p>

			<form className='flex flex-col sm:w-[70%] w-full' onSubmit={handleSubmit}>
				<label htmlFor='name'>Full Name</label>
				<input
					name='name'
					type='text'
					id='name'
					className='border-[1px] border-[#304D30] px-4 py-2 w-full rounded-sm mb-6'
					required
					value={name}
					onChange={(e) => setName(e.target.value)}
				/>
				<label htmlFor='email'>Email Address</label>
				<input
					name='email'
					type='email'
					id='email'
					className='border-[1px] border-[#304D30] px-4 py-2 w-full rounded-sm mb-6'
					required
					value={email}
					onChange={(e) => setEmail(e.target.value)}
				/>
				<label htmlFor='message'>Message</label>
				<textarea
					name='message'
					id='message'
					className='border-[1px] border-[#304D30] px-4 py-2 w-full rounded-sm mb-6'
					rows={7}
					required
					value={message}
					onChange={(e) => setMessage(e.target.value)}
				/>
				<button
					className='bg-[#304D30] text-white p-4 rounded-sm font-bold'
					type='submit'
				>
					Send
				</button>
			</form>
		</div>
	);
};

export default ContactForm;
```

![Contact Form](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/b9pbvkmbr5imc5wjj4n4.png)

### The Footer component

Update the `Footer.tsx` file by copying the code snippet below into the file.

```tsx
import React from "react";

const Footer = () => {
	return (
		<footer className='w-full flex items-center justify-center bg-[#304D30] h-[15vh]'>
			<p className='text-[#B6C4B6] text-center'>
				Copyright &copy; {new Date().getFullYear()} OneShop
			</p>
		</footer>
	);
};

export default Footer;
```
***
## How to save contents to One Entry CMS

Within your project dashboard, click `Content Management`. It navigates you to where you can create a page and add various content to the page.

![how to save contents to One Entry Headless CMS](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/y1f6bld1w5guky3tqqyl.png)

To add content to the page, click on the Edit button.

![Adding contents to the CMS](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/w9qkyyam8323v9p4ku0w.png)

One Entry CMS provides a default WYSIWYG editor that enables you to enter default content for the page. However, in most cases, you may have numerous contents for various sections of a webpage. One Entry allows you to do this using Attributes.

![Adding page attributes to the Headless CMS](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/tje49n6m85p0h31bch6w.png)

Therefore, navigate to the `Attributes` tab from the top menu and click Settings under the `Selecting a set of attributes` field to add a set of attributes to the page.

![How to add page attributes to the content](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/mskwatxmhnpikw80nmuh.png)

Fill in the attribute name and marker (similar to ID for easy identification), and specify its type as Pages.

![Attributes names for the Headless CMS](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/zt1clwcyl20cwe3bzp9e.png)

Next, click the Settings icon to add various content types as attributes to the page. 

From the image below, I created an image, text with header, group of images, and text attributes for adding image and text contents.

![Contents for the One Entry CMS](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/4p38vho7hcpkribfy5ow.png)

Congratulations! You've added various attributes to the page, and now you can add content using the newly created attributes.

Go back to the Page route to add contents using the pre-built attributes. The red icon below the content field allows you to add multiple contents to the page.

![Overview of the CMS](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/slngmpw1jll8gfcixjea.gif)
***
## How to retrieve contents from One Entry CMS

To retrieve your contents from the One Entry Headless CMS, you need an authentication token or certificate to enable you to send a request for the contents within your project.

Click `Settings` from the sidebar menu within your project dashboard, and navigate to the App tokens menu to grenerate a token.

![Preview of the authentication tokens](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/8uq4qdyurkq1i13zuq0e.png)

Save the authentication token and your project URL to a `.env.local` file within your Next.js project.

```
PROJECT_URL=<YOUR_PROJECT_URL>
PROJECT_TOKEN=<YOUR_AUTH_TOKEN>
```

Ensure you've installed the One Entry SDK within your project.

```bash
npm install oneentry
```

Next, copy the code snippet below into an endpoint on the Next.js API route (server). The code snippet above retrieves the page attributes from One Entry CMS.

```tsx
//👉🏻 In pages/api/data
import type { NextApiRequest, NextApiResponse } from "next";
import { defineOneEntry } from "oneentry";

const { Pages } = defineOneEntry(
	process.env.PROJECT_URL!,
	process.env.PROJECT_TOKEN!
);

export default async function handler(
	req: NextApiRequest,
	res: NextApiResponse
) {
	//👇🏻 access the Home page using its marker and language code
	const request = await Pages.getPageByUrl("home", "en_US");
	//👇🏻 returns the attribute values
	res.status(200).json({ attributes: request.attributeValues });
}
```

Finally, you can retrieve the attributes from the API endpoint and display the contents on the client.

```tsx
export default function Home() {
	const [contents, setContents] = useState({});
	const [loading, setLoading] = useState(true);

	useEffect(() => {
		async function getContents() {
			const request = await fetch("/api/data");
			const data = await request.json();
			setLoading(false);
			setContents(data.attributes);
		}
		getContents();
	}, []);

	return (
		<main>
			<Head>
				<title>Home | OneShop</title>
			</Head>
			{loading ? (
				<div className='w-full h-screen flex items-center justify-center'>
					<h3 className='text-2xl font-bold'>Loading</h3>
				</div>
			) : (
				<>
					<Header
						image={contents.single_image.value[0]}
						content={contents.title_and_content.value[0]}
					/>
					<Main content={contents.title_and_content.value} />
					<Products
						content={contents.title_and_content.value}
						images={contents.image_group.value}
					/>
					<ContactForm />
					<Footer content={contents.single_text.value[0].htmlValue} />
				</>
			)}
		</main>
	);
}
```

Congratulations! You’ve completed the project for this tutorial.
***
## Conclusion

So far, you've learnt what One Entry CMS is, how to use it, and some of its amazing features. You've also learnt how to store and retrieve contents from the One Entry CMS via its SDK.

[One Entry Headless CMS](https://oneentry.cloud) is an amazing tool suitable for blogs, e-commerce, educational platforms, mobile applications, and many others. If you need to build an application that requires you to separate your content from the application's logic, you should consider One Entry CMS.

The source code for this tutorial is available here:  [https://github.com/dha-stix/oneentry](https://github.com/dha-stix/oneentry)

Thank you for reading!🎉