Nasser Setti

Building My Portfolio with Payload CMS

Published 2 days ago
4 min read
Building My Portfolio with Payload CMS
#PayloadCMS
#Next.js
#Portfolio

Discover how I built my developer portfolio using Payload CMS and Next.js. This post walks through setting up content models, implementing dynamic routing, and optimizing for SEO—perfect for developers looking to create a modern, maintainable portfolio site with a headless CMS.

Introduction

I recently rebuilt my portfolio using Payload CMS, a powerful headless CMS built with TypeScript and MongoDB. In this post, I'll share my journey and some key code snippets that made it possible.

Setting Up the Project

I started with Payload's Next.js template and configured it to work with PostgreSQL:
import { postgresAdapter } from "@payloadcms/db-postgres";
import path from 'path'
import { buildConfig } from 'payload'
import { fileURLToPath } from 'url'
export default buildConfig({
admin: {
user: Users.slug,
},
collections: [Users, Media, Skills, Blogs, Experiences, Projects],

Defining Content Models

The heart of any CMS is its content structure. I created several collections to organize my portfolio data:
export const Projects: CollectionConfig = {
slug: "projects",
admin: {
useAsTitle: "title",
},
fields: [
{
name: "title",
type: "text",
required: true,

Rich Text Editing

One of the most powerful features of Payload is its rich text editor. I extended it with custom blocks for code snippets:
{
name: "content",
type: "richText",
editor: lexicalEditor({
features: ({ defaultFeatures }) => [
...defaultFeatures,
SlateToLexicalFeature({}),
BlocksFeature({
blocks: [
{

Fetching Data in Next.js

I created utility functions to fetch data from Payload:
import config from '@payload-config'
import { getPayload } from 'payload'
export const getBlogs = async (limit?: number) => {
const payload = await getPayload({ config })
const data = await payload.find({
collection: 'blogs',
where: {
published: {

Building the Frontend

With the CMS set up, I created React components to display my content.

Creating Dynamic Blog Pages

For blog posts, I implemented dynamic routing:
export default async function BlogDetailPage({
params,
}: PageProps) {
const slug = (await params).slug;
const blogResponse = await getBlogBySlug(slug);
if (!blogResponse) {
notFound();
}

Rendering Rich Text Content

To display the rich text content, I created specialized components:
case "block":
switch (node.fields?.blockType) {
case "Code":
return (
<div key={i} className="relative group my-8">
<div className="absolute -top-5 right-2 bg-secondary/80 text-xs px-2 py-1 rounded-t-md font-mono">
{node.fields?.language || "javascript"}
</div>
<Highlight
key={i}

Deployment

For deployment, I used Docker to containerize the application:
version: '3'
services:
payload:
image: node:18-alpine
ports:
- '3000:3000'
volumes:
- .:/home/node/app
- node_modules:/home/node/app/node_modules

Conclusion

Building my portfolio with Payload CMS has been a rewarding experience. The combination of a powerful headless CMS with Next.js allowed me to create a fast, dynamic, and maintainable portfolio site. The TypeScript integration and rich text editing capabilities made content management a breeze.
The modular approach to collections allowed me to easily extend my portfolio with new sections like blogs, projects, and experiences without having to rewrite any core functionality.
If you're considering building your own portfolio, I highly recommend giving Payload CMS a try!

Article Details

Published:2 days ago
Updated:2 days ago
Reading Time:4 min

Share This Article