Willows Media Group

Willows Media Group

Built a bilingual music label website with headless CMS, enabling non-technical staff to manage artist content and releases independently.

Year

2025

Role

Full Stack Developer

Duration

3 weeks

Read Time

5 min read

engineeringdesignintegration
Willows Media Group - Willows Media Group

Willows Media Group: A Modern Platform for Indonesian Hip-Hop

Willows Media Group is an independent Jakarta-based label focused on Indonesian hip-hop, R&B, and electronic artists. I built them a bilingual website and content management system — live at willow-records.vercel.app — that lets non-technical staff manage the whole site, in two languages, without developer involvement.

The Problem

The label had the growing pains you'd expect:

  • Artist bios, photos, and show information required a developer to update
  • All content needed Indonesian translations for the local market alongside English
  • Release information was manually duplicated between the website and Spotify
  • Large artist photos and videos needed to perform on Indonesian mobile connections
  • Artist pages needed individual personality within a consistent label identity

Architecture

The build separates content from presentation: Payload CMS as a headless backend, Next.js App Router on the front, deployed as separate Vercel projects with Neon PostgreSQL and Vercel Blob for media. Editors get live preview — they see their changes rendered on the real frontend, at mobile and desktop breakpoints, before publishing:

// Live preview configuration in Payload CMS
livePreview: {
  url: ({ data }) => {
    const baseUrl = 'https://willow-records.vercel.app'
    if (data?.slug) {
      return `${baseUrl}/en/artists/${data.slug}`
    }
    return baseUrl
  },
  collections: ['artists', 'shows', 'videos', 'pages'],
  breakpoints: [
    { label: 'Mobile', width: 375, height: 667 },
    { label: 'Desktop', width: 1440, height: 900 },
  ],
}

Collections model the label's world directly — artists, shows, videos, pages — with bilingual fields side by side so editors manage both languages in one place:

// Collection definition for Artists
export const Artists: CollectionConfig = {
  slug: 'artists',
  admin: {
    useAsTitle: 'name',
    livePreview: { url: ({ data }) => `${FRONTEND_URL}/artists/${data.slug}` },
  },
  fields: [
    { name: 'name', type: 'text', required: true },
    { name: 'slug', type: 'text', required: true, unique: true },
    { name: 'bio', type: 'richText' },
    { name: 'bioId', type: 'richText', label: 'Bio (Indonesian)' },
    { name: 'photo', type: 'upload', relationTo: 'media' },
    { name: 'spotifyId', type: 'text' },
    { name: 'accentColor', type: 'text' },
  ],
}

Key Features

Bilingual by Construction

Internationalization runs on next-intl with URL-based locales (/en/artists vs /id/artists), automatic locale detection with manual override, and dual content fields in the CMS. Doubling the content fields is a real cost, but keeping both languages on one editing screen beat every alternative for the actual editors using it.

Spotify Catalog Sync

Each artist's releases come straight from the Spotify Web API — albums, singles, and EPs with artwork, track counts, release dates, and streaming links — so the catalog stays current without manual entry:

export async function getArtistReleases(artistId: string, limit = 20) {
  const token = await getSpotifyToken()
  const response = await fetch(
    `https://api.spotify.com/v1/artists/${artistId}/albums?limit=${limit}&include_groups=album,single,ep`,
    { headers: { Authorization: `Bearer ${token}` } }
  )
  return response.json()
}
Homepage releases section

Immersive Artist Pages

Each artist gets a custom Three.js scene via React Three Fiber — particle systems keyed to their accent color — with scroll-driven parallax and graceful fallbacks for low-powered devices. Individual personality, consistent system.

Performance for the Actual Audience

The audience is substantially on Indonesian mobile connections, which shaped the performance work: WebP with responsive srcsets through Next.js Image, dynamic imports so 3D scenes never block first load, skeleton states with lazy-loaded components, and ISR with one-hour revalidation for CMS content.

Problems Worth Writing Down

Vercel Blob + Payload 3.x. The storage adapter docs were sparse for production setups. The working configuration: disablePayloadAccessControl: true to serve files directly from the CDN, a Content Security Policy updated to allow the blob storage domain, and remotePatterns in the Next.js config so image optimization accepts the external host.

Live preview across separate deployments. The CMS and frontend are different Vercel projects, so live preview means a cross-origin iframe. Getting it working took adding the CMS domain to the frontend's frame-ancestors CSP directive, CORS configuration in Payload, and Permissions-Policy headers so Spotify embeds work inside the preview iframe.

Payload import maps. Payload 3.x handles complex use cases well, but custom admin components require regenerating the import map — the kind of thing that costs an hour the first time and a minute every time after.

Lessons Learned

Plan collections before building them. Time invested in field design upfront prevented migration headaches later — schema changes after content exists are an order of magnitude more annoying.

Live preview earns its setup cost. Editors strongly prefer seeing real rendered changes over abstract admin forms. Of everything in the build, it's the feature that most changed how the label works.

3D needs a performance budget. The WebGL backgrounds carry the site's visual identity, but only because they're aggressively optimized and degrade gracefully. Shipped without the fallbacks, they'd be a liability on exactly the devices the audience uses.

Next.jsTypeScriptPayload CMSPostgreSQLThree.jsFramer MotionVercelSpotify API

Interested in similar results?

Let's discuss how I can help bring your project to life with the same attention to detail.