DEV Community

Cover image for Building a modern landing page with Next.js and One Entry Headless CMS👑🌟
David Asaolu
David Asaolu

Posted on

Building a modern landing page with Next.js and One Entry Headless CMS👑🌟

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.

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 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.

Headless CMS One Entry project

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

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

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


Enter fullscreen mode Exit fullscreen mode

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


Enter fullscreen mode Exit fullscreen mode

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


Enter fullscreen mode Exit fullscreen mode

Landing page demo for the Headless CMS

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;


Enter fullscreen mode Exit fullscreen mode

Header component

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;


Enter fullscreen mode Exit fullscreen mode

Landing Page Main Component

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;


Enter fullscreen mode Exit fullscreen mode

Landing Page - Products Component

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;


Enter fullscreen mode Exit fullscreen mode

Contact Form

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 &copy; {new Date().getFullYear()} OneShop
            </p>
        </footer>
    );
};

export default Footer;


Enter fullscreen mode Exit fullscreen mode

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

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

Adding contents to the CMS

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

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

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

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

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


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

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>


Enter fullscreen mode Exit fullscreen mode

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



npm install oneentry


Enter fullscreen mode Exit fullscreen mode

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 });
}


Enter fullscreen mode Exit fullscreen mode

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>
    );
}


Enter fullscreen mode Exit fullscreen mode

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)

Collapse
 
artemshchirov profile image
Artem Shchirov

Hi! Thanks for the article!

Just one question: Why choose Nextj.js for landing pages?