
Technologies Used
Project Overview
This portfolio website showcases a cutting-edge headless WordPress architecture where WordPress serves as a powerful CMS backend while Next.js 16 handles server-side rendering and static generation on the frontend. The result is a blazing-fast, SEO-optimized portfolio with the content editing flexibility of WordPress.
Architecture Highlights
Headless WordPress + Next.js
The architecture separates concerns cleanly: WordPress manages content through its familiar admin interface and Gutenberg editor, while Next.js consumes the REST API to render optimized pages with Incremental Static Regeneration (ISR).
// Fetching blocks from WordPress REST API
export async function getBlocksBySlug(slug: string) {
const response = await fetch(
`${WP_API_URL}/portfolio/v1/blocks/page/${slug}`,
{ next: { revalidate: 60 } }
);
return response.json();
}
Matching Gutenberg Editor Visual Experience
A common challenge with headless WordPress is the disconnect between what content editors see in the Gutenberg editor and what appears on the live frontend. I solved this by creating a shared component architecture where the exact same React components render in both environments.
Each custom Gutenberg block has a corresponding UI component that lives in a shared directory. The WordPress block imports this component for the editor preview, while the Next.js frontend imports the same component for rendering. This ensures pixel-perfect consistency—what you see in WordPress is exactly what you get on the live site.
// Shared UI component: HeroBlockUI.jsx
// Used by BOTH WordPress editor AND Next.js frontend
export default function HeroBlockUI({
heading,
profileImage,
socialLinks,
styles,
getClass = (name) => styles?.[name] || name,
}) {
return (
<section className={getClass('hero')}>
<div className={getClass('profileImageWrapper')}>
{profileImage}
</div>
<h1 className={getClass('heading')}>{heading}</h1>
<div className={getClass('socialLinks')}>
{socialLinks?.map(link => (
<a key={link.platform} href={link.url}>{link.label}</a>
))}
</div>
</section>
);
}
The getClass helper function handles the styling differences between environments. In WordPress, it maps to editor stylesheets, while in Next.js it maps to CSS modules. The markup stays identical, only the style binding changes:
// WordPress Block (edit.js)
import HeroBlockUI from '../shared/HeroBlockUI';
import './editor.scss';
export default function Edit({ attributes }) {
return (
<HeroBlockUI
{...attributes}
styles={null} // Uses plain class names
getClass={(name) => name} // editor.scss defines .hero, .heading, etc.
/>
);
}
// Next.js Component (HeroBlock.tsx)
import HeroBlockUI from './shared/HeroBlockUI';
import styles from './HeroBlock.module.scss';
export default function HeroBlock({ attributes }) {
return (
<HeroBlockUI
{...attributes}
styles={styles} // CSS modules with hashed class names
/>
);
}
To keep styles in sync, I created a custom build script that compiles shared SCSS files for both environments. The same variables, mixins, and design tokens flow into both the WordPress editor stylesheets and the Next.js CSS modules.
// scripts/build-wp-styles.js
// Compiles shared SCSS to WordPress plugin directory
const sharedStyles = glob.sync('app/components/blocks/*.shared.scss');
sharedStyles.forEach(file => {
const compiled = sass.compile(file);
// Output to WordPress: editor.scss and style.scss
fs.writeFileSync(wpBlockPath + '/editor.scss', compiled.css);
fs.writeFileSync(wpBlockPath + '/style.scss', compiled.css);
});
Custom Build Pipeline
I created custom npm scripts that orchestrate builds across both the Next.js frontend and WordPress plugin simultaneously. The shared SCSS files are compiled for both environments, and React components are bundled for WordPress using wp-scripts.
// package.json - Unified build commands
{
"scripts": {
"build": "npm run build:wp-styles && next build",
"build:wp-styles": "node scripts/build-wp-styles.js",
"build:blocks": "cd ../portfolio-wordpress && npm run build"
}
}
Performance Optimization
Lighthouse Results
Through careful optimization, the site achieves excellent Core Web Vitals scores:
- Performance: ~100% on mobile
- Accessibility: 100%
- Best Practices: 100%
- SEO: 100%

Image Optimization
All images are automatically converted to AVIF/WebP formats with responsive sizing using Next.js Image component:
// next.config.ts - Image optimization
images: {
formats: ['image/avif', 'image/webp'],
remotePatterns: [
{ hostname: 'cms.firascodes.ca' }
],
}
Dynamic OG Images
Open Graph and Twitter card images are generated dynamically at the edge using Next.js ImageResponse API, ensuring brand consistency without manual image creation:
// app/opengraph-image.tsx
export default async function Image() {
return new ImageResponse(
<div style={{ background: '#0a0a0a', color: '#D4AF37' }}>
<div style={{ fontSize: 120 }}>FC.</div>
<div style={{ fontSize: 56 }}>Firas Codes</div>
</div>
);
}
CI/CD Pipeline
Automated Deployments
The project uses a streamlined CI/CD workflow:
- Git Push: Triggers automatic Vercel deployment
- On-Demand ISR: WordPress content changes trigger targeted revalidation via webhook
- Preview Mode: Editors can preview unpublished content on the live frontend
// WordPress hook for on-demand revalidation
add_action('save_post', function($post_id) {
$frontend_url = get_option('headless_frontend_url');
wp_remote_post($frontend_url . '/api/revalidate', [
'body' => json_encode([
'type' => get_post_type($post_id),
'slug' => get_post_field('post_name', $post_id)
])
]);
});
Real-Time Preview
Content editors can preview changes in real-time on the Next.js frontend before publishing, with URL-based preview mode that fetches draft content directly from WordPress.
Key Features
- Custom Gutenberg blocks (Hero, Skills, Experience, Projects, Contact, CTA)
- Shared React components ensuring editor-to-frontend visual parity
- Unified SCSS build pipeline for consistent styling
- Dynamic JSON-LD structured data for SEO
- Contact form with WordPress REST API backend
- Global settings management from WordPress admin
- Responsive design with CSS modules and SCSS
- Accessibility-first approach (WCAG compliant)
- PWA-ready with manifest and optimized icons