DEV Community

Chadere Nyore
Chadere Nyore

Posted on

Building My Resume in Next.js using BCMS

Creating a resume with React or other frontend technologies is often viewed as a simple task for one to try out his frontend skills. But while this project is simple, it is also very easy to mess up and over-engineer.

In this tutorial, you will learn how to create a simple resume with Next.js with the resume data hosted on BCMS, a headless CMS. Why this approach you may ask?

Easier Content Updates: With your resume content hosted separately from your app, you can modify your resume without touching your codebase.

Real-Time Updates: You can make changes on your CMS and they’re instantly reflected on the Next.js website.
Customization and Integration: Headless CMSs often have APIs allowing deep customization. If you need to integrate with other services (like job application APIs, LinkedIn, or GitHub), a CMS can facilitate these integrations more seamlessly than hard-coded data.

These features do not come standard with any CMS, so using a headless CMS with these and other features is essential. . That is why for this tutorial we’re gonna be using BCMS. A modern, minimalistic CMS designed for Developers and content editors.

By the end of this tutorial, you will be able to harness the power of Next.js and the super features that BCMS provides to create a fully functional resume website.

Resume Full created with Next.js and BCMS

Table of content

  1. Setting up Next.js app
  2. Setting up BCMS and creating Resume Content
  3. Fetching resume content from BCMS and rendering on the Next app.
  4. Deployment
  5. Next Steps

Setting Up Next.js App

Now let’s set up the Next.js frontend, add styling and populate resume fields with dummy data.

To begin, head to https://thebcms.com/nextjs-cms copy the terminal command and paste it on your terminal to create a simple BCMS Next project using one of the starters available. For this tutorial, I will be using the simple blog template.

npx @thebcms/cli create next starter simple-blog

This Command will create a next.js project, clone the simple-blog starter inside of it, and then connect to a BCMS instance(more on this later). This is where it requires you to log in, so simply log in when it prompts you to log in using your browser or create a new account if you don’t have a BCMS account yet.

After logging in, the command will prompt you for the name of the project and then successfully create it, as shown in the image below.

BCMS Next.js CLI Command

Navigate to the project folder, run the install command and the npm run dev command, after that, your page will be live at localhost:3000

BCMS Template

Now your Next.js project is ready to go and is linked to a BCMS backend, which is where these blog posts are coming from. We don't need these blog posts, though, because our needs are different. So, Let's clean up the page.

Page Cleanup

Navigate to the src folder, followed by app, in your newly created Next.js project. There's page.tsx, which is the index page, and that's the only page we'll use for this tutorial because it's a one-page resume with no links to other pages.

First, I'll remove the BlogCard and Tag templates that I won't be using, as well as the BlogEntry types that were automatically generated for us when we used the bcms CLI command.

//Delete

import { BlogEntry, BlogEntryMetaItem } from '../../bcms/types/ts';
import BlogCard from '@/components/blog/Card';
import Tag from '@/components/Tag';
Enter fullscreen mode Exit fullscreen mode

After that you can change the page title to any title of your choice.

Moving on to the HomePage function, Remove all functions and clean up the return statement leaving just a div there with a simple “My Resume”.

The code for the page.tsx should look something like this at this point:

import React from 'react';
import { bcms } from './bcms-client';
import { Metadata } from 'next';

const pageTitle = 'Resume - My Simple Resume';
export const metadata: Metadata = {
    title: pageTitle,
    openGraph: {
        title: pageTitle,
    },
    twitter: {
        title: pageTitle,
    },
};

const HomePage: React.FC = async () => {
    return <div>My Resume</div>;
};

export default HomePage;
Enter fullscreen mode Exit fullscreen mode

And on the browser, this is what will be displayed.

Cleaned Up Page

Great, you’ve successfully cleaned up the page.

But there are still other files in the project which we don’t need, so I’m just gonna safely delete them. They’re :

  • src/app/blog
  • src/components Delete every other file in this folder except the layout folder which holds the footer.

After deleting these folders or the files in them, your application structure should look like this:

Folder Structure

Great, with the project cleaned up, it is time to set up the UI for the resume.

Designing Page UI

Now, I will set up the page for the resume and for now, it will hold dummy hardcoded data.

This requires creating the page's HTML structure and then styling it with tailwindcss. I will not go into detail about the CSS or tailwindCSS used to style the page because this is not a CSS tutorial; instead, simply copy the page and replace your existing page.tsx code with it.

import React from 'react';
import { bcms } from './bcms-client';
import { Metadata } from 'next';
import Image from 'next/image';

const pageTitle = 'Resume - My Simple Resume';
export const metadata: Metadata = {
    title: pageTitle,
    openGraph: {
        title: pageTitle,
    },
    twitter: {
        title: pageTitle,
    },
};

const HomePage: React.FC = async () => {
    return (
        <div className="w-8/12 mx-auto mt-6 bg-white px-20 py-12">
            <header className="flex flex-row justify-between mb-4">
                <section className="name">
                    <h1 className="font-bold text-3xl">Your Name</h1>
                    <h3 className="text-2xl">Senior Product Designer</h3>
                </section>
                <section className="profile-photo mr-24">
                    <Image
                        src="/images/avatar.svg"
                        height={90}
                        width={90}
                        alt="resume profile picture"
                    />
                </section>
            </header>
            <main className="details flex justify-between gap-10">
                <section className="basis-[60%]">
                    <h4 className="uppercase text-[#73808D] text-sm tracking-widest mb-1">
                        Experience
                    </h4>
                    <div className="mb-6">
                        <h1 className="font-bold mb-1">
                            Senior UI/UX Product Designer
                        </h1>
                        <h3 className="mb-1">Enterprise Name</h3>
                        <h4 className="text-[#73808D] text-sm">
                            Aug 2018 - Present - 1 year, Paris
                        </h4>
                        <p>
                            Directly collaborated with CEO and Product team to
                            prototype, design and deliver the UI and UX
                            experience with a lean design process: research,
                            design, test, and iterate.
                        </p>
                    </div>
                    <div className="mb-6">
                        <h1 className="font-bold mb-1">
                            UI/UX Product Designer
                        </h1>
                        <h3>Enterprise Name</h3>
                        <h4 className="text-[#73808D] text-sm">
                            Aug 2013 - Aug 2018 - 5 years, Paris
                        </h4>
                        <p>
                            Lead the UI design with the accountability of the
                            design system, collaborated with product and
                            development teams on core projects to improve
                            product interfaces and experiences.
                        </p>
                    </div>
                    <div className="mb-6">
                        <h1 className="font-bold mb-1">UI Designer</h1>
                        <h3>Enterprise Name</h3>
                        <h4 className="text-[#73808D] text-sm">
                            Aug 2012 - July 2013, Paris
                        </h4>
                        <p>
                            Designed mobile UI applications for Orange R&D
                            departement, BNP Paribas, La Poste, Le Cned...
                        </p>
                    </div>
                    <div className="mb-6">
                        <h1 className="font-bold mb-1">Graphic Designer</h1>
                        <h3>Enterprise Name</h3>
                        <h4 className="text-[#73808D] text-sm">
                            Sept 2010 - jul 2012 - 2 years, Paris
                        </h4>
                        <p>
                            Designed print and web applications for Pau Brasil,
                            Renault, Le théatre du Mantois, La mairie de Mantes
                            la Ville...
                        </p>
                    </div>
                    <h4 className="uppercase text-[#73808D] text-sm tracking-widest mb-1">
                        Education
                    </h4>
                    <div className="mb-6">
                        <h1 className="font-bold mb-1">
                            Bachelor European in Graphic Design
                        </h1>
                        <h3>School Name</h3>
                        <h4 className="text-[#73808D] text-sm">
                            2009 - 2010, Bagnolet
                        </h4>
                    </div>
                    <div className="mb-6">
                        <h1 className="font-bold mb-1">
                            BTS Communication Visuelle option Multimédia
                        </h1>
                        <h3>School Name</h3>
                        <h4 className="text-[#73808D] text-sm">
                            2007 - 2009, Bagnolet
                        </h4>
                    </div>
                </section>
                <section className="text-[#73808D] basis-[30%]">
                    <div className="contact mb-4">
                        <p>yourmail@gmail.com</p>
                        <p>+33 6 33 33 33 33</p>
                        <p>Vernouillet</p>
                    </div>
                    <div className="mb-4">
                        <h3 className="font-bold">Industry Knowledge</h3>
                        <p>Industry Knowledge</p>
                        <p>Product Design</p>
                        <p>User Interface</p>
                        <p>User Experience</p>
                        <p>Interaction Design</p>
                        <p></p>
                    </div>
                    <div className="mb-4">
                        <h3 className="font-bold">Tools & Technologies</h3>
                        <p>
                            Figma, Sketch, Protopie, Framer, Invision, Abstract,
                            Zeplin, Google Analytics, Amplitude, Fullstory...
                        </p>
                    </div>
                    <div className="mb-4">
                        <h3 className="font-bold">Other Skills</h3>
                        <p>HTML, CSS, jQuery</p>
                    </div>
                    <div className="mb-4">
                        <h3 className="font-bold">Languages</h3>
                        <p>French (native)</p>
                        <p>English (professionnal)</p>
                    </div>
                    <div className="mb-4">
                        <h3 className="font-bold">Social</h3>
                        <p>yoursite.com</p>
                        <p>linkedin.com/in/yourname</p>
                        <p>dribbble.com/yourname</p>
                    </div>
                </section>
            </main>
        </div>
    );
};

export default HomePage;

Enter fullscreen mode Exit fullscreen mode

From the code above, you can see that you need a photo, for now it will be hosted on our Next.js Project but it will be changed later, so simply go to the public folder, create a new folder named images and save a portrait of your choice to use as a sample profile photo.

Moving on, go to the layout.tsx file under the app folder and change the background color so we get a more beautiful feel and focus on the resume section.

<body className={`${inter.className} overflow-x-hidden bg-[#E5E5E5]`}>
Enter fullscreen mode Exit fullscreen mode

After that, your page should have come to life

Full Page with dummy data

Great, now it's time to set up BCMS and create the resume data.

Setting up BCMS and creating Resume Content

Navigate to https://app.thebcms.com/ to login to your BCMS account. On the left side of your dashboard, you see a dropdown containing a list of your BCMS instances(also called projects).

BCMS instance is just like a box or repository containing your application data, so this is the backend for that particular application. You can create different instances for different applications.

BCMS Instance

If this is your first time using BCMS, you should see only one instance there.

So how do you create data for your application?

The principle behind BCMS data creation is no different from the ones you’ve been using, be it your custom-made backend with either PHP or Nodejs or another CMS if any: You create a data model, this is where you specify the inputs you need, and the property types for that input and then you create data based on those data models.

On the left side of your BCMS dashboard is an administration panel containing Templates, Widgets, and Groups…., and then further Down, you will find entries. What does this all mean, and how do they come together?

**Templates -** This is your content structure, your building block. Here is where you spell out what fields you need for the section you’re creating a template for.

Entries - An Entry is a simple record of a template, a dynamic piece of content that follows the content structure you defined in your template.

Groups - Groups in BCMS are reusable building blocks made of multiple properties. Groups can be included in any template, widget, or other group.

Let’s see this in action.

Modelling Data in BCMS

On your templates tab, you’ll find a blog template which was automatically generated upon the creation of this project using the CLI command up above. Since you’ve gotten rid of all blog related files on your frontend app, it means the blog template is no longer useful to you. So, click on this template, and then click on edit template on the top right of the page, and then safely delete it.

Delete BCMS Blog Template

Now let’s model the data we need for the resume.

First, create a new template named resume. Select single entry as you will only have just one resume unlike a blog where you can have multiple blogs.

Create new BCMS Template
So what is the data needed for this resume?

  • Name - String - Required
  • Title - Automatically created by BCMS
  • Profile Picture - Media - Required

Template creation process

  • Work Experience

This will be a group. Groups in BCMS group reusable pieces of fields together. To create a group, navigate to the group tab in your dashboard and click on Create a new group

I will name this group Work Experience as it will hold information concerning work experience. The information it will hold is:

  • Job Title - String - Required
  • Enterprise Name - String - Required
  • Work Duration - String - Required
  • Work Description - Rich Text - Required

    Work Experience

    Now back to the Resume template, you can now successfully link to this group, and it should be an array since it will be holding multiple work experiences.

Work Edperience Group Pointer

  • Education. Like the work experiences, this will also be a group holding similar data.
    Education History

  • Contact information - This will be a group holding the contact information

Contact Information

   Create the group and link it to the resume template.
Enter fullscreen mode Exit fullscreen mode
  • Industry Knowledge - String, Array
  • Tools and Technologies - Rich Text
  • Other Skills - Rich Text
  • Languages - String, Array
  • Social - String, Array

Congratulations, your template should look something like this at this point

Finished template

Next up, go to the entries tab and then click on the resume to create an entry, there you can now write the actual resume data following the structure which you just defined in the template.

Simply edit the fields there and after doing that click on the status drop down on the right side of the page, change it to published and then update.

With that you’ve successfully written your data and you’re ready to have it on your next.js app.

But before that, we need to update our API key permissions. Navigate to settings and then the API key, click on edit and you should see permissions for the newly created Resume template. Toggle the ones you need on, for this tutorial that will be “can get”. Without this permission, you will not be able to get resume data on your Nextjs frontend.

Update Api key

Fetching resume content from BCMS and rendering on the Next app.

So How do you fetch data from BCMS in your Next.js app? Well you saw a sneak peak, at the start of the project with the blog starter example. Now we’re gonna replicate the same thing but this time, we’re fetching the data for the resume.

First, we need to create the resume types to let typescript know what we’re expecting to get when fetching the data. When we first created the data, the BCMS CLI automatically connected to the BCMS instance, got the blog entry and then generated the types for us which is great.

But that is not all… The CLI continues to generate types for us automatically when we add new templates and entries.

To get these auto-generated types from BCMS all you have to do is stop the dev server and start it again, and BCMS will pick those new templates and entries and generate types for them.

This is one of the great features BCMS offers developers to ease their task, so you don’t have to manually create types for your data, it is already handled for you. Really cool :)

Fetching the data

To fetch BCMS data for your Nextjs application in the page.tsx file, import the types auto generated for you by BCMS,

import { ResumeEntry, ResumeEntryMetaItem } from '../../bcms/types/ts';
Enter fullscreen mode Exit fullscreen mode

And then on our HomePage function, before the return statement, we get the Resume using the bcms client installed for us when we set up the app.

    const resume = (await bcms.entry.getAll('resume')) as ResumeEntry[];
    console.log(resume);
Enter fullscreen mode Exit fullscreen mode

And you should be able to see the returned data in your terminal.

BCMS Entry data

You can also log more fields to see what you can get. For example, I’m gonna log the meta object as it is the object that holds our entry.
Meta Object

Great, now we can use the data in our app. So I'm going to replace the dummy data with that from BCMS. You can copy the finished code below.

import React, { Fragment } from 'react';
import { bcms } from './bcms-client';
import { ResumeEntry, ResumeEntryMetaItem } from '../../bcms/types/ts';
import { Metadata } from 'next';
import { notFound } from 'next/navigation';
import Image from 'next/image';
import Link from 'next/link';
import { BCMSImage } from '@thebcms/components-react';

const pageTitle = 'Resume - My Simple Resume';
export const metadata: Metadata = {
    title: pageTitle,
    openGraph: {
        title: pageTitle,
    },
    twitter: {
        title: pageTitle,
    },
};

const HomePage: React.FC = async () => {
    const resume = (await bcms.entry.getAll('resume')) as ResumeEntry[];
    if (!resume) {
        return notFound();
    }

    const data = {
        meta: resume[0].meta.en as ResumeEntryMetaItem,
    };

    const imgObj = resume[0].meta.en?.profile_picture;

    return (
        <div className="w-8/12 mx-auto mt-6 bg-white px-20 py-12">
            <header className="flex flex-row justify-between mb-4">
                <section className="name">
                    <h1 className="font-bold text-3xl">{data.meta.name}</h1>
                    <h3 className="text-2xl">{data.meta.title}</h3>
                </section>
                <section className="profile-photo mr-24">
                    <BCMSImage
                        clientConfig={bcms.getConfig()}
                        media={imgObj}
                        className="object-cover rounded-2xl w-28"
                    />
                </section>
            </header>
            <main className="details flex justify-between gap-10">
                <section className="basis-[60%]">
                    <h4 className="uppercase text-[#73808D] text-sm tracking-widest mb-1">
                        Experience
                    </h4>
                    {data.meta.work_experience.map((experience, index) => {
                        return (
                            <div className="mb-6" key={index}>
                                <h1 className="font-bold mb-1">
                                    {experience.job_title}
                                </h1>
                                <h3 className="mb-1">
                                    {experience.enterprise_name}
                                </h3>
                                <h4 className="text-[#73808D] text-sm">
                                    {experience.work_duration}
                                </h4>
                                <p />
                                <span
                                    dangerouslySetInnerHTML={{
                                        __html: experience.work_description
                                            .nodes[0].value,
                                    }}
                                />
                            </div>
                        );
                    })}
                    <h4 className="uppercase text-[#73808D] text-sm tracking-widest mb-1">
                        Education
                    </h4>
                    {data.meta.education.map((degree, index) => {
                        return (
                            <div className="mb-6" key={index}>
                                <h1 className="font-bold mb-1">
                                    {degree.degree_name}
                                </h1>
                                <h3>{degree.school_name}</h3>
                                <h4 className="text-[#73808D] text-sm">
                                    {degree.duration_and_location}
                                </h4>
                            </div>
                        );
                    })}
                </section>
                <section className="text-[#73808D] basis-[30%]">
                    <div className="contact mb-4">
                        <p>{data.meta.contact_information.email}</p>
                        <p>{data.meta.contact_information.phone_number}</p>
                        <p>{data.meta.contact_information.location}</p>
                    </div>
                    <div className="mb-4">
                        <h3 className="font-bold">Industry Knowledge</h3>
                        {data.meta.industry_knowledge.map(
                            (knowledge, index) => {
                                return <p key={index}>{knowledge}</p>;
                            },
                        )}
                    </div>
                    <div className="mb-4">
                        <h3 className="font-bold">Tools & Technologies</h3>
                        <span
                            dangerouslySetInnerHTML={{
                                __html: data.meta.tools_and_technologies
                                    .nodes[0].value,
                            }}
                        />
                    </div>
                    <div className="mb-4">
                        <h3 className="font-bold">Other Skills</h3>
                        <span
                            dangerouslySetInnerHTML={{
                                __html: data.meta.other_skills.nodes[0].value,
                            }}
                        />
                    </div>
                    <div className="mb-4">
                        <h3 className="font-bold">Languages</h3>
                        {data.meta.languages.map((language, index) => {
                            return <p key={index}>{language}</p>;
                        })}
                    </div>
                    <div className="mb-4 flex flex-col">
                        <h3 className="font-bold">Social</h3>
                        {data.meta.social.map((social, index) => {
                            return (
                                <Link key={index} href={social}>
                                    {social}
                                </Link>
                            );
                        })}
                    </div>
                </section>
            </main>
        </div>
    );
};

export default HomePage;

Enter fullscreen mode Exit fullscreen mode

With that, you’ve successfully queried data from the BCMS backend and rendered it on your website. If you check your browser now, you will see the content being displayed.

Next.js resume with data coming from BCMS

Deployment🚀

To deploy your Next.js app, you need to use a service called Vercel. It's a straightforward way to make your website live on the internet. Follow this tutorial to learn how to use Vercel.🚀

Next steps

To sum it up, creating a resume using NextJS headless CMS is simple and powerful. BCMS headless CMS helps you manage your resume’s content, while Next.js makes sure your website works well. The resume you've created is not just for show – it's a starting point for you to explore what BCMS can do. With BCMS, you can easily adapt your website to different needs and connect it with other tools.

You have created this one-page resume, but you can take it further by making it multiple. That means in the resume template you can make it a non-single-entry to create multiple resumes for different needs. Maybe a Java resume and then a UI design resume that you show to different recruiters.

BCMS makes this process really simple, by just toggling off single entry in the template, you will be able to create multiple resumes, and then on your nextjs app, you get those different resumes and display them on different urls just like a blog works.

Well with that I will say happy coding and let’s see what you’re able to achieve with Nextjs and BCMS.🙌

Top comments (0)