All case studies
03Client · Luxury creative agency

Noevella Group

A media-forward agency site on a headless CMS: divisions, work, journal and a shop, with a design language locked with the client before a page was built.

Role
Design and full build through Shannara
When
2026
Stack
Next.js 16, React 19, Payload CMS 3, PostgreSQL
Links
Live site ↗Client repo, private. Walkthrough on request.
  • 11
    CMS collections
  • 5
    divisions
  • 9
    research briefs
01

The problem

  • A founder running events, brand strategy, talent and a creative hub needed one story, not five service pages.
  • She needed to publish work and products herself, without a developer.
02

Architecture

CMSPayload 3 inside the Next.js app: Divisions, Projects, Products, Orders, Inquiries, Insights, Services, Team, Testimonials, Media, Users.
StoragePostgres for content, Vercel Blob for media, scripted media imports.
Front endClay and glass panels, sticker cards, type-on hero, floating pill nav with a mega panel.
GrowthMeta and GA4 pixels with campaign attribution carried into every order and WhatsApp message.
ResilienceEvery CMS query falls back to labelled static content, so pages render before any entry exists.
03

Key decisions

01

No WebGL, on purpose

The design DNA keeps motion in CSS and GSAP so video stays the hero and phones stay fast.

TradeoffLess spectacle, more speed and content.
02

Research before layout

Nine research briefs fed one content master plan; the core message became 'one integrated team'.

TradeoffSlower start, far fewer rewrites.
04

Real code

src/lib/queries.tsexcerpt
async function safe<T>(fn: () => Promise<T>, fallback: T): Promise<T> {
  try {
    return await fn()
  } catch (err) {
    console.warn('[queries] falling back:', (err as Error).message)
    return fallback
  }
}

export async function getFeaturedProjects(limit = 9) {
  const fromCms = await safe(async () => { /* payload.find featured projects … */ }, [])
  // Real featured work leads, clearly labelled demo work fills the rest
  return [...fromCms, ...DEMO_PROJECTS].slice(0, limit)
}
Every CMS query degrades to labelled static content, so the site renders before a single entry exists and never shows a blank grid.
src/components/Reveal.tsxexcerpt
const reduce = window.matchMedia('(prefers-reduced-motion: reduce)').matches
if (reduce) {
  el.classList.add('is-in')
  return
}

// Arm the CSS effect (idempotent across instances).
document.documentElement.setAttribute('data-reveal-armed', '')
// … IntersectionObserver adds .is-in on entry …

// Safety net: never leave content hidden.
const failSafe = window.setTimeout(() => el.classList.add('is-in'), 3000)
Scroll reveals are progressive enhancement: off for reduced motion, armed only after JS runs, and forced visible after 3 seconds so content can never stay hidden.
05

How it was verified

  • Typed collections with generated types.
  • Every animation respects reduced motion, with a global kill switch.
  • Orders and inquiries accept public creates but only signed-in staff can read them.
06

Results

  • The founder edits content, work and products herself.
07

What I'd do next

  • Remove demo labels from shop items before launch.
  • Move from a Gmail address to a domain email.
$ ./hire --re noevella-group

Want a build like Noevella Group?

Senior remote roles and contract builds. Replies within a day, EAT (UTC+3).