← Notes
Jan 2026

Film Portfolio Build

Built a 31-project film portfolio on Next.js 15 with hover previews, autoplay flows, Cloudinary hosting, and a minimal A24-ish aesthetic — plus a bunch of real-world breakage fixes.

nextjsvideocloudinaryffmpegdebuggingRelated: Work (site-building)
Quick scan
Context Client needed a fast, modern film portfolio: full-screen hero, responsive grid, video-first UX, and clean navigation that still feels premium.
Breakage Hero gaps, mobile header sizing, Next.js 15 params in client components, mobile fullscreen restrictions, Cloudinary ID chaos, bad poster frames, header overlap, and the classic ‘hide in UI vs delete from data’ trap.
Takeaway Build incrementally, test on real devices, expect platform constraints, use tooling when third parties are messy, and prioritize UX and robustness over clever code.

What we built

A Next.js 15 film portfolio with 31 video projects.

Full-screen video hero + parallax-ish work sections.

Hover-preview video cards in a responsive grid.

Autoplay behavior on click, with platform-safe fallbacks.

Cloudinary video hosting with dynamic poster extraction.

Light/dark theme + scroll-responsive header.

Minimalist aesthetic influenced by A24 (typography + whitespace doing the work).

What broke and how we fixed it

1) Work section ‘leaked’ into the hero: `min-h` left a gap where the next section peeked through. Fix: use `h-screen` for true 100vh coverage.

2) Mobile header got cut off: desktop padding was too big. Fix: responsive padding/text, hide non-critical header content on mobile, reduce gaps.

3) Next.js 15 client component params caused 404s: `params` is a Promise. Fix: server can `await`, client must `use(params)`.

4) Mobile auto-fullscreen failed: iOS blocks fullscreen requests without direct user gesture. Fix: device detection + desktop-only fullscreen, always allow play.

5) Cloudinary asset naming chaos: random suffixes meant filenames weren’t predictable. Fix: pull asset list via Cloudinary API and map manually (reliable, tedious).

6) Bad poster frames: first frame often black/blurry. Fix: use Cloudinary frame extraction (`so_X`) to pick better frames without re-uploading.

7) Header overlapped detail page content: fixed header + top-starting content caused collisions. Fix: simplify the detail page UI and avoid duplicating header navigation.

8) Year removed from UI but still needed: almost deleted it from the data model. Fix: hide in UI, keep in data for future filtering/sorting.

New challenges and patterns that worked

Compression pipeline: raw videos were too big. Built an FFmpeg flow for preview loops and compressed 1080p full versions (big size cuts with no visible quality loss).

Priority ordering: client wanted featured projects first. Chose manual array order (curation beats sorting logic for small datasets).

Autoplay URL flag: used `?autoplay=true` to differentiate click-through vs direct navigation (better UX and shareable URLs).

Header state machine: combined Intersection Observer (hero in view) + scroll direction tracking (hide on scroll down).

Hover preview without fighting autoplay policies: muted autoplay runs; CSS opacity reveals on hover (smoother than JS play/pause spam).

Fluid type: used `clamp()` for typography that scales smoothly instead of breakpoint jumps.

Theme switching without re-renders: CSS variables + `data-theme` attribute for instant updates.

Summary

Built: 31-project video portfolio with autoplay flows, hover previews, responsive layout, theme switching.

Broke: hero gaps, mobile header sizing, Next.js 15 params, fullscreen policies, asset management, posters, header overlap, UI vs data confusion.

Fixed: all of it with better patterns: full viewport sections, responsive sizing, `use()` for params in client components, device-safe fallbacks, tooling, transformations, minimal UI, data preservation.

Key lesson: test on real devices, have fallbacks for everything, and keep the design simple so the work stays loud.


← Back to Notes