How to turn the JekCMS Personal theme into a developer portfolio with project showcase, skills timeline, dark mode, and a 96 PageSpeed score.
Why Build a Portfolio with a CMS
Static site generators are the default choice for developer portfolios, and they work fine if you never update your site. The reality is different. You finish a project, want to add it to your portfolio, and suddenly you're dealing with build pipelines, deployment scripts, and waiting for CI/CD to finish. With JekCMS, you log into the admin, add your project, upload screenshots, and it's live in 30 seconds.
We built the Personal theme specifically for portfolios and personal sites. It ships with project showcase, skills display, timeline, contact form, and dark mode out of the box. Here's how to set it up from scratch.
Theme Configuration
After installing JekCMS with the Personal theme, the first step is configuring your identity in Admin > Settings:
- Site Name: Your full name
- Tagline: Your title (e.g., "Full-Stack Developer & Open Source Contributor")
- Logo: Upload a professional headshot or monogram SVG
- Social Links: GitHub, LinkedIn, Twitter/X, personal email
The theme reads these from the site_settings table and displays them in the header, footer, and contact section automatically. No hardcoding.
Project Showcase
Projects are stored in the services table (repurposed for portfolio items). Each project has:
// Project fields in admin
Title: "JekCMS - Content Management System"
Description: "A lightweight PHP CMS built for speed and simplicity"
Category: "Open Source" // Maps to service category
Image: Screenshot or logo (AVIF/WebP auto-generated)
URL: "https://github.com/jekcms/jekcms"
Sort Order: 1 // Controls display order
The project grid uses CSS Grid with auto-fill and minmax(320px, 1fr) so it adapts from 1 column on mobile to 3 columns on desktop without media queries:
.projects-grid {
display: grid;
grid-template-columns: repeat(auto-fill, minmax(320px, 1fr));
gap: 2rem;
}
.project-card {
background: var(--card-bg);
border: 1px solid var(--border-color);
overflow: hidden;
transition: transform 0.2s ease;
}
.project-card:hover {
transform: translateY(-4px);
}
.project-card img {
width: 100%;
aspect-ratio: 16 / 10;
object-fit: cover;
}
Each card links to the project URL. If the URL is a GitHub repository, we display the star count fetched from the GitHub API (cached for 1 hour to avoid rate limits).
Skills Section
Skills are displayed as a categorized list rather than percentage bars (which are meaningless — what does "85% JavaScript" even mean?). We use a tag-cloud style layout grouped by category:
<div class="skills-section">
<div class="skill-group">
<h3>Languages</h3>
<div class="skill-tags">
<span class="skill-tag">PHP</span>
<span class="skill-tag">JavaScript</span>
<span class="skill-tag">TypeScript</span>
<span class="skill-tag">SQL</span>
</div>
</div>
<div class="skill-group">
<h3>Frameworks</h3>
<div class="skill-tags">
<span class="skill-tag">JekCMS</span>
<span class="skill-tag">React</span>
<span class="skill-tag">Node.js</span>
</div>
</div>
</div>
Skills are editable from Admin > Pages using the content editor. No database table needed — they're part of the About page content.
Timeline / Experience
The experience timeline uses the team table repurposed as timeline entries. Each entry has a title (job title), description (company + responsibilities), and a date field. The CSS creates a vertical line with alternating left/right cards:
.timeline {
position: relative;
padding: 2rem 0;
}
.timeline::before {
content: '';
position: absolute;
left: 50%;
top: 0;
bottom: 0;
width: 2px;
background: var(--border-color);
}
.timeline-item {
width: 45%;
padding: 1.5rem;
background: var(--card-bg);
border: 1px solid var(--border-color);
}
.timeline-item:nth-child(odd) { margin-left: 0; }
.timeline-item:nth-child(even) { margin-left: 55%; }
@media (max-width: 768px) {
.timeline::before { left: 1rem; }
.timeline-item { width: calc(100% - 3rem); margin-left: 3rem !important; }
}
Dark Mode
The Personal theme ships with dark mode enabled by default. The toggle uses CSS custom properties and localStorage for persistence:
:root {
--bg: #ffffff;
--text: #1a1a1a;
--card-bg: #f8f9fa;
--border-color: #e0e0e0;
}
[data-theme="dark"] {
--bg: #0d1117;
--text: #e6edf3;
--card-bg: #161b22;
--border-color: #30363d;
}
// Toggle script
const toggle = document.querySelector('.theme-toggle');
toggle.addEventListener('click', () => {
const current = document.documentElement.getAttribute('data-theme');
const next = current === 'dark' ? 'light' : 'dark';
document.documentElement.setAttribute('data-theme', next);
localStorage.setItem('theme', next);
});
The theme check runs before the page renders (in <head>) to prevent a flash of the wrong theme.
Contact Form
The contact form uses the standard JekCMS message system with CSRF protection and rate limiting. Messages go to Admin > Messages and optionally trigger an email notification.
PageSpeed Results
After optimization, the portfolio site scores: Mobile 96, Desktop 99. Key factors: AVIF images, proper lazy loading, minimal CSS (8KB total), zero JavaScript frameworks, and server-side rendering. Total page weight: 180KB including all images.
The Personal theme proves you don't need React, Next.js, or any JavaScript framework to build a fast, professional portfolio. PHP + clean CSS + proper image handling delivers better performance than most Gatsby or Hugo sites because there's no client-side JavaScript bundle to parse.