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 step-by-step guide, you will learn how to make a resume website with Next.js with the resume data hosted on BCMS, a headless CMS.
How to make a resume website with the headless approach?
Why this approach you may ask? Well, there are a couple of reasons why to use headless CMS as a resume website builder, to name a few:
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, and social media like 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 BCMS is the best choice for building a professional resume, along with Next.js.
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.
Table of contents: How to make a resume website with BCMS
- Setting up Next.js app
- Setting up BCMS and creating Resume Content
- Fetching resume content from BCMS and rendering on the Next app.
- Deployment
- 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 Next CMS, copy the terminal command and paste it on your terminal to create a simple BCMS Next project using one of the Next starters available.
For this tutorial, I will be using the simple blog website 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.
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
.
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. You don't need these blog posts, though, because your 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 me when I used the bcms CLI command.
//Delete
import { BlogEntry, BlogEntryMetaItem } from '../../bcms/types/ts';
import BlogCard from '@/components/blog/Card';
import Tag from '@/components/Tag';
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;
And on the browser, this is what will be displayed:
Great, you’ve successfully cleaned up the page.
But there are still other files in the project that 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:
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 Tailwind CSS. I will not go into detail about the CSS or Tailwind CSS 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;
From the code above, you can see that you need a photo, for now, it will be hosted on 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]`}>
After that, your page should have come to life:
Great, now it's time to set up BCMS and create the resume data.
Setting up BCMS and creating Resume Content
Navigate to BCMS Cloud 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.
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 that 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.
Now let’s model the data I need for the resume.
First, create a new template named resume. Select a single entry as you will only have just one resume, unlike a blog where you can have multiple blogs.
So what is the data needed for this resume?
- Name - String - Required
- Title - Automatically created by BCMS
- Profile Picture - Media - Required
- 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
Now back to the Resume template, you can successfully link to this group, and it should be an array since it will be holding multiple work experiences.
- Education: Like the work experiences, this will also be a group holding similar data.
Create the group and link it to the resume template.
Contact form information - This will be a group holding the contact information
Create the group and link it to the resume template.
- 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:
Next up, go to the entries tab and then click on the resume to create an entry, you can now write the actual resume data following the structure that 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, you need to update your 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.
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 you’re gonna replicate the same thing but this time, you’re fetching the data for the resume.
First, I need to create the resume types to let the Typescript know what I am expecting to get when fetching the data. When I first created the data, the BCMS CLI automatically connected to the BCMS instance got the blog entry, and then generated the types for me which is great.
But that is not all… The CLI continues to generate types for me automatically when I 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 tasks, 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 on the page.tsx file, import the types auto-generated for you by BCMS.
import { ResumeEntry, ResumeEntryMetaItem } from '../../bcms/types/ts';
And then on your HomePage function, before the return statement, you get the Resume using the bcms client installed for you when you set up the app.
const resume = (await bcms.entry.getAll('resume')) as ResumeEntry[];
console.log(resume);
And you should be able to see the returned data in your terminal.
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.
Great, now you can use the data in your 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;
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.
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 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. For example, you could create 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 a 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 can achieve with Nextjs and BCMS.
Top comments (0)