Building a Portfolio Website with React and ButterCMS

Posted by Taminoturoko Briggs on September 30, 2022

GSD

Portfolios are personal websites used to show our works, skills, accomplishments, experiences, and achievements in detail. As a developer, having one is essential for presenting evidence of our abilities to potential employers and clients when on a job hunt.

In this tutorial, we will learn how to build and deploy a portfolio website with React, Tailwind CSS, and ButterCMS.

Project requirements

To follow along with this tutorial, you need to have the following:

  • Basic knowledge of React and Tailwind CSS
  • Node.js and Git installed on your system
  • A code editor for writing and editing our code—I recommend using VS code, you can download it here.

What we'll be building

The portfolio will have sections showing our introduction, the skills we have acquired, and our most recent and best projects. You can find the code for this tutorial in this GitHub repo

Below is a GIF showing what we will build in the tutorial:

Portfolio landing page

Tools we need to build with React

Below are the tools and platforms we will use to build and deploy our portfolio website:

  • ButterCMS: This is a headless content management system (CMS) that we will be using to store and manage the content of different sections of the portfolio web page. To allow for frequent updates to the portfolio site, we will create components with ButterCMS that manage different portions of the portfolio. That will allow us to easily create, edit, and delete them from the CMS dashboard.
  • Tailwind CSS: Tailwind CSS is a utility-based CSS framework that enables developers to quickly build user interfaces. It provides the user with CSS classes that provide different CSS styles to easily customize an application. We will be using Tailwind CSS in our application to quickly build and design the portfolio web page.
  • React-scroll: React-scroll is a React.js library for handling scroll transitions in a React application. We will make use of React-scroll to smoothly scroll to sections of the web page when the corresponding navigation menu item is clicked upon.
  • React Icons: React Icons is a repository of numerous icons that include social icons, chat icons, and hero icons, among many others. We will make use of React Icons to provide the icons for the social media profiles, which will be displayed in our application.
  • Axios: Axios is a well-known library that is mostly used to send asynchronous HTTP queries to REST endpoints. When performing CRUD activities, this library comes in quite handy. Using this library, you can talk to the backend. In our application, we will use Axios to connect our CMS to our application through a fetch request which will return the content stored on our CMS.

Setting up React

I have already created a starter repo where I have included the images and data we will be using in this tutorial. I have also included dependencies and configurations for Tailwind CSS and other dependencies like Axios, React Icons, and React Scroll.

The next step is to clone the GitHub repo. You can do this with the following commands:

git clone -b starter https://github.com/Tammibriggs/portfolio-butterCMS.git

cd portfolio-butterCMS 

npm install

After this, we can now start the development server with the npm start command. 

React banner CTA

Building a portfolio website with React

Since React follows a component-based architecture where a large interface is decoupled into individual self-sustaining pieces called components, to build our portfolio, we will be creating multiple components.

The portfolio will have a header with links to navigate to different sections of the page. It will have sections for about, skills, works, and a contact form. Knowing this, we can figure out the components to be created and what name they should be given. Let’s start by creating and rendering them so that we won’t have to come back to this process.

In the src directory of the cloned app, create a component folder and add the following files: Navbar.jsx, Home.jsx, About.jsx, Skills.jsx, Works.jsx, and Contact.jsx. Below is what the folder structure of the src directory should look like:

undefined

Next, in all the created files, create the basic structure of a React functional component. For example, here is what the About.jsx file will look like:

import React from 'react'

function About() {
  return (
    <div>About</div> 
  )
}
export default About

Next, let’s render all the created components. Modify the App.js file to look like the following:

import About from "./components/About";
import Contact from "./components/Contact";
import Home from "./components/Home";
import Navbar from "./components/Navbar";
import Skills from "./components/Skills";
import Works from "./components/Works";

function App() { 
  return (
    <div>
      <Navbar />
      <Home />
      <About />
      <Skills />
      <Works />
      <Contact />
    </div>
  );
}
export default App;

Finally, modify index.css to look like the following:

@tailwind base;
@tailwind components;
@tailwind utilities;
@import url('https://fonts.googleapis.com/css2?family=Raleway:wght@300;400;500;600;700;800;900&display=swap');
@layer base { 
    body {
        @apply font-[Raleway];
    }
    li {
        @apply px-4;
        @apply cursor-pointer
    }
}
.content-div {
    background-repeat: no-repeat;
    background-size: cover;
    background-position: center;
    height: 250px;
}
.content-div:hover {
    background-image: linear-gradient(
        to right,
        rgba(112, 157, 255, 0.8),
        hsla(242, 74%, 61%, 0.8)   
    )!important;
}

Creating the navigation bar

Our navigation component is the Navbar.jsx file. To create this, add the following code to Navbar.jsx:

import React, { useState } from 'react'; 
import {
  FaBars,
  FaTimes,
  FaGithub,
  FaLinkedin,
  FaFacebook,
  FaLinkedinIn,
} from 'react-icons/fa';
import { HiOutlineMail } from 'react-icons/hi';
import { BsFillPersonLinesFill } from 'react-icons/bs';
import { Link } from 'react-scroll';

const Navbar = () => {
  const [nav, setNav] = useState(false);
  const handleClick = () => setNav(!nav);

  return (
    <div className='fixed w-full h-[80px] flex justify-between items-center px-4 bg-[#0a192f] text-gray-300'>
      <div>
        <h1 className=' font-thin text-2xl italic font-serif'>TB</h1>
      </div>
      {/* menu */}
      <ul className='hidden md:flex gap-x-8'>
        <li>
          <Link to='home' smooth={true} duration={500}>
            Home
          </Link>
        </li>
        <li>
          <Link to='about' smooth={true} duration={500}>
            About
          </Link>
        </li>
        <li>
          <Link to='skills' smooth={true} duration={500}>
            Skills
          </Link>
        </li>
        <li>
          <Link to='work' smooth={true} duration={500}>
            Work
          </Link>
        </li>
        <li>
          <Link to='contact' smooth={true} duration={500}>
            Contact
          </Link>
        </li>
      </ul>
      {/* Hamburger */}
      <div onClick={handleClick} className='md:hidden z-10'>
        {!nav ? <FaBars /> : <FaTimes />}
      </div>
      {/* Mobile menu */}
      <ul
        className={
          !nav
            ? 'hidden'
            : 'absolute top-0 left-0 w-full h-screen bg-[#0a192f] flex flex-col justify-center items-center'
        }
      >
        <li className='py-6 text-4xl'>
          <Link onClick={handleClick} to='home' smooth={true} duration={500}>
            Home
          </Link>
        </li>
        <li className='py-6 text-4xl'>
          {' '}
          <Link onClick={handleClick} to='about' smooth={true} duration={500}>
            About
          </Link>
        </li>
        <li className='py-6 text-4xl'>
          {' '}
          <Link onClick={handleClick} to='skills' smooth={true} duration={500}>
            Skills
          </Link>
        </li>
        <li className='py-6 text-4xl'>
          {' '}
          <Link onClick={handleClick} to='work' smooth={true} duration={500}>
            Work
          </Link>
        </li>
        <li className='py-6 text-4xl'>
          {' '}
          <Link onClick={handleClick} to='contact' smooth={true} duration={500}>
            Contact
          </Link>
        </li>
      </ul>
      {/* Social icons */}
      <div className='hidden lg:flex fixed flex-col top-[35%] left-0'>
        <ul>
          <li className='w-[160px] h-[60px] flex justify-between items-center ml-[-100px] hover:ml-[-10px] duration-300 bg-blue-600'>
            <a
              className='flex justify-between items-center w-full text-gray-300'
              href='/'
            >
              Linkedin <FaLinkedin size={30} />
            </a>
          </li>
          <li className='w-[160px] h-[60px] flex justify-between items-center ml-[-100px] hover:ml-[-10px] duration-300 bg-[#333333]'>
            <a
              className='flex justify-between items-center w-full text-gray-300'
              href='/'
            >
              Github <FaGithub size={30} />
            </a>
          </li>
          <li className='w-[160px] h-[60px] flex justify-between items-center ml-[-100px] hover:ml-[-10px] duration-300 bg-[#6fc2b0]'>
            <a
              className='flex justify-between items-center w-full text-gray-300'
              href='/'
            >
              Email <HiOutlineMail size={30} />
            </a>
          </li>
          <li className='w-[160px] h-[60px] flex justify-between items-center ml-[-100px] hover:ml-[-10px] duration-300 bg-[#565f69]'>
            <a
              className='flex justify-between items-center w-full text-gray-300'
              href='/'
            >
              Resume <BsFillPersonLinesFill size={30} />
            </a>
          </li>
        </ul>
      </div>
    </div>
  );
};
export default Navbar;

Creating a landing page

Our landing page component is the Home.jsx file. Copy the code below into it:

import React from 'react';
import { HiArrowNarrowRight } from 'react-icons/hi';
import me from '../assets/me.png';
import { Link } from "react-scroll"; 

const Home = () => {
  return (
    <div
    name="home"
    className="h-screen w-full bg-[#0a192f]"
  >
    <div className="max-w-screen-lg mx-auto flex flex-col items-center justify-center h-full px-4 md:flex-row">
      <div className="flex flex-col justify-center h-full">
        <h2 className="text-4xl sm:text-7xl font-bold text-white">
          I'm a Full Stack Web Developer
        </h2>
        <p className="text-gray-500 py-4 max-w-md">
           I have 4 years of experience in graphics design and web development.
          Currently, I love to work on web application using technologies like
          React, Tailwind, Next.js and Mongodb.
        </p>
        <div>
          <Link
            to="about"
            smooth
            duration={500}
            className="group text-white w-fit px-6 py-3 my-2 flex items-center rounded-md bg-gradient-to-r from-cyan-500 to-blue-500 cursor-pointer"
          >
            About Me
            <span className="group-hover:rotate-90 duration-300">
              <HiArrowNarrowRight size={25} className="ml-3" />
            </span>
          </Link>
        </div>
      </div>
      <div>
        <img
          src={me}  
          alt="my profile"
          className="rounded-2xl mx-auto w-2/3 md:w-full"
        />
      </div>
    </div>
  </div>
  );
};
export default Home;

In the browser, we get a result similar to the image below:

Rendered landing section

Adding an about section

To create the about section, add the following code to About.jsx:

import React from "react";

const About = () => { 
  return (
    <div
      name="about"
      id="about"
      className="w-full h-screen bg-[#0a192f] text-gray-300"
    >
      <div className="flex flex-col justify-center items-center w-full h-full">
        <div className=" py-16 rounded-md bg-cyan-800 flex flex-col justify-center items-center w-4/6">
          <div className="max-w-[1000px] w-full grid grid-cols-2 gap-8 mb-4">
            <div className="sm:text-right pb-8 pl-4">
              <p className="text-4xl font-bold inline border-b-4 border-cyan-500">
                About
              </p>
            </div>
            <div></div>
          </div>
          <div className="max-w-[1000px] w-full grid sm:grid-cols-2 gap-8 px-4">
            <div className="sm:text-right text-4xl font-bold">
              <p>
                Hi. I'm Taminoturoko Briggs, nice to meet you. Please take a
                look around.
              </p>
            </div>
            <div>
              <p>
                {" "}
                A software developer with experience in building Responsive and
                Scalable Web apps. I am well-knowledged in UI/UX principles and
                practices. In addition to software development, I am also a
                technical writer--simplifying topics/concepts on the web.
              </p>
            </div>
          </div>
        </div>
      </div>
    </div>
  );
};
export default About;

The above code will produce the following result in our about section:

Rendered about section

Adding a skills section

Here is a preview of what the skills section would look like:

Rendered skills section

The skills section will showcase a list of different technologies. We can create this section in Skills.jsx with the following code:

import React from 'react';

const Skills = () => {
  return (
    <div name='skills' className='w-full h-screen bg-[#0a192f] text-gray-300'>
      {/* Container */}
      <div className='max-w-[1000px] mx-auto p-4 flex flex-col justify-center w-full h-full'>
          <div className=' w-full flex justify-center items-center flex-col mb-7'>
              <p className='text-4xl font-bold inline border-b-4 border-cyan-500 text-center '>Skills</p>
              <p className='py-4 text-2xl'>I enjoy diving into and learning new things. Here's a list of technologies I've worked with</p>
          </div> 
          <div className='w-full grid grid-cols-2 sm:grid-cols-4 gap-4 text-center py-8'>
              <div className='shadow-md shadow-[#040c16] hover:scale-110 duration-500'>
                  <p className='my-4'>HTML</p>
              </div>
              <div className='shadow-md shadow-[#040c16] hover:scale-110 duration-500'>
                  <p className='my-4'>CSS</p>
              </div>
              <div className='shadow-md shadow-[#040c16] hover:scale-110 duration-500'>
                  <p className='my-4'>JAVASCRIPT</p>
              </div>
              <div className='shadow-md shadow-[#040c16] hover:scale-110 duration-500'>
                  <p className='my-4'>REACT</p>
              </div>
              <div className='shadow-md shadow-[#040c16] hover:scale-110 duration-500'>
                  <p className='my-4'>GITHUB</p>
              </div>
              <div className='shadow-md shadow-[#040c16] hover:scale-110 duration-500'>
                  <p className='my-4'>NODE JS</p>
              </div>
              <div className='shadow-md shadow-[#040c16] hover:scale-110 duration-500'>
                  <p className='my-4'>MONGO DB</p>
              </div>
              <div className='shadow-md shadow-[#040c16] hover:scale-110 duration-500'>
                  <p className='my-4'>AWS</p>
              </div>
              <div className='shadow-md shadow-[#040c16] hover:scale-110 duration-500'>
                  <p className='my-4'>Django</p>
              </div>
              <div className='shadow-md shadow-[#040c16] hover:scale-110 duration-500'>
                  <p className='my-4'>Sass</p>
              </div>
              <div className='shadow-md shadow-[#040c16] hover:scale-110 duration-500'>
                  <p className='my-4'>Mongodb</p>
              </div>
              <div className='shadow-md shadow-[#040c16] hover:scale-110 duration-500'>
                  <p className='my-4'>GraphQl</p>
              </div>
          </div>
      </div>
    </div>
  );
};
export default Skills;

Adding a works section

The works section will display a grid-like presentation of different projects. When a user hovers over any of the projects, it will display an overlay with additional info and buttons.

undefined

To recreate this, paste the following in Works.jsx:

import React from 'react';
import code from '../assets/code2.png';

const Works = () => {
  return (
    <div name='work' className='w-full md:h-screen text-gray-300 bg-[#0a192f]'>
      <div className='max-w-[1000px] mx-auto p-4 flex flex-col justify-center w-full h-full'>
        <div className='pb-8 w-full flex justify-center items-center flex-col'>
          <p className='text-4xl font-bold inline border-b-4 text-gray-300 border-cyan-500'>
            Work
          </p>
          <p className='py-6 text-2xl'>Check out some of my most recent work</p>
        </div>
{/* Container */}
        <div className='grid sm:grid-cols-2 md:grid-cols-3 gap-4'>
            {/* Grid Item */}
          <div
            style={{ backgroundImage: `url(${code})` }}
            className='shadow-lg shadow-[#040c16] group container rounded-md flex justify-center items-center mx-auto content-div'
          >
            {/* Hover Effects */}
            <div className='opacity-0 group-hover:opacity-100 flex justify-center items-center flex-col'>
              <span className=' text-lg font-bold text-white tracking-wider'>
                CBT Application
              </span>
              <p className='text-center'>A CBT web application built with React and Mongodb</p>
              <div className='pt-8 text-center'>
                <a href='/'>
                  <button className='text-center rounded-lg px-4 py-3 m-2 bg-white text-gray-700 font-bold text-lg'>
                    Demo
                  </button>
                </a>
                <a href='/'>
                  <button className='text-center rounded-lg px-4 py-3 m-2 bg-white text-gray-700 font-bold text-lg'>
                    Code
                  </button>
                </a>
              </div>
            </div>
          </div>
          <div
            style={{ backgroundImage: `url(${code})` }}
            className='shadow-lg shadow-[#040c16] group container rounded-md flex justify-center items-center mx-auto content-div'
          >
            {/* Hover Effects */}
            <div className='opacity-0 group-hover:opacity-100 flex justify-center items-center flex-col'>
              <span className=' text-lg font-bold text-white tracking-wider'>
                CBT Application
              </span>
              <p className='text-center'>A CBT web application built with React and Mongodb</p>
              <div className='pt-8 text-center'>
                <a href='/'>
                  <button className='text-center rounded-lg px-4 py-3 m-2 bg-white text-gray-700 font-bold text-lg'>
                    Demo
                  </button>
                </a>
                <a href='/'>
                  <button className='text-center rounded-lg px-4 py-3 m-2 bg-white text-gray-700 font-bold text-lg'>
                    Code
                  </button>
                </a>
              </div>
            </div>
          </div>
          <div
            style={{ backgroundImage: `url(${code})` }}
            className='shadow-lg shadow-[#040c16] group container rounded-md flex justify-center items-center mx-auto content-div'
          >
            {/* Hover Effects */}
            <div className='opacity-0 group-hover:opacity-100 flex justify-center items-center flex-col'>
              <span className=' text-lg font-bold text-white tracking-wider'>
                CBT Application
              </span>
              <p className='text-center'>A CBT web application built with React and Mongodb</p>
              <div className='pt-8 text-center'>
                <a href='/'>
                  <button className='text-center rounded-lg px-4 py-3 m-2 bg-white text-gray-700 font-bold text-lg'>
                    Demo
                  </button>
                </a>
                <a href='/'>
                  <button className='text-center rounded-lg px-4 py-3 m-2 bg-white text-gray-700 font-bold text-lg'>
                    Code
                  </button>
                </a>
              </div>
            </div>
          </div>
          <div
            style={{ backgroundImage: `url(${code})` }}
            className='shadow-lg shadow-[#040c16] group container rounded-md flex justify-center items-center mx-auto content-div'
          >
            {/* Hover Effects */}
            <div className='opacity-0 group-hover:opacity-100 flex justify-center items-center flex-col'>
              <span className=' text-lg font-bold text-white tracking-wider'>
                CBT Application
              </span>
              <p className='text-center'>A CBT web application built with React and Mongodb</p>
              <div className='pt-8 text-center'>
                <a href='/'>
                  <button className='text-center rounded-lg px-4 py-3 m-2 bg-white text-gray-700 font-bold text-lg'>
                    Demo
                  </button>
                </a>
                <a href='/'>
                  <button className='text-center rounded-lg px-4 py-3 m-2 bg-white text-gray-700 font-bold text-lg'>
                    Code
                  </button>
                </a>
              </div>
            </div>
          </div>
          <div
            style={{ backgroundImage: `url(${code})` }}
            className='shadow-lg shadow-[#040c16] group container rounded-md flex justify-center items-center mx-auto content-div'
          >
            {/* Hover Effects */}
            <div className='opacity-0 group-hover:opacity-100 flex justify-center items-center flex-col'>
              <span className=' text-lg font-bold text-white tracking-wider'>
                CBT Application
              </span>
              <p className='text-center'>A CBT web application built with React and Mongodb</p>
              <div className='pt-8 text-center'>
                <a href='/'>
                  <button className='text-center rounded-lg px-4 py-3 m-2 bg-white text-gray-700 font-bold text-lg'>
                    Demo
                  </button>
                </a>
                <a href='/'>
                  <button className='text-center rounded-lg px-4 py-3 m-2 bg-white text-gray-700 font-bold text-lg'>
                    Code
                  </button>
                </a>
              </div>
            </div>
          </div>
          <div
            style={{ backgroundImage: `url(${code})` }}
            className='shadow-lg shadow-[#040c16] group container rounded-md flex justify-center items-center mx-auto content-div'
          >
            {/* Hover Effects */}
            <div className='opacity-0 group-hover:opacity-100 flex justify-center items-center flex-col'>
              <span className=' text-lg font-bold text-white tracking-wider'>
                CBT Application
              </span>
              <p className='text-center'>A CBT web application built with React and Mongodb</p>
              <div className='pt-8 text-center'>
                <a href='/'>
                  <button className='text-center rounded-lg px-4 py-3 m-2 bg-white text-gray-700 font-bold text-lg'>
                    Demo
                  </button>
                </a>
                <a href='/'>
                  <button className='text-center rounded-lg px-4 py-3 m-2 bg-white text-gray-700 font-bold text-lg'>
                    Code
                  </button>
                </a>
              </div>
            </div>
          </div>
          
        </div>
      </div>
    </div>
  );
};
export default Works;

Contact section

Finally, we will add the contact section. To do this, add the following code to Contact.jsx:

import React from 'react'

const Contact = () => {
  return (
    <div name='contact' className='w-full h-screen bg-[#0a192f] flex justify-center items-center p-4'>
        <div className='flex flex-col max-w-[600px] w-full'>
            <div className='pb-8 flex flex-col justify-center w-full h-full items-center'>
                <p className='text-4xl font-bold inline border-b-4 border-cyan-500 text-gray-300'>Contact</p>
                <p className='text-gray-300 py-4'>Send me a message</p>
            </div>
            <input className='bg-[#ccd6f6] p-2' type="text" placeholder='Name' name='name' />
            <input className='my-4 p-2 bg-[#ccd6f6]' type="email" placeholder='Email' name='email' />
            <textarea className='bg-[#ccd6f6] p-2' name="message" rows="10" placeholder='Message'></textarea>
            <button className='text-white border-2 hover:bg-cyan-500 hover:border-cyan-500 px-4 py-3 my-8 mx-auto flex items-center'>Let's Collaborate</button>
        </div>
    </div>
  )
}
export default Contact

React banner CTA

Setting up our content in ButterCMS

We will set up our content for our portfolio site using ButterCMS, a headless CMS. To get started, navigate in your browser to the ButterCMS page and set up a new user account if you do not have a pre-existing one. After logging in, we are directed to the user dashboard. Here, we will set up a page which will contain all the data to be displayed on our portfolio site.

Creating a new page

The site sections will be divided into individual components that make up the page. Let’s go over how to set this up. From the left sidebar, click on the “Page Types” plus icon:

Create a new page type for the portfolio page.

On this new page, we will set up the content structure for each individual site section. From the side menu, select “Component Picker”:

Creating a component picker in ButterCMS for the portfolio page.

Creating the landing page section

Next, we enter a name for the page and create the first section. To do this, select “component picker” from the left sidebar. Name the component picker “my personal portfolio” and click on the “Create Component” button, then create the first component “Landing section”: 

Creating the landing page component in ButterCMS.

In the GIF above, we have structured the “Landing section” component to contain two text fields: one for the caption and one for the main text of our landing page.

Creating the about page component

To create the next section, click on the Create Component button at the bottom of the page to add another component. The about section will contain two fields: one for the caption and the other for the main text. This component will be our "About section".

Configuring the "About" component in ButterCMS.

Creating the skills and works section Components

The skills section will contain a text field and a list of different technologies. To achieve this, we will need an array-like structure. This can be achieved using a Repeater. A Repeater is a group of content fields which can be repeated on a web page. To create the skills component, add a long text field for the section header, and add a Repeater.  We will use the Repeater to create an array of content for the skills section. The Repeater will contain a short text field for entering our skill names. Add these two fields as shown in the GIF below:

Configuring the Skills component in ButterCMS.

With the Repeater, we will be able to add more than one skill when entering data for our page. Click on the “Create Component” button at the bottom of the page to create another component for our works section.

We will create the “Works” section similar to how the “Skills” section was created, i.e., we will have a “work header” long text field and will also be using a Repeater, as we will have multiple content fields using the same component structure. In the Repeater field, we will add a short text field for the work title, a long text field for the work description, an image field for the project image, and two fields for URLs: one for the demo link of the project and the other for the code link.

Configuring the "Works" component in ButterCMS.

From the GIF above, we see that each individual project defined using the Repeater will have a title, a description, an image, and two URLs for the project’s code and demo.

To save the page, click on the Create Page Type button at the top of the page, and then key in “portfolio” in the page-type-name field.

Creating our site content

With the page setup complete, we will add the content for our site to the page, then later fetch and display data from the CMS in our application. Select the created page, “portfolio”, from the page option on the left sidebar. We are prompted to enter a page title. This will be the title of the created page. Here, we can just enter “a portfolio site” into the field. To add new content, all we need to do is click on the plus icon, thereby accessing the component picker, and select what content structure we want to use from the list of components we defined for our page. Then, we add the required data into the field.

Component picker for the portfolio page in ButterCMS.

Let’s populate each section. For the landing section:

  • Landing caption: I'm a Full Stack Web Developer
  • Landing main text: I have 4 years of experience in graphic design and web development. Currently, I love to work on web applications using technologies like React, Tailwind, Next.js, and MongoDB.

Adding content to the landing page section in ButterCMS.

About section:

  • About caption: Hi. I'm Taminoturoko Briggs. Nice to meet you. Please take a look around.
  • About main details: A software developer with experience in building responsive and scalable web apps. I am well-knowledged in UI/UX principles and practices. In addition to software development, I am also a technical writer—simplifying topics/concepts on the web.

Adding content to the About section in ButterCMS.

Skills section:

  • Skill header: I enjoy diving into and learning new things. Here's a list of technologies I've worked with:
  • Skills: HTML, JavaScript, CSS 

Add content to the skills section in ButterCMS.

Works section:

  • Works header: Check out some of my most recent work
  • Works:
    • Work title: Web design
    • Work description: This is a web design for a portfolio site
    • Insert an image of choice in the work image section

Adding content to the works section in ButterCMS.

To save changes, click on the Save and Publish draft buttons at the top of the page.

Connecting our portfolio site to ButterCMS

To connect our application to ButterCMS, we will require the read token. This can be found in the settings tab of the user account. In App.js, we will set up a fetch request to return data from the CMS:

//...
import {React, useEffect, useState} from "react";
import axios from "axios";

function App() { 
  const readtoken = "your read token"
  const [data, setData] = useState([]);

  useEffect(() => {
    const getData = async () => {
      axios.get(`https://api.buttercms.com/v2/pages/portfolio/a-portfolio-site?auth_token=${readtoken}`).then(res => {
        setData(res.data.data.fields.my_personal_porfolio);
      }).catch(err => {
        console.log(err);
      })
    }
    getData();
  }, []);
  return (
    <div>
      <Navbar />
      <Home content={data[0]}/>
      <About content={data[1]}/>
      <Skills content={data[2]}/>
      <Works content={data[3]}/>
      <Contact />
    </div>
  );
}
//...

In the code block above, we set up a fetch request which returns data from the CMS. When the request is executed, we set the value of the state data to be the returned value. We also passed the corresponding data elements as props to each of the individual components. We can set up the Home component to use the CMS content:

const Home = ({content}) => {
//....
 <h2 className="text-4xl sm:text-7xl font-bold text-white">
    {content?.fields.landing_caption}
  </h2>
  <p className="text-gray-500 py-4 max-w-md">
    {content?.fields.landing_main_text}
  </p>

in About.jsx:

const About = ({content}) => {
//...
<div className="max-w-[1000px] w-full grid sm:grid-cols-2 gap-8 px-4">
<div className="sm:text-right text-4xl font-bold">
  <p>
   {content?.fields.about_caption}
  </p>
</div>
<div>
  <p>
    {" "}
    {content?.fields.about_main_details}
  </p>
</div>
</div>

In Skills.jsx:

const Skills = ({content}) => {
//....
<p className="text-4xl font-bold inline border-b-4 border-cyan-500 text-center ">
      Skills
    </p>
    <p className="py-4 text-2xl">{content?.fields.skill_header}</p>
  </div>
  <div className="w-full grid grid-cols-2 sm:grid-cols-4 gap-4 text-center py-8">
    {content?.fields.skills.map((skill, index) => {
      return (
        <div className="shadow-md shadow-[#040c16] hover:scale-110 duration-500" key={index}>
          <p className="my-4">{skill.terminology_learnt}</p>
        </div>
      );
    })}
  </div>

Finally, in Works.jsx, we have the following:

const Works = ({content}) => {
//....
<p className="text-4xl font-bold inline border-b-4 text-gray-300 border-cyan-500">
    Works
  </p>
  <p className="py-6 text-2xl">{content?.fields.work_header}</p>
</div>
{/* Container */}
<div className="grid sm:grid-cols-2 md:grid-cols-3 gap-4">
  {/* Grid Item */}
  {content?.fields.works.map((work, index) => {
    return (
      <div
        style={{ backgroundImage: `url(${work.work_image})` }}
        className="shadow-lg shadow-[#040c16] group container rounded-md flex justify-center items-center mx-auto content-div"
        key={index}
      >
        {/* Hover Effects */}
        <div className="opacity-0 group-hover:opacity-100 flex justify-center items-center flex-col">
          <span className=" text-lg font-bold text-white tracking-wider">
            {work.work_title}
          </span>
          <p className="text-center">
            {work.work_description}
          </p>
          <div className="pt-8 text-center">
            <a href={work.github_url}>
              <button className="text-center rounded-lg px-4 py-3 m-2 bg-white text-gray-700 font-bold text-lg">
                Demo
              </button>
            </a>
            <a href={work.demo_url}>
              <button className="text-center rounded-lg px-4 py-3 m-2 bg-white text-gray-700 font-bold text-lg">
                Code
              </button>
            </a>
          </div>
        </div>
      </div>
    );
  })}
</div>

Now if we view our application in the browser, we have a result similar to the GIF below:

Tutorial results: A rendered portfolio website.

From the GIF, we can see that the content displayed on our portfolio page matches the content of our CMS.

Examples of portfolio websites built with React

For more inspiration on creating a portfolio that stands out, check out the links below:

Following the above links, we will be taken to a page where we will find the GitHub repo link to the portfolio. Clone the repo and start the app to view it. 

Upon viewing the above portfolios, we will notice how concise they are—not going overboard on every single detail. This is an important aspect to consider while adding information to your portfolio. Just showcasing the relevant details is ok. We will also notice that they are responsive. Although the portfolio in this tutorial isn't responsive, note that as with making our portfolio concise, responsiveness is equally important.

Also, to make our portfolio stand out to recruiters and clients, we can add some creative and cool animations to it. The following libraries can help us to easily do this: Framer Motion, tsParticles, and Three.js.

Closing thoughts

And there you have it! We have come to the end of this tutorial. In this tutorial, we built a portfolio site, set up a content structure for it on the headless ButterCMS, and connected it to our application. Using a CMS to manage site content makes it easier to update, view, and manage the application’s data.

Make sure you receive the freshest React tutorials and Butter product updates.
    
Taminoturoko Briggs

Taminoturoko Briggs is a software developer and technical writer with sound knowledge of different web technologies. His core languages include JavaScript and Python.

ButterCMS is the #1 rated Headless CMS

Related articles

Don’t miss a single post

Get our latest articles, stay updated!