DEV Community

Cover image for Building agency website with headless BCMS and NextJs
Momcilo
Momcilo

Posted on

Building agency website with headless BCMS and NextJs

This guide will show you how to build an agency website with NextJs and BCMS. You’ll create a professional website for your clients in a few minutes with little coding experience.

Elements of an Agency Website

Before getting hands-on, these are some of the key elements that make up a stellar agency website:

  • Hero Section: This can be a picture or video that communicates your agency’s value and acts as a first impression.
  • Header: This is your navigation bar, providing easy access to different sections of your website like "Services," "Portfolio," "About Us," and "Contact."
  • Services: This section outlines the services you offer, highlighting your expertise and how it benefits clients.
  • Portfolio: This section showcases your best work through compelling visuals and client testimonials.
  • Blog Page: This section your agency as a thought leader by sharing valuable insights and industry trends.
  • Contact Form: This section makes it easy for potential clients to reach you with a user-friendly contact form.
  • Footer: This includes essential information like contact details, copyright information, and social media links.
  • About Us Section: This section introduces your team, shares your agency's story, and builds trust with potential clients.
  • Pricing Section: If applicable, this section provides a transparent overview of your pricing structure.

Why you should use BCMS and NextJs for building agency website

Next.js utilizes static site generation (SSG) to prerender your website content, resulting in faster load times. It is also SEO-friendly, ensuring your website ranks well in search results. Additionally, you can build reusable components that ensure consistent design across your website.

However, using a user-friendly interface, a headless CMS like BCMS enables you to manage website content easily without touching code while updating text, images, and other content.

Learn more: Choosing the right CMS for Next.js Project: 14 Key Questions

Now, combining these two, allows you to focus on creating compelling content with BCMS while Next.js handles the technical aspects. For this tutorial, you’ll create an agency website similar to the BCMS Agency Website Starter.

Introducing the BCMS Nextjs Agency WebsiteStarter

The BCMS Nextjs Agency Website starter is a prebuilt solution with all the essential components for an agency website. This starter gives you a solid foundation to build your website. It gives you an idea of BCMS and how it works with Nextjs.

In this tutorial, you will learn how to build an Agency website with Nextjs and BCMS modeling this starter. You won’t be coding from scratch so you don’t have to be a developer expert to build your website. Instead, you’ll explore the starter, see how it was built, and understand why it was built that way.

By following this NextJs tutorial, you’ll learn how to build NextJS sites integrated with BCMS to add your desired functionalities. Do well to give the BCMS starters repo a star.

Getting started with BCMS and Nextjs

Now, you are ready to use BCMS and Nextjs to bring your agency website to life. The article will have the following structure:

  • Initializing BCMS
  • Populating BCMS with website data
  • Configuring NextJs to use BCMS
  • Fetching and Displaying Data on the Frontend
  • Deployment

Initializing BCMS

There are two options for using BCMS: Live and Local. BCMS Live is a hosted solution where BCMS takes care of server management and infrastructure. You can sign up for a BCMS Cloud account on their website.

While BCMS Local enables you to self-host BCMS on your own server. The documentation provides instructions for setting this up.

For this tutorial, you’ll be setting it up locally. To begin, install the BCMS CLI globally:

npm install @becomes/cms-cli -g
Enter fullscreen mode Exit fullscreen mode

Verify your installation, by running this command:

bcms --help
Enter fullscreen mode Exit fullscreen mode

If the command is recognized in your terminal, this means BCMS is successfully installed.

Creating a project

To create a BCMS project, run the following command:

bcms --cms create
Enter fullscreen mode Exit fullscreen mode

Answer the prompts as required and wait for the setup to complete. On completion, the project doesn’t start by default. BCMS requires Docker to deploy the application and run it. See the official Docker documentation to install Docker if you don’t have it on your machine.

Going forward, navigate to your created BCMS project in your terminal and run:

docker-compose up
Enter fullscreen mode Exit fullscreen mode

If the docker installation were successful, the server would be up and running. To verify, visit localhost:8080 on your browser. You’ll see a welcome screen that says You are now logged in. The application you just launched is your BCMS Instance.

Populating BCMS with Website Data

BCMS data creation follows the same basic principles as other systems you might be familiar with, like custom backends or other CMS. For starters, you define a data model that defines the information you want to capture like specific fields, each having a designated data type (text, number, etc).

Now, on your BCMS dashboard, you’ll find an admin panel with the key elements:

  • Templates: These are your content blueprints. You define the fields needed for each section within a template.
  • Entries: These are individual pieces of content that follow the structure you defined in the template.
  • Groups: These are reusable collections of properties that can be incorporated into any template, widget, or even another group.

In simpler terms: Templates are like pre-designed content boxes, entries are the actual content that fills those boxes, and groups are like pre-made sets of elements that you can insert anywhere.

Combining these elements allows you to create and manage your content with BCMS. To learn more about BCMS Widgets, check out this resource.

Now, you can enter the information you want for an agency site. You’ll identify the kind of information that should be stored in BCMS (backend) instead of being directly hard coded into the website’s front end. The sections of the agency website that likely need BCMS to store their data are:

  1. Contact page
  2. Footer
  3. Header
  4. Homepage
  5. Legal page
  6. Portfolio ( Item )
  7. Portfolio page
  8. Service
  9. Service page
  10. Team member
  11. Team page

There are 11 sections, hence you’ll create 11 templates.

Building agency website: Contact page

Starting with the contact page, the necessary properties are:

  • Title
  • Slug
  • SEO
  • Email
  • Phone

Firstly, create a new group called SEO. This group will store essential information for search engines, in this case, the Title and Description:

Image description

Now create the Contact page template with the properties mentioned earlier. On creating a template, the Title and Slug features are added by default, so the features you’ll add are SEO, Email, and Phone.

Image description

Building agency website: Footer

The properties needed for the Footer are Title and Slug. These are created by default when you create the Footer template.

Image description

Header

Along with the default properties, the Header template has a Nav, which is an array of navigation links.

Image description

Homepage Template

Looking at the Starter’s Hompage above, the identified properties are:

  • Home Hero
  • Home About
  • Home Services
  • Home Capabilities
  • Team
  • Contact block

Home Hero: This group should contain Title and Gallery properties.

Image description

Home about: This group should contain a Title, Subtitle, Description, and Cover image for the About Us section on the home page.

Image description

Home services: Likewise, the Home services group has the Title, Subtitle, Description, and Cover image properties.

Image description

Home capabilities: The capabilities group has a Title, Subtitle, Description, and Portfolio items properties. However, Portfolio items are linked to the Portfolio template. So first create the Portfolio template with Title, Slug, Subtitle, Description, Cover, Project Cover, and Url properties:

Image description

Then create the Home capabilities group:

Image description

Home team: In addition to the Title, Subtitle, Description, and Cover, there are Members title, Members Description and Members properties in this group. Members property is linked to the Team member template. So create it with Role, Description, and Image properties in addition to the template defaults:

Image description

Then create the Home team group:

Image description

Contact block: This group has just the Title and Description properties.

Image description

Finally, you can create the Home page template since the necessary groups and templates have been built:

Image description

Legal page

This template contains the template defaults in addition to the SEO and Blocks properties. The Blocks property is linked to the Legal block group. Therefore, create the Legal block group with a Title and Description property:

Image description

Then create the Legal page template:

Image description

Portfolio

This template should contain the default properties with Subtitle, Description, Cover, Project Cover, and Url properties.

Image description

Portfolio page

The Portfolio page template has the SEO, Description, Items, and Contact block properties besides the default properties.

Image description

Service

The Service template contains a Description, Cover, and Theme properties as well as the defaults.

Image description

Services page

The Services page template contains the SEO, Description, Services, and Contact block properties in addition to the defaults.

Image description

Team member

The Team member template contains the member’s Role, Description, and Image in addition to the defaults.

Image description

Team page

The Team page template should contain the SEO, Description, Team members, and contact block properties alongside the defaults.

Image description

Now you've successfully developed templates for your project. However, templates define the structure for your content, similar to a data model. But to populate that structure you need entries.

You’ll find the Entries tab with the admin panel (scroll down if needed). Here you can create entries for each template.

Also, if the provided Starter content meets your needs you can download the entire BCMS instance from GitHub, run it locally, and use the existing content directly or you can download the media files and content from the starter instance and import them into your own BCMS instance.

For the scope of this tutorial, you’ll be creating your content from scratch for just the Contact page, Header, and Footer.

First, create an entry named Contact page and fill it with content based on the template.

The entry should contain the title, SEO, Email, and Phone fields as defined in the Contact Page properties.

Image description

Then create an entry for the Header and Footer and place them in a collection/folder called LAYOUT by dragging one above the other. This collection is there just for better organization - it does not affect the CMS in any other way.

Image description

Image description

Configuring NextJS to use BCMS

Now, you can connect your Next.js frontend to your BCMS backend to retrieve data. You’ll use the BCMS CLI to streamline this process.

To ensure secure communication between your Next.js app and BCMS, you’ll need an API key. This key grants your app access to specific BCMS data.

Creating an API Key

  1. Navigate to the Key Manager tab within the BCMS admin panel
  2. Click Add New Key to create a new key.
  3. Give your key a descriptive name
  4. Permissions: Since this tutorial focuses on data fetching, you only need the Can get resources permission for all the templates.

Image description

Now, navigate to the directory you want to create your Next.js project and use the following BCMS CLI command:

bcms --website create
Enter fullscreen mode Exit fullscreen mode

This will prompt you with details like the framework which in this case is Next.js, the project name, BMCS instance (live or local), and the API key. Once you provide this information the BCMS CLI will set up your Next.js project for you.

If you encounter a Not Logged in Message during setup, in your terminal run:

bcms --login true
Enter fullscreen mode Exit fullscreen mode

A login URL will open in your browser. Follow the steps to log in successfully. Then delete the project that initially created the login issue. Finally, rerun the BCMS CLI with the correct details to set up a new project.

Now that we have set up the NextJs project, let’s proceed to add the API key to it.

Within your project directory, locate bcms.config.js and add the new API key to it. Also, the origin field should point to http://localhost:8080.Remember to replace this with your production URL when deploying to your server.

const { createBcmsMostConfig } = require('@becomes/cms-most');

module.exports = createBcmsMostConfig({
  cms: {
    origin:
      process.env.BCMS_API_ORIGIN ||
      'http://localhost:8080',
    key: {
      id: process.env.BCMS_API_KEY || '663126a615be57915b79a1cb',
      secret:
        process.env.BCMS_API_SECRET ||
        'f1d4444fdbd7a481d3f5d2e77be3bc162c272d0481d7999d90bdf9cc71f69abd',
    },
  },
  media: {
    output: 'public/api',
    download: false,
  },
  enableClientCache: true,
});

Enter fullscreen mode Exit fullscreen mode

Similarly, navigate to pages/_app.tsx and the API keys


import '../styles/globals.css';
import type { AppProps } from 'next/app';
import { BCMSImageConfig } from '@becomes/cms-most/frontend';

BCMSImageConfig.cmsOrigin =
  process.env.NEXT_PUBLIC_BCMS_API_ORIGIN ||
  'http://localhost:8080';
BCMSImageConfig.publicApiKeyId =
  process.env.NEXT_PUBLIC_BCMS_API_PUBLIC_KEY_ID || '663126a615be57915b79a1cb';

function MyApp({ Component, pageProps }: AppProps): JSX.Element {
  return <Component {...pageProps} />;
}

export default MyApp;


Enter fullscreen mode Exit fullscreen mode

💡 It’s advisable to store sensitive data such as API keys in an environment file.

You can now run the development server by npm run dev which will also fetch the data from the CMS.

Fetching and Displaying Data on the Frontend

While setting up the NextJs project with BCMS, the BCMS CLI automatically generates type definitions for entries and templates, which can be imported from ./bcms/types

Now create a page-data.ts under utils in the project directory to fetch header and footer data from your Nextjs application using BCMS. It retrieves entries for the header and footer :

import {
  FooterEntry,
  FooterEntryMeta,
  HeaderEntry,
  HeaderEntryMeta,
} from '@/bcms/types';
import { BCMSClient } from '@becomes/cms-client/types';

export interface HeaderAndFooter {
  header: HeaderEntryMeta;
  footer: FooterEntryMeta;
}

export async function getHeaderAndFooter(
  client: BCMSClient,
): Promise<HeaderAndFooter> {
  const header = (await client.entry.get({
    // Template name or ID
    template: 'header',
    // Entry slug or ID
    entry: 'header',
  })) as HeaderEntry;
  const footer = (await client.entry.get({
    // Template name or ID
    template: 'footer',
    // Entry slug or ID
    entry: 'footer',
  })) as FooterEntry;

  return {
    header: header.meta.en as HeaderEntryMeta,
    footer: footer.meta.en as FooterEntryMeta,
  };
}

Enter fullscreen mode Exit fullscreen mode

Then, create PageWrapper.tsx to define the layout component for your Next.js application and page data:

import Header from './layout/Header';
import Footer from './layout/Footer';
import React, { FC, PropsWithChildren, useMemo } from 'react';
import { PageProps } from '@/types';
import Head from 'next/head';
import { useRouter } from 'next/router';

export const PageWrapper: FC<PropsWithChildren<PageProps>> = ({
  page,
  header,
  children,
  footer,
}) => {
  const router = useRouter();
  const routePath = useMemo(() => router.asPath, [router.asPath]);
  const title = page?.meta?.title ?? 'YourBrand';
  const description =
    'Jumpstart your Next project with this BCMS starter. Easily manage your content and scale your application without the backend hassle. Get started now!';
  const image = '/thumbnail.jpg';
  const domain = 'https://yourwebsite.com';
  return (
    <div className="flex flex-col min-h-screen flex-1 overflow-hidden">
      <Head>
        <title>{title} - YourBrand</title>
        <meta name="description" content={description} />
        <meta property="og:site_name" content={`${title} - YourBrand`} />
        <meta property="og:type" content="website" />
        <meta property="twitter:card" content="summary_large_image" />
        <meta
          name="ogUrl"
          property="og:url"
          content={`${domain}${routePath}`}
        />
        <meta property="og:title" content={`${title} - YourBrand`} />
        <meta property="og:description" content={description} />
        <meta property="og:image" content={image} />
        <meta property="twitter:url" content={`${domain}${routePath}`} />
        <meta property="twitter:title" content={`${title} - YourBrand`} />
        <meta property="twitter:description" content={description} />
        <meta property="twitter:image" content={image} />
        <link rel="canonical" href={`${domain}${routePath}`} />
        <link rel="preconnect" href="https://fonts.googleapis.com" />
        <link rel="preconnect" href="https://fonts.gstatic.com" />
        <link rel="preconnect" href="https://fonts.gstatic.com" />
        <link
          href="https://fonts.googleapis.com/css2?family=Inter:wght@400;500;600;700&family=Playfair+Display:wght@400;500;600;700&display=swap"
          rel="stylesheet"
        />
      </Head>
      <Header data={header} />
      <main className="flex-1">{children}</main>
      <Footer data={footer} />
    </div>
  );
};

Enter fullscreen mode Exit fullscreen mode

Also, create a contact.ts under the types/pages folder and define the type interface for the contact page data. You’ll import ContactPageEntryMeta type defined in @bcms/types :

import { ContactPageEntryMeta } from '@/bcms/types';

export interface ContactPageData {
  meta: ContactPageEntryMeta;
}
Enter fullscreen mode Exit fullscreen mode

After that create an interface for page-props.ts in your Next.js application under the types folder.

import { HeaderEntryMeta, FooterEntryMeta } from '@/bcms/types';
import {
  ContactPageData,
} from './pages';

export interface PageProps<
  Page =
    | ContactPageData

> {
  page: Page;
  header: HeaderEntryMeta;
  footer: FooterEntryMeta;
}

Enter fullscreen mode Exit fullscreen mode

Finally, create the contact.tsx page under the pages folder:

import React from 'react';
import { PageWrapper } from '@/components/PageWrapper';
import ContactHero from '@/components/contact/Hero';
import ContactForm from '@/components/contact/Form';
import { ContactPageData, PageProps } from '@/types';
import { GetStaticProps } from 'next';
import { getBcmsClient } from 'next-plugin-bcms';
import { getHeaderAndFooter } from '@/utils/page-data';
import { ContactPageEntry, ContactPageEntryMeta } from '@/bcms/types';

const ContactPage: React.FC<PageProps<ContactPageData>> = ({
  page,
  header,
  footer,
}) => {
  return (
    <PageWrapper page={page} header={header} footer={footer}>
      <ContactHero
        title={page.meta.title}
        email={page.meta.email}
        phone={page.meta.phone}
      />
      <ContactForm />
    </PageWrapper>
  );
};

export const getStaticProps: GetStaticProps<
  PageProps<ContactPageData>
> = async () => {
  const client = getBcmsClient();
  const { header, footer } = await getHeaderAndFooter(client);

  // Get Contact Page entry
  const contactPage = (await client.entry.get({
    template: 'contact_page',
    entry: 'contact',
  })) as ContactPageEntry;

  if (!contactPage) {
    throw new Error('Home page entry does not exist.');
  }

  return {
    props: {
      header,
      footer,
      page: {
        meta: contactPage.meta.en as ContactPageEntryMeta,
      },
    },
  };
};

export default ContactPage;
Enter fullscreen mode Exit fullscreen mode

After this has been added, you can now visit the contact page on localhost:3000/contact to see the data we’ve fetched from the BCMS.

💡 The page will not be styled properly because it depends on Tailwind CSS which is out of the scope of this tutorial. See TailwindCSS documentation to learn more.

Image description

Hence, you’ve successfully integrated BCMS into the NextJs frontend. If you’d like to finish this project and build all the pages, then check out the BCMS Next.js Agency Website Starter.

Deployment

To deploy your self-hosted BCMS instance, you’ll need to deploy it to a live server like DigitalOcean to make it accessible on the internet. The BCMS documentation provides deployment steps for DigitalOcean deployment.

However with the BCMS cloud, deployment is handled for you, eliminating the need for manual server configuration,

Once your BCMS instance is deployed (either self-hosted or cloud-based), you can proceed to deploy your Next.js frontend.

💡 Again, remember to update BCSM_API_ORIGIN in your app with the new production URL of your BCMS instance on DigitalOcean.

Then test your Next.js application locally to verify everything works by running npm run build and if there are no errors, you can now deploy to Vercel. See the official Next.js guide to deploy your Next.js frontend to Vercel.

Top comments (0)