All docs/general

docs/architecture/branded-video-flow-app-architecture.md

Last verified: 2026-03-04 Target: apps/branded-video-flow Companion: 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

RouteAuth RequiredCachePurpose
/flows/[flowId]NoISR 60sMain respondent flow page
/flows/[flowId]/layoutNoISR 60sFont + image preloads, wraps flow
/flows/[flowId]/frameNoISR 60sIframe version for device emulation debug
/flows/[flowId]/adminSuper adminISR 60sPer-flow session manager
/adminSuper adminunstable_cache 5minGlobal all-flows session overview
/kioskClerk userforce-dynamicMulti-flow kiosk rotator
/rec/testNoDefaultBrowser video capability test
/api/blob/uploadNo (rate-limited)NoneVercel Blob upload handler
/api/infoNoNoneDeployment version + git SHA
/api/correlationNoNoneSentry correlation ID injection
/api/sentry-errorNoNoneSentry error test endpoint
/healthNoNoneHealth check
/sign-in, /sign-upNoDefaultClerk auth pages
/sso-callbackNoDefaultClerk SSO callback
/select-organizationClerk userDefaultOrg 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

LayerTTLScopeKey
React.cache()Request lifetimePer-request deduplicationflowId
revalidate = 6060 secondsCDN/Next.js ISRroute path
unstable_cache (admin flows)3600sNext.js Data Cache["flows:list"] tag "flows"
unstable_cache (admin sessions)300sNext.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:

ClientTypePurpose
organizationOrganizationClientAuth checks, super admin detection
videoFlowVideoFlowClientFlow config storage, blob upload handler
videoFlowActionsFlowActionsClientSession lifecycle, webhook trigger
videoProcessingRegistryVideoProcessingRegistryRead processed video data
loggerLoggerStructured 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