
Introduction
Creating a portfolio website is an exciting project, particularly when trying for showing your professionalism in IT industry. This blog article details the creation of my portfolio utilizing Next.js for the frontend and Strapi as the backend CMS. I will discuss the challenges I encountered and the lessons I learnt throughout the process. A significant difficulties was getting that images from my backend were shown correctly on the frontend, a common difficulty for many newbie developers.
Project Overview
This Portfolio is a responsive web app that displays my cybersecurity projects, blog posts, skills, certifications, and experiences. Here's the main tech stack I used:
Frontend:
Next.js 14.2.10
React 18 (dependencies)
Tailwind CSS 3.4.1
Backend:
Strapi CMS
Node.js
Database:
Postgres
Media Storage:
Amazon S3 (for image and file upload)
Additional libraries:
Axios (for API requests),
DOMPurify (for sanitizing HTML),
Lucide React (for icons),
React Syntax Highlighter (for code blocks)
Deployment:
Vercel (Frontend)
Heroku (Backend)
The website has a dynamic project showcase, a blog system with pagination, a skills section, and a responsive design with dark mode. I used Strapi as a headless CMS to manage content, which made it easy to add new projects or blog posts whenever I wanted.
Key Features of this Portfolio
Dynamic project showcase with detailed pages
Blog posts with search, and sorting options
Responsive design for any device
Syntax highlighting for code snippets
Image optimization using the Next.js Image component
Project Structure and Technologies
The project is organized into two main directories:
Frontend (Next.js)
: This includes all the app components, pages, and utilities.
Backend (Strapi)
: This contains the API endpoints, configuration, and database setup.
Deployment Workflow Diagram
Below is a diagram that shows the deployment workflow of this website, explaining how the backend, frontend, and media storage connect.

The Image Display Challenge
Issue:
One of the main challenges I faced was displaying images from the Strapi CMS on the frontend. The image URLs returned from Strapi were relative URLs, meaning they didn't include the full domain. This caused the images not to load properly on my frontend hosted with Vercel.
Solution:
After doing some research and troubleshooting, I found a solution that worked. I had to prepend the Strapi URL to the image paths before displaying them. I used a utility function to format the image URLs by adding the base URL of my Strapi instance:
1// Original code
2const imageUrl = media.data[0].attributes.url; // Get URL of the first media object
3const imageAlt = media.data[0].attributes.alternativeText;
4
5// Updated code with Strapi URL prepended
6const strapiUrl = process.env.NEXT_PUBLIC_STRAPI_API_URL || 'http://localhost:1337';
7const imageUrl = `${strapiUrl}${media.data[0].attributes.url}`; // Get full URL of the first media object
8const imageAlt = media.data[0].attributes.alternativeText;This made sure that each image request included the full URL, so the images displayed correctly on the frontend. This solution was inspired by a discussion I found on the Strapi forum, where other developers had similar issues.
Content Security Policy Challenges
Issue:
Another problem I faced was Content Security Policy (CSP) violations when embedding YouTube videos, loading external fonts, fetching files from the backend, or using AWS S3 buckets. These errors occurred because my CSP settings were too restrictive and certain sources were not allowed.
Solution:
To solve this, I adjusted the CSP in my Strapi middleware file to explicitly allow these external resources. By updating the CSP directives, I allowed YouTube embeds (frame-src), Google Fonts (style-src and font-src), and media from AWS S3. This fixed the CSP violations and allowed these resources to load correctly.
Example API Calls
Here are examples of how the API calls are structured for different content types:
Projects
1const fetchProjects = async (): Promise<ProjectData[]> => {
2 return apiCall<ProjectData[]>({
3 method: 'GET',
4 url: '/api/projects',
5 params: { populate: '*' },
6 });
7};Explanation of ?populate=*
The ?populate=* parameter is a query parameter used in Strapi API calls to include all related data in the response. This is useful when you need complete data, including related fields, in a single API call.
Lessons Learned
API Integration
: When working with a headless CMS like Strapi, you need to make sure your data is formatted correctly before sending it to the frontend. This often means handling relationships between content types and optimizing image URLs.
Environment Variables
: Using environment variables in both development and production is important to keep sensitive information, like API URLs and database credentials, safe and easy to change.
Deployment Considerations
: Working with two deployment platforms (Heroku for the backend and Vercel for the frontend) taught me a lot about hosting a full-stack app. Understanding how to set up environment variables and configure CORS policies is really important.
Image Handling
: Optimizing images isn't just about resizing them. Handling image paths and making sure they are fetched correctly from the backend to the frontend can be challenging, but tools like the Next.js Image component make it easier.
Building a Portfolio website was a rewarding experience that tested my skills in both frontend and backend development. From setting up Strapi as the CMS to deploying on multiple platforms, I learned a lot about creating and maintaining a full-stack app. Solving the challenges with displaying images on the frontend taught me how important it is to manage URLs and optimize content delivery.
Tags
Author

Bereket Takiso
Share
Article Info
Related Articles
Continue exploring cybersecurity insights


