Last verified: 2026-03-04 Target:
apps/branded-video-flowCompanion:packages/app-video-flow
Branded Video Flow — App Architecture
The apps/branded-video-flow (BVF) is a Next.js 15 App Router application that renders per-flow, branded video recording experiences for end-users (respondents). It is a thin shell — all domain logic, UI components, and the parameterized design system live in packages/app-video-flow.
Component / Module Overview
Primary Data Flow — Respondent Records a Video
Integration Map — BVF in the Platform
Route Inventory
| Route | Auth Required | Cache | Purpose |
|---|---|---|---|
/flows/[flowId] | No | ISR 60s | Main respondent flow page |
/flows/[flowId]/layout | No | ISR 60s | Font + image preloads, wraps flow |
/flows/[flowId]/frame | No | ISR 60s | Iframe version for device emulation debug |
/flows/[flowId]/admin | Super admin | ISR 60s | Per-flow session manager |
/admin | Super admin | unstable_cache 5min | Global all-flows session overview |
/kiosk | Clerk user | force-dynamic | Multi-flow kiosk rotator |
/rec/test | No | Default | Browser video capability test |
/api/blob/upload | No (rate-limited) | None | Vercel Blob upload handler |
/api/info | No | None | Deployment version + git SHA |
/api/correlation | No | None | Sentry correlation ID injection |
/api/sentry-error | No | None | Sentry error test endpoint |
/health | No | None | Health check |
/sign-in, /sign-up | No | Default | Clerk auth pages |
/sso-callback | No | Default | Clerk SSO callback |
/select-organization | Clerk user | Default | Org selection after sign-in |
Parameterized Design System (BVF-Specific)
BVF uses a separate, runtime parameterized design system — every visual property flows from the per-flow FlowTheme config. This is fundamentally different from @repo/design-system used by other apps.
Rule: Never hardcode colors or use Tailwind color classes on BVF flow screens. All visuals must be parameterized through the theme cascade.
FlowConfig.theme.brand
├── typography.fontFamily → CSS var, applied by FlowLayout
├── colors.primary → CSS var, applied by components
├── colors.background → CSS var, applied by components
├── assets.backgroundImages[] → Preloaded by FlowLayout, rotated by BackgroundIndexProvider
├── assets.logo → Rendered by BrandedHeader
└── assets.backgroundImage → Legacy single-image fallback
The BackgroundIndexProvider (from @repo/app-video-flow/web) manages sequential cycling through backgroundImages[] across flow steps.
Key Architectural Patterns
React cache() for Per-Request Deduplication
getCachedFlowConfig uses React.cache() to deduplicate the flow config fetch within a single server request. Three components call it per request — generateMetadata, FlowLayout, and FlowPage — but only one Redis fetch occurs:
generateMetadata(flowId) ─┐
FlowLayout(flowId) ├─► getCachedFlowConfig(flowId) ─► 1x Redis fetch
FlowPage(flowId) ─┘
File: apps/branded-video-flow/app/_providers/server/cached-flow-config.ts
The wrapper also includes a single retry guard for transient cold-start failures.
Window Global Injection Pattern
Public environment variables are injected via a beforeInteractive script tag in layout.tsx, setting window.__APP_VIDEO_FLOW_ANALYTICS__ and window.__APP_VIDEO_FLOW_ENV__ before any JS runs. Client components and the package's analytics module read from these globals rather than from process.env (which is not available in browser).
layout.tsx (server) → <Script strategy="beforeInteractive"> → window.__APP_VIDEO_FLOW_ANALYTICS__
→ window.__APP_VIDEO_FLOW_ENV__
↓
client-providers.tsx → posthog.init() from window globals
→ setAnalyticsEnvironment() from window globals
Provider Registration Pattern
Client providers in client-providers.tsx register Sentry, analytics, and layout telemetry providers at module load time (outside the component), ensuring registration occurs before any component renders:
// Module level — runs once on bundle load
registerSentryProvider({ addBreadcrumb, captureException });
registerBreadcrumbProvider((b) => Sentry.addBreadcrumb(b));
registerErrorCaptureProvider(Sentry.captureException);
registerMessageCaptureProvider(createSentryMessageCaptureProvider(Sentry));
registerLayoutTelemetrySentry(Sentry);
File: apps/branded-video-flow/app/_providers/client-providers.tsx
ISR + React cache() Layering
| Layer | TTL | Scope | Key |
|---|---|---|---|
React.cache() | Request lifetime | Per-request deduplication | flowId |
revalidate = 60 | 60 seconds | CDN/Next.js ISR | route path |
unstable_cache (admin flows) | 3600s | Next.js Data Cache | ["flows:list"] tag "flows" |
unstable_cache (admin sessions) | 300s | Next.js Data Cache | ["admin-all-flows-sessions"] |
Device Token Tracking
Server actions in actions.ts manage a device token cookie (DEVICE_TOKEN_COOKIE_NAME) to track video ownership without requiring authentication. When source=library query param is present, the Clerk user ID is also captured for ownership association.
Kiosk Mode
/kiosk is an authenticated route (Clerk) that fetches all standard flows and renders a KioskRotator client component. It filters out language-picker flows and flows without welcome screens. The kiosk rotates through flows and reuses the same server actions as the respondent flow route.
Frame / Debug Panel Mode
/flows/[flowId]/frame renders the flow inside an iframe for accurate device emulation. The FloatingDebugPanelClient (dynamically imported, admin-only) hosts this iframe in the main flow page. PostMessage is used for parent-frame communication.
Self-Hosted Fonts
Google Fonts are self-hosted in apps/branded-video-flow/public/fonts/ to eliminate external CDN dependency. The FlowLayout preloads only the fonts actually used by the current flow:
FlowConfig → extractUsedFonts(flowConfig) → [FontFamily]
→ getFontPreloadPaths([FontFamily]) → ["/fonts/...woff2"]
→ <link rel="preload" as="font"> in FlowLayout
Source: packages/app-video-flow/src/web/fonts.ts
Server-Side Image Preloading
FlowLayout extracts critical images and injects <link rel="preload"> tags into the <head>, making the browser discover images during HTML parse rather than after JS runs:
- First 2 images:
fetchPriority="high"(LCP candidates) - Images 3–6: standard preload
Source: packages/app-video-flow/src/critical-images.ts
Server Clients (getClients())
apps/branded-video-flow/app/_providers/server/clients.ts provides a lazy singleton that wires together all server-side dependencies:
| Client | Type | Purpose |
|---|---|---|
organization | OrganizationClient | Auth checks, super admin detection |
videoFlow | VideoFlowClient | Flow config storage, blob upload handler |
videoFlowActions | FlowActionsClient | Session lifecycle, webhook trigger |
videoProcessingRegistry | VideoProcessingRegistry | Read processed video data |
logger | Logger | Structured logging |
Open Architecture Questions
The perf-investigate-bvf-admin kanban task documents a known N+1 issue in the BVF admin page. The outer getAllSessions() now fetches all raw sessions in one call (resolved), but getMergedSessionsForFlow still fans out per-flow to the processing registry.
See performance-profiling-2026-03.md for the full performance investigation.
Related Documentation
- Video Recording Data Architecture — How video data flows from browser to storage to processing
- Client-Side Provider Registration — The
register*Provider()pattern - Error Logging & Sentry — How errors flow to Sentry from BVF
- PostHog Analytics — Project B: end-user analytics in BVF
- E2E Testing Architecture — Smoke tests through BVF
- Performance Profiling March 2026 — BVF admin N+1 and overall bottlenecks