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!