A full-stack artist portfolio platform with a custom-built headless CMS. Built so a working artist never has to touch code again.
Live Demo Β· Admin Demo Β· Report Bug Β· Request Feature
Sheffex Arts is a production-ready portfolio and booking platform built for a professional artist specialising in face painting, body art, and special effects makeup. What makes this project stand out is not just the frontend β it's the complete content management system built behind it.
The artist can log into a private admin dashboard and update every single word, image, price, and section on their live website. No Wordpress. No plugins. No developer needed after handoff.
This was built as a complete freelance product β from database schema design to deployment β and represents a real-world example of a modern fullstack web application.
Most artist websites are either:
- Static β beautiful but frozen in time, requiring a developer for every update
- CMS-built β flexible but generic, slow, and hard to customise
This project solves both problems by pairing a handcrafted, high-performance frontend with a purpose-built admin CMS that gives the artist complete control β without sacrificing design quality or performance.
- π¬ Cinematic hero section with scrubable video slides and smooth transitions
- πΌοΈ Masonry portfolio gallery with category filtering and fullscreen lightbox
- π¨ Work categories grid with 3D scroll-triggered tilt animations
- β¨ Featured projects section with alternating layout and scroll reveals
- π¬ Accordion testimonials with hover-to-expand client reviews
- π€ About the artist section with clip-path image reveal animation
- π± Fully responsive across all screen sizes
- β‘ Server-side rendering β pages load with data, zero layout shift
- π Single-admin authentication β only one email can ever access the dashboard
- ποΈ Hero editor β update heading, subtext, and manage video slides
- π€ About editor β edit bio paragraphs, heading, eyebrow, and upload artist photo
- ποΈ Category editor β update the 3 homepage category cards independently
- π Featured works manager β full CRUD with accordion UI and inline image upload
- β Testimonials manager β add, edit, delete reviews with star rating picker
- π οΈ Services editor β tab-based editor for each service with dynamic pricing tiers and icon bullets
- πΌοΈ Gallery manager β bulk image upload, per-image category tagging, inline title editing
- βοΈ Site settings β WhatsApp number, CTA text, studio brand name β all editable
- π Global search β search any admin section with
βKkeyboard shortcut
| Layer | Technology | Why |
|---|---|---|
| Framework | Next.js 15 App Router | Server components, file-based routing, ISR |
| Language | TypeScript (strict) | End-to-end type safety, zero any |
| Database | Supabase (PostgreSQL) | Row Level Security, realtime, storage |
| Auth | Supabase Auth | Single-admin session, middleware protection |
| Storage | Supabase Storage | Image/video uploads with public CDN URLs |
| Styling | Tailwind CSS | Utility-first, no CSS files needed |
| Animations | GSAP + ScrollTrigger | Production-grade scroll animations |
| Images | Next.js Image | Automatic optimisation, lazy loading |
All public pages are async Server Components that fetch data directly from Supabase at request time. This means:
- No loading spinners on the public site
- Data is always fresh without client-side fetching
- The HTML that ships to the browser already contains real content
Admin API routes use Supabase's service role key which bypasses Row Level Security. The security boundary is the verifyAdmin() middleware check at the top of every route β not RLS. The service role key never touches the browser.
All database row shapes live in lib/types/database.ts. Every API route, fetch function, and component imports from there. This means a schema change requires updating one file, and TypeScript catches every breakage at compile time.
Server pages fetch data and pass it as props to Client Components. Client Components contain zero data fetching logic β only UI logic. This keeps animations, interactions, and GSAP code fully in the client while keeping data concerns fully on the server.
The CMS manages 11 tables across two concerns β content and configuration:
site_settings β Key-value store for global config (WhatsApp, CTA, brand name)
hero_slides β Video slides for the hero section
hero_content β Hero heading and subtext
about_section β Artist bio, paragraphs, and photo
categories β The 3 homepage work category cards
featured_works β Selected projects on the homepage
testimonials β Client reviews with star ratings
services β Service pages (face painting, body art, SFX)
service_details β Icon + text bullet points per service
service_pricing β Pricing tiers per service
gallery_items β Portfolio images with category tags
All tables have Row Level Security enabled with public read and authenticated write policies.
- Node.js 18+
- A Supabase account (free tier works)
git clone https://github.com/yourusername/sheffex-arts.git
cd sheffex-artsnpm installCreate a .env.local file in the root:
NEXT_PUBLIC_SUPABASE_URL=your_supabase_project_url
NEXT_PUBLIC_SUPABASE_ANON_KEY=your_supabase_anon_key
SUPABASE_SERVICE_ROLE_KEY=your_service_role_key
ADMIN_EMAIL=admin@yourdomain.com
NEXT_PUBLIC_WHATSAPP_NUMBER=780097654678Copy the contents of supabase/schema.sql and run it in your Supabase SQL Editor. This creates all 11 tables, enables RLS, and seeds initial data.
In your Supabase dashboard go to Storage β New Bucket and create a public bucket named media. Then run the storage policies from the schema file.
In your Supabase dashboard go to Authentication β Users β Invite User and invite the email you set as ADMIN_EMAIL.
npm run devOpen http://localhost:3000 for the portfolio site and http://localhost:3000/admin for the CMS.
| Variable | Description | Required |
|---|---|---|
NEXT_PUBLIC_SUPABASE_URL |
Your Supabase project URL | β |
NEXT_PUBLIC_SUPABASE_ANON_KEY |
Supabase anonymous/public key | β |
SUPABASE_SERVICE_ROLE_KEY |
Supabase service role key (server only) | β |
ADMIN_EMAIL |
The only email allowed to access /admin |
β |
NEXT_PUBLIC_WHATSAPP_NUMBER |
WhatsApp number with country code, no + |
β |
Every section uses GSAP's ScrollTrigger for entrance animations. The pattern used throughout β gsap.context() with cleanup via ctx.revert() β ensures animations don't leak between navigations in the App Router:
useEffect(() => {
const ctx = gsap.context(() => {
gsap.fromTo(".element", { y: 40, opacity: 0 }, {
y: 0, opacity: 1,
scrollTrigger: { trigger: section, start: "top 80%" }
});
}, sectionRef);
return () => ctx.revert(); // cleanup on unmount
}, []);The hero section supports drag-to-scrub video playback using requestAnimationFrame and pointer events β giving the feel of an interactive film reel.
The fetch layer maps raw database column names (snake_case) to component-friendly prop names (camelCase) in one place:
// lib/data/fetch.ts β mapping happens once here
return services.map((s: ServiceRow) => ({
slug: s.slug,
heroImage: s.hero_image_url, // snake_case β camelCase
longDescription: s.long_description,
...
}));Components always receive clean camelCase props. If the database schema changes, only fetch.ts needs updating.
- Designing a CMS from scratch forces you to think about data relationships before writing a single line of frontend code
- The boundary between Server and Client Components in Next.js App Router is a genuine architectural decision, not just a performance hint
- Supabase's Row Level Security is powerful but you need to understand when to bypass it (admin writes via service role) versus when to respect it (public reads)
- TypeScript's
nullvsundefineddistinction matters enormously when your data comes from a PostgreSQL database via a JavaScript ORM - GSAP's
context()API is essential in React β without it, ScrollTrigger instances accumulate across renders and cause subtle animation bugs
- Email notifications when a booking inquiry comes in via WhatsApp link
- Image optimisation pipeline β auto-compress uploads before sending to Supabase Storage
- Drag-and-drop reordering for gallery items and featured works
- Preview mode β see CMS changes before publishing
- Analytics dashboard showing most-visited portfolio pieces
MIT β feel free to use this as a reference for your own projects. If you build something with it, I'd love to see it.
Built by Ovuoba Emmanuel β a fullstack developer focused on building polished, production-ready web applications.
If this project helped you, consider giving it a β β it helps others find it.