Building a portfolio website doesn't have to be time-consuming. With ElmapiCMS as your content backend and Cursor as your AI coding assistant, you can create a professional portfolio site in hours instead of days.
This tutorial will walk you through building a complete portfolio website from scratch, using Cursor to generate both the ElmapiCMS project template and the Next.js frontend code.
Table of Contents
- What We're Building
- Prerequisites
- Step 1: Design the Content Structure with Cursor
- Step 2: Load the Template into ElmapiCMS
- Step 3: Set Up the Next.js Project
- Step 4: Install ElmapiCMS SDK
- Step 5: Create the API Client
- Step 6: Build the Home Page
- Step 7: Create Project Detail Pages
- Step 8: Add Skills and Contact Sections
- Step 9: Style and Polish
- Step 10: Deploy
- Tips for Working with Cursor
- Common Patterns
- Conclusion
- Next Steps
What We're Building
We'll create a portfolio website with:
- About Section - Personal introduction and skills
- Projects Gallery - Showcase of your work with images and descriptions
- Experience Timeline - Work history and achievements
- Contact Form - Way for visitors to reach out
- Blog Section (optional) - Share your thoughts and updates
All content will be managed through ElmapiCMS, and the frontend will be built with Next.js 16 using the App Router.
Prerequisites
Before we start, make sure you have:
- ElmapiCMS installed and running (local or hosted)
- Cursor installed and configured
- Node.js 20+ and npm installed (required for Next.js 16)
- Basic knowledge of React and Next.js
Step 1: Design the Content Structure with Cursor
First, we need to design our ElmapiCMS collections. To do this effectively, you need to run Cursor in your ElmapiCMS installation directory where the existing templates are located.
Setting Up Cursor
-
Open Cursor in your ElmapiCMS directory:
cd /path/to/your/elmapi-installation cursor . -
Open an existing template for reference:
- Navigate to
resources/data/project_templates/landing-page-nextjs.json - Open this file in Cursor so it can see the template structure
- Navigate to
Prompt for Cursor
With the existing template file open, use Cursor's chat feature and say:
"I have an existing ElmapiCMS project template open (landing-page-nextjs.json). I'm building a portfolio website and need a new template. Design a complete project template JSON structure for a professional portfolio website. The template should include:
- An About section (singleton collection) for personal information, bio, skills, and resume
- A Projects collection (multi-entry) to showcase portfolio projects with images, descriptions, technologies used, and links
- An Experience collection (multi-entry) for work history and professional experience
- A Contact settings collection (singleton) for contact information and social media links
ElmapiCMS supports these field types: text, longtext, richtext, slug, email, password, number, enumeration, boolean, color, date, time, media, relation, json, and group. Use appropriate field types for each field. Include demo data for all collections. Follow the same structure and field conventions as the existing template."
If Cursor needs more information about field types, you can also reference the ElmapiCMS documentation:
"You can also reference the ElmapiCMS documentation at https://elmapicms.com/documentation/ to understand field types and structure better."
Cursor will analyze the existing template structure and generate a complete portfolio template with all appropriate fields, following the same patterns.
Save the Template
- Create a new file:
resources/data/project_templates/portfolio.json - Paste the generated template structure
- Review and adjust if needed
Step 2: Load the Template into ElmapiCMS
Once you have the template file, load it into your ElmapiCMS installation:
cd /path/to/elmapi
php artisan db:seed --class=ProjectTemplateSeeder
Then create a new project in ElmapiCMS:
- Go to your ElmapiCMS dashboard
- Click "Create project"
- Select "Chose from a template" from the project type dropdown
- Select "Portfolio" from the template dropdown
- Check "Include demo content" to see sample data
- Create the project
Step 3: Set Up the Next.js Project
Now let's create the Next.js frontend. In Cursor, open a terminal or use the chat:
Prompt: "Create a new Next.js 16 project with TypeScript, Tailwind CSS, and the App Router."
Or manually run:
npx create-next-app@latest portfolio-website --typescript --tailwind --app
cd portfolio-website
This will create a Next.js 16 project with the latest features and improvements.
Step 4: Install ElmapiCMS SDK
Install the ElmapiCMS JavaScript SDK:
Prompt in Cursor: "Show me how to install and configure @elmapicms/js-sdk in this Next.js project. Include environment variables setup."
Or manually:
npm install @elmapicms/js-sdk
Create a .env.local file:
ELMAPI_API_URL=https://your-instance.elmapi.com/api
ELMAPI_API_KEY=your-api-key
ELMAPI_PROJECT_ID=your-project-uuid
Step 5: Create the API Client
Use Cursor to generate a reusable API client:
Prompt: "Create a TypeScript API client utility for ElmapiCMS using @elmapicms/js-sdk. Include:
- Client initialization with environment variables
- Type-safe functions for fetching about section, projects, experience, and contact settings
- Error handling
- Export the client and all fetch functions"
Cursor will generate something like:
// lib/elmapi.ts
import { createClient } from '@elmapicms/js-sdk';
const client = createClient(
process.env.ELMAPI_API_URL!,
process.env.ELMAPI_API_KEY!,
process.env.ELMAPI_PROJECT_ID!
);
export async function getAbout() {
try {
const about = await client.getEntry('about');
return about;
} catch (error) {
console.error('Error fetching about:', error);
return null;
}
}
export async function getProjects() {
try {
const projects = await client.getEntries('projects', {
state: 'published',
sort: { completion_date: 'desc' }
});
return projects;
} catch (error) {
console.error('Error fetching projects:', error);
return [];
}
}
export async function getExperience() {
try {
const experience = await client.getEntries('experience', {
state: 'published',
sort: { start_date: 'desc' }
});
return experience;
} catch (error) {
console.error('Error fetching experience:', error);
return [];
}
}
export async function getContact() {
try {
const contact = await client.getEntry('contact-settings');
return contact;
} catch (error) {
console.error('Error fetching contact:', error);
return null;
}
}
Step 6: Build the Home Page
Now let's create the main portfolio page. Use Cursor to generate the layout:
Prompt: "Create a Next.js page component for a portfolio website. Include:
- Hero section with name, title, and profile image from the about collection
- Featured projects grid (3-4 projects)
- Skills section
- Recent experience (2-3 items)
- Use Tailwind CSS for styling
- Make it responsive and modern
- Fetch data from the ElmapiCMS API using the client functions"
Cursor will generate a complete page component. Here's a simplified example:
// app/page.tsx
import { getAbout, getProjects, getExperience } from '@/lib/elmapi';
import Image from 'next/image';
import Link from 'next/link';
export default async function Home() {
const about = await getAbout();
const projects = await getProjects();
const experience = await getExperience();
const featuredProjects = projects
.filter((p: any) => p.fields?.featured)
.slice(0, 4);
return (
<main className="min-h-screen">
{/* Hero Section */}
<section className="container mx-auto px-4 py-20">
<div className="flex flex-col md:flex-row items-center gap-8">
{about?.fields?.profile_image && (
<Image
src={about.fields.profile_image}
alt={about.fields.name}
width={200}
height={200}
className="rounded-full"
/>
)}
<div>
<h1 className="text-4xl font-bold mb-2">
{about?.fields?.name || 'Your Name'}
</h1>
<p className="text-xl text-gray-600 mb-4">
{about?.fields?.title || 'Your Title'}
</p>
<div
className="prose"
dangerouslySetInnerHTML={{
__html: about?.fields?.bio || ''
}}
/>
</div>
</div>
</section>
{/* Featured Projects */}
<section className="container mx-auto px-4 py-20">
<h2 className="text-3xl font-bold mb-8">Featured Projects</h2>
<div className="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-3 gap-6">
{featuredProjects.map((project: any) => (
<Link
key={project.uuid}
href={`/projects/${project.fields?.slug || project.uuid}`}
className="group"
>
<div className="border rounded-lg overflow-hidden hover:shadow-lg transition">
{project.fields?.featured_image && (
<Image
src={project.fields.featured_image}
alt={project.fields.title}
width={400}
height={250}
className="w-full h-48 object-cover"
/>
)}
<div className="p-4">
<h3 className="text-xl font-semibold mb-2">
{project.fields?.title}
</h3>
<p className="text-gray-600 line-clamp-2">
{project.fields?.description}
</p>
</div>
</div>
</Link>
))}
</div>
</section>
{/* Experience */}
<section className="container mx-auto px-4 py-20">
<h2 className="text-3xl font-bold mb-8">Experience</h2>
<div className="space-y-6">
{experience.slice(0, 3).map((exp: any) => (
<div key={exp.uuid} className="border-l-4 border-blue-500 pl-4">
<h3 className="text-xl font-semibold">{exp.fields?.role}</h3>
<p className="text-gray-600">{exp.fields?.company}</p>
<div
className="prose mt-2"
dangerouslySetInnerHTML={{
__html: exp.fields?.description || ''
}}
/>
</div>
))}
</div>
</section>
</main>
);
}
Step 7: Create Project Detail Pages
Generate dynamic project pages:
Prompt: "Create a dynamic Next.js route for individual project pages at /projects/[slug]. Include:
- Project title, description, and images
- Technology stack
- Links to live site and GitHub
- Image gallery
- Use generateStaticParams for static generation
- Fetch project data from ElmapiCMS"
Step 8: Add Skills and Contact Sections
Use Cursor to generate additional components:
Prompt: "Create a skills section component that displays skills from the about collection as badges or tags. Style it with Tailwind CSS."
Prompt: "Create a contact section component that displays contact information and social links from the contact settings. Include a contact form."
Step 9: Style and Polish
Ask Cursor to help with styling:
Prompt: "Improve the design of this portfolio website. Add:
- Better typography
- Smooth animations
- Dark mode support
- Better mobile responsiveness
- Loading states"
Step 10: Deploy
Once everything is working locally, deploy your portfolio:
Prompt: "Show me how to deploy this Next.js portfolio to Vercel. Include environment variable setup."
Or follow Vercel's deployment guide:
- Push your code to GitHub
- Import the project in Vercel
- Add your environment variables
- Deploy
Tips for Working with Cursor
Iterative Development
Don't try to generate everything at once. Break it down:
- First iteration: Basic structure and data fetching
- Second iteration: Styling and layout
- Third iteration: Animations and polish
Use Context
Open relevant files when asking Cursor to generate code. For example:
- Open
lib/elmapi.tswhen asking for API functions - Open existing components when asking for similar ones
- Open the template JSON when asking about data structure
Refine and Iterate
Use follow-up prompts to refine:
"Make the project cards more visually appealing with hover effects."
"Add a filter by technology feature to the projects page."
"Improve the mobile layout for the hero section."
Common Patterns
Fetching Singleton Collections
const about = await client.getEntry('about');
Fetching Multi-Entry Collections
const projects = await client.getEntries('projects', {
state: 'published',
sort: { completion_date: 'desc' },
limit: 10
});
Handling Rich Text
<div
dangerouslySetInnerHTML={{ __html: entry.fields.description }}
/>
Working with Media
<Image
src={entry.fields.featured_image}
alt={entry.fields.title}
width={800}
height={600}
/>
Conclusion
Building a portfolio with ElmapiCMS and Cursor is a powerful combination. You can:
- Design your content structure with AI assistance
- Generate template files automatically
- Build frontend components quickly
- Iterate and refine with AI help
The key is breaking the work into small, focused prompts and iterating on the results. Start with the structure, then build out features one at a time.
Your portfolio is now ready to showcase your work, and you can easily update content through ElmapiCMS without touching code.
Next Steps
- Customize the design to match your brand
- Add more sections (testimonials, certifications, etc.)
- Set up a blog section
- Add analytics and SEO optimization
- Deploy and share your portfolio!
Happy building with ElmapiCMS and Cursor!