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 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.
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 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 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.
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.
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.
Finally, create a Next.js project using the code snippet below and install the One Entry SDK package as its dependency.
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 and 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.
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.
mkdir components
cd components
touch Header.tsx Main.tsx Products.tsx ContactForm.tsx Footer.tsx
The Header Component
Copy the code snippet below into the Header.tsx
file. The code snippet uses React Scroll to navigate users between each section of the page.
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;
The Main Component
Update the Main.tsx
file by copying the code snippet below into the file.
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;
The Products component
Copy the code snippet below into the Products.tsx
file. It displays the available products.
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;
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:
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;
The Footer component
Update the Footer.tsx
file by copying the code snippet below into the file.
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 © {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.
To add content to the page, click on the Edit button.
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.
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.
Fill in the attribute name and marker (similar to ID for easy identification), and specify its type as Pages.
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.
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.
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.
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.
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.
//👉🏻 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.
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 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
Thank you for reading!🎉
Top comments (1)
Hi! Thanks for the article!
Just one question: Why choose Nextj.js for landing pages?