{"id":39999,"date":"2026-04-23T07:00:00","date_gmt":"2026-04-23T14:00:00","guid":{"rendered":"https:\/\/cloudinary.com\/blog\/?p=39999"},"modified":"2026-04-23T15:19:20","modified_gmt":"2026-04-23T22:19:20","slug":"high-performance-video-backgrounds-smart-previews","status":"publish","type":"post","link":"https:\/\/cloudinary.com\/blog\/high-performance-video-backgrounds-smart-previews","title":{"rendered":"High-Performance Video Backgrounds: Automating Smart Previews With TanStack Start and PowerFlows"},"content":{"rendered":"<div class=\"wp-block-cloudinary-markdown \"><ul>\n<li>\n<strong>Live Demo:<\/strong> <a href=\"https:\/\/media-flows-video-preview-demo.vercel.app\/\">media-flows-video-preview-demo.vercel.app<\/a>\n<\/li>\n<li>\n<strong>Source Code:<\/strong> <a href=\"https:\/\/github.com\/musebe\/MediaFlows-Video-Preview-Demo\">GitHub Repository<\/a>\n<\/li>\n<\/ul>\n<p>Hero videos are visually engaging, but can be a silent performance killer on a slow network. And if you swap them for static images to reduce page weight, you lose the beautiful animations that made your hero section so compelling.<\/p>\n<p>We can change that with <strong>Automated Smart Previews<\/strong>. In this guide, we\u2019ll combine <strong>TanStack Start<\/strong> with <strong>Cloudinary PowerFlows<\/strong> to create a system that will trigger a low-code workflow to generate, optimize, and deliver a lightweight preview clip the moment an upload happens. By the end, you\u2019ll have a functional pipeline where uploading a video in the navbar instantly transforms the hero section into a high-performance experience.<\/p>\n<h3>The Logic<\/h3>\n<p>The goal is to stop loading full videos for first impressions. Instead, you\u2019ll generate a high-impact highlight that\u2019s:<\/p>\n<ul>\n<li>\n<strong>Trimmed<\/strong> so the video highlights the best few seconds.<\/li>\n<li>\n<strong>Resized<\/strong> and scaled specifically for hero backgrounds.<\/li>\n<li>\n<strong>Optimized<\/strong> using automatic format and quality switching.<\/li>\n<li>\n<strong>Synced<\/strong> to the app via webhooks for an instant UI update.<\/li>\n<\/ul>\n<p>The full video still exists, but the preview carries the weight of the initial page load.<\/p>\n<h3>The Workflow<\/h3>\n<p>We\u2019ve cut the manual glue code by connecting the frontend directly to a media pipeline.<\/p>\n<ol>\n<li>\n<strong>Upload.<\/strong> Use the Cloudinary Upload Widget in the TanStack Start frontend for direct-to-cloud delivery.<\/li>\n<li>\n<strong>Trigger.<\/strong> A PowerFlow catches the <code>upload<\/code> event automatically.<\/li>\n<li>\n<strong>Process.<\/strong> The flow generates a preview URL (e.g., using <code>so_0, eo_5<\/code> for a 5-second loop).<\/li>\n<li>\n<strong>Notify.<\/strong> PowerFlows sends the structured media data to a TanStack Start API route via a webhook.<\/li>\n<li>\n<strong>Update.<\/strong> The app refreshes the hero component with the new optimized asset.<\/li>\n<\/ol>\n<h2>The Stack and Workflow<\/h2>\n<p>The architecture is lean. We aren\u2019t building a custom media server, just orchestrating specialized tools to do one job well.<\/p>\n<h3>Application Layer: TanStack Start<\/h3>\n<p><a href=\"https:\/\/tanstack.com\/start\/latest\"><strong>TanStack Start<\/strong><\/a> acts as your command center. It handles the app shell, route loading, and the server-side logic required to catch webhooks. It doesn\u2019t process the video itself. Instead, it waits for structured media data and updates the UI state. This separation ensures the frontend remains fast and focused on rendering.<\/p>\n<h3>Upload Layer: Cloudinary Upload Widget<\/h3>\n<p>Use the <a href=\"https:\/\/cloudinary.com\/documentation\/upload_widget\"><strong>Cloudinary Upload Widget<\/strong><\/a> as the entry point. It removes the need for custom multi-part upload logic by handling direct browser-to-cloud delivery, chunked uploads for large files, and success callbacks to trigger the next phase.<\/p>\n<h3>Automation Layer: MediaFlows PowerFlows<\/h3>\n<p>This is the engine. A <strong>PowerFlow<\/strong> is a low-code workflow that triggers the moment a file hits Cloudinary. It passes data through a sequence of connected blocks:<\/p>\n<ul>\n<li>\n<strong>Cloudinary Upload<\/strong> receives the asset.<\/li>\n<li>\n<strong>Create Asset URL<\/strong> generates a transformed preview (e.g., a 5-second loop).<\/li>\n<li>\n<strong>Send HTTP Request<\/strong> POSTs the preview metadata back to your app.<\/li>\n<\/ul>\n<h3>Delivery Layer: Smart URLs<\/h3>\n<p>The hero section is powered by two distinct URLs. The first is the preview, which is a lightweight, trimmed, and optimized version for the initial background load. Second is the full asset, or the original high-quality video, loaded only when necessary.<\/p>\n<h3>Webhook Layer: Closing the Loop<\/h3>\n<p>The PowerFlow sends a payload (PublicID, Preview URL, and metadata) back to a dedicated <strong>TanStack Start API route<\/strong>. The app receives this, updates the global state, and the hero component refreshes instantly.<\/p>\n<p>So why this architecture? By offloading the heavy lifting to PowerFlows, the project stays easy to manage. The code you write is mostly \u201cglue\u201d rather than infrastructure. You\u2019ll get:<\/p>\n<ul>\n<li>\n<strong>Zero backend bloat.<\/strong> No ffmpeg or local processing required.<\/li>\n<li>\n<strong>Predictable performance.<\/strong> Previews are generated once and cached at the edge.<\/li>\n<li>\n<strong>High scalability.<\/strong> Cloudinary handles the traffic while your app stays lean.<\/li>\n<\/ul>\n<h2>Project Setup and Core App Logic<\/h2>\n<p>The project remains lightweight because you\u2019ve divided the logic by responsibility. The application essentially runs on four \u201cengines\u201d that handle the data flow from upload to UI.<\/p>\n<h3>The Four Core Engines<\/h3>\n<ol>\n<li>\n<strong>The Webhook route.<\/strong> A server-side handler that receives the POST payload from PowerFlows. It acts as the \u201cear\u201d of the application, waiting for processed media data.<\/li>\n<li>\n<strong>The asset store.<\/strong> A simple in-memory store for this demo. It holds the latest processed video metadata so the UI can update without a full database setup.<\/li>\n<li>\n<strong>The payload builder.<\/strong> A normalization layer. It serves the latest MediaFlows data if available, or falls back to a high-quality demo video to keep the rendering path stable.<\/li>\n<li>\n<strong>The hero renderer.<\/strong> A pure UI component. It doesn\u2019t \u201cknow\u201d about the pipeline; it simply renders the <code>previewUrl<\/code> for the background and prepares the <code>fullVideoUrl<\/code> for user interaction.<\/li>\n<\/ol>\n<p>By offloading video processing to Cloudinary and orchestration to PowerFlows, the <strong>TanStack Start<\/strong> code stays focused on state. The logic is a straight line: The loader checks the store, and the UI renders the result. You aren\u2019t fighting with <code>ffmpeg<\/code> or complex backend tasks; you\u2019re simply managing structured data.<\/p>\n<h2>Configuring the Cloudinary Upload Preset<\/h2>\n<p>Before the Upload Widget can work, it needs a rulebook. In Cloudinary, that\u2019s the <strong>Upload Preset<\/strong>. It centralizes your logic so you don\u2019t have to scatter upload rules throughout your frontend code.<\/p>\n<h3>The Unsigned Path<\/h3>\n<p>Use an <a href=\"https:\/\/cloudinary.com\/documentation\/upload_presets#unsigned_uploads\"><strong>Unsigned<\/strong> preset<\/a> for this build. This is the low-friction route for browser-based uploads because it doesn\u2019t require a custom backend signing endpoint.<\/p>\n<h3>The Configuration<\/h3>\n<p>Keep the setup focused on consistency:<\/p>\n<ul>\n<li>Name: <code>mediaflows_demo_video_unsigned<\/code>\n-*Mode: <code>Unsigned<\/code>\n<\/li>\n<li>Folder* <code>mediaflows-demo<\/code> (ensures the Media Library stays organized and triggers stay predictable).<\/li>\n<li>Allowed formats: <code>mp4, mov, webm, m4v<\/code> (strictly enforced to ensure the pipeline receives video).<\/li>\n<\/ul>\n<h3>Strategic Logic Placement<\/h3>\n<p>Leave <strong>Transformations<\/strong> and <strong>Analysis<\/strong> settings empty in the preset. You\u2019ll want the \u201cintelligence\u201d of the preview generation to live inside <strong>MediaFlows<\/strong>. Keeping the preset as a simple gatekeeper makes the workflow modular and much easier to debug.<\/p>\n<h3>Connecting to the App<\/h3>\n<p>Once the preset is live in your dashboard, the integration is a one-liner in your <code>.env<\/code> file:<\/p>\n<pre class=\"js-syntax-highlighted\"><span><code class=\"hljs shcb-wrap-lines\">VITE_CLOUDINARY_CLOUD_NAME=your_cloud_name\nVITE_CLOUDINARY_UPLOAD_PRESET=mediaflows_demo_video_unsigned\n<\/code><\/span><\/pre>\n<p>This keeps the environment clean. The widget is now authorized to upload directly to your folder, which automatically kicks off the automation.<\/p>\n<h2>Adding the Cloudinary Upload Widget<\/h2>\n<p>The upload engine is where the app transforms from a static demo into an interactive system. By clicking a single button in the navbar, a user triggers the entire pipeline.<\/p>\n<p>Instead of building custom upload infrastructure, use the <strong>Cloudinary Upload Widget<\/strong>. It handles the heavy lifting: file selection, transport, and browser-side restrictions directly from the client.<\/p>\n<h3>Core Configuration<\/h3>\n<p>Keep the configuration focused on video integrity. The goal is to ensure only compatible assets enter the pipeline.<\/p>\n<pre class=\"js-syntax-highlighted\" aria-describedby=\"shcb-language-1\" data-shcb-language-name=\"JavaScript\" data-shcb-language-slug=\"javascript\"><span><code class=\"hljs language-javascript shcb-wrap-lines\">widgetRef.current = <span class=\"hljs-built_in\">window<\/span>.cloudinary.createUploadWidget(\n  {\n    cloudName,\n    uploadPreset,\n    <span class=\"hljs-attr\">sources<\/span>: &#91;<span class=\"hljs-string\">\"local\"<\/span>, <span class=\"hljs-string\">\"camera\"<\/span>, <span class=\"hljs-string\">\"url\"<\/span>],\n    <span class=\"hljs-attr\">multiple<\/span>: <span class=\"hljs-literal\">false<\/span>,\n    <span class=\"hljs-attr\">maxFiles<\/span>: <span class=\"hljs-number\">1<\/span>,\n    <span class=\"hljs-attr\">resourceType<\/span>: <span class=\"hljs-string\">\"video\"<\/span>,\n    <span class=\"hljs-attr\">clientAllowedFormats<\/span>: &#91;<span class=\"hljs-string\">\"mp4\"<\/span>, <span class=\"hljs-string\">\"mov\"<\/span>, <span class=\"hljs-string\">\"webm\"<\/span>, <span class=\"hljs-string\">\"m4v\"<\/span>],\n    <span class=\"hljs-attr\">showAdvancedOptions<\/span>: <span class=\"hljs-literal\">false<\/span>,\n    <span class=\"hljs-attr\">cropping<\/span>: <span class=\"hljs-literal\">false<\/span>,\n  },\n  <span class=\"hljs-keyword\">async<\/span> (error, result) =&gt; {\n    <span class=\"hljs-keyword\">if<\/span> (error) {\n      <span class=\"hljs-built_in\">console<\/span>.error(<span class=\"hljs-string\">\"&#91;upload-widget] callback error:\"<\/span>, error);\n      <span class=\"hljs-keyword\">return<\/span>;\n    }\n\n    <span class=\"hljs-keyword\">if<\/span> (result?.event === <span class=\"hljs-string\">\"success\"<\/span>) {\n      <span class=\"hljs-keyword\">const<\/span> uploadedPublicId = result?.info?.public_id;\n      <span class=\"hljs-comment\">\/\/ The handoff: wait for the MediaFlows webhook to finish processing<\/span>\n    }\n  }\n);\n<\/code><\/span><small class=\"shcb-language\" id=\"shcb-language-1\"><span class=\"shcb-language__label\">Code language:<\/span> <span class=\"shcb-language__name\">JavaScript<\/span> <span class=\"shcb-language__paren\">(<\/span><span class=\"shcb-language__slug\">javascript<\/span><span class=\"shcb-language__paren\">)<\/span><\/small><\/pre>\n<p>Let\u2019s break down each of the video settings:<\/p>\n<ul>\n<li>resourceType: \u201cvideo\u201d aligns the upload flow with our video-processing pipeline.<\/li>\n<li>clientAllowedFormats forces the browser to reject incompatible files before the upload even begins.<\/li>\n<li>Unsigned uploads use the preset you configured earlier to keep the frontend code simple and secure without custom backend signing.<\/li>\n<\/ul>\n<h3>The Success Handoff<\/h3>\n<p>An upload \u201csuccess\u201d event is only half the story. When a video finishes uploading, MediaFlows still needs a moment to trigger, generate the preview, and call your webhook.<\/p>\n<p>Because of this, the success callback acts as a handoff point. In this project, the UI doesn\u2019t refresh immediately. Instead, it waits until the app detects that the latest saved asset in the store matches the newly uploaded <code>public_id<\/code>. This polling step ensures the hero updates only when the optimized preview is actually ready to be served.<\/p>\n<h3>Handling Large Files<\/h3>\n<p>To handle high-resolution videos, utilize chunking settings like <code>maxChunkSize<\/code> and <code>maxVideoFileSize<\/code>. These options allow the widget to manage larger uploads gracefully. However, it\u2019s important to remember that account-level limits still apply. If a file exceeds your product environment\u2019s maximum size, the upload will fail regardless of your widget settings.<\/p>\n<h3>User Experience<\/h3>\n<p>By placing the upload button in the navbar, you\u2019ll make the entire workflow testable directly from the live demo. There\u2019s no need for a separate admin panel or the Cloudinary dashboard. A user uploads a file, the pipeline processes it, and the hero updates in real time.<\/p>\n<blockquote>\n<p>Explore the code on <a href=\"https:\/\/github.com\/musebe\/MediaFlows-Video-Preview-Demo\/blob\/main\/src\/components\/cloudinary\/UploadWidgetButton.tsx\">GitHub<\/a>.<\/p>\n<\/blockquote>\n<h2>Building the MediaFlows PowerFlow<\/h2>\n<p>This is the automated bridge of the entire project. While the Upload Widget handles the entry point, <strong>MediaFlows PowerFlows<\/strong> transforms that raw file into a high-performance hero preview without any manual intervention.<\/p>\n<p><img decoding=\"async\" src=\"https:\/\/cloudinary-marketing-res.cloudinary.com\/image\/upload\/v1776980941\/blog-High-Performance_Video_Backgrounds-_Automating_Smart_Previews_With_TanStack_Start_and_PowerFlows-1.png\" alt=\"Articles Images MediaFlows Demo\" loading=\"lazy\" class=\"c-transformed-asset\"  width=\"1916\" height=\"929\"\/><\/p>\n<h3>The Three-Block Flow<\/h3>\n<p>You\u2019ve built this workflow using a minimal three-block chain. Because PowerFlows allows blocks to pass response values to subsequent steps, the logic stays lean and easy to debug.<\/p>\n<ul>\n<li>\n<p><strong>The trigger (Cloudinary Upload).<\/strong> You configured this to trigger \u201cOn Asset Upload\u201d and filtered it for \u201cAsset Type: Video\u201d. This ensures the preview logic only runs when it should, avoiding unnecessary processing on images.<\/p>\n<\/li>\n<li>\n<p><strong>The transformation (Create Asset URL).<\/strong> This is where the preview clip is built. You used a specific transformation string to shape the output:<\/p>\n<p><code>so_1,du_8,c_fill,ar_16:9,w_1600,q_auto,f_auto,vc_auto<\/code><\/p>\n<ul>\n<li>\n<code>so_1<\/code>, <code>du_8<\/code> starts 1 second in and keeps only 8 seconds of footage.<\/li>\n<li>\n<code>c_fill<\/code>, <code>ar_16:9<\/code>, <code>w_1600<\/code> crops and resizes the video for a high-resolution desktop hero banner.<\/li>\n<li>\n<code>q_auto<\/code>, <code>f_auto<\/code>, <code>vc_auto<\/code> optimizes quality, format, and codec automatically for the best performance.<\/li>\n<\/ul>\n<\/li>\n<li>\n<p><strong>The handoff (\u201cSend HTTP Request\u201d).<\/strong> Once the preview URL is generated, and the flow POSTs the data to your app\u2019s webhook.<\/p>\n<\/li>\n<\/ul>\n<h3>The Payload<\/h3>\n<p>The \u201cSend HTTP Request\u201d block uses dynamic variables from the previous steps to build a structured payload for the app.<\/p>\n<pre class=\"js-syntax-highlighted\" aria-describedby=\"shcb-language-2\" data-shcb-language-name=\"JSON \/ JSON with Comments\" data-shcb-language-slug=\"json\"><span><code class=\"hljs language-json shcb-wrap-lines\">{\n  <span class=\"hljs-attr\">\"source\"<\/span>: <span class=\"hljs-string\">\"mediaflows\"<\/span>,\n  <span class=\"hljs-attr\">\"event\"<\/span>: <span class=\"hljs-string\">\"video_preview_created\"<\/span>,\n  <span class=\"hljs-attr\">\"asset\"<\/span>: {\n    <span class=\"hljs-attr\">\"publicId\"<\/span>: <span class=\"hljs-string\">\"{{$.Cloudinary_Upload.result.public_id}}\"<\/span>,\n    <span class=\"hljs-attr\">\"title\"<\/span>: <span class=\"hljs-string\">\"{{$.Cloudinary_Upload.result.public_id}}\"<\/span>,\n    <span class=\"hljs-attr\">\"posterPublicId\"<\/span>: <span class=\"hljs-string\">\"{{$.Cloudinary_Upload.result.public_id}}\"<\/span>,\n    <span class=\"hljs-attr\">\"preview\"<\/span>: {\n      <span class=\"hljs-attr\">\"startOffset\"<\/span>: <span class=\"hljs-number\">1<\/span>,\n      <span class=\"hljs-attr\">\"duration\"<\/span>: <span class=\"hljs-number\">8<\/span>,\n      <span class=\"hljs-attr\">\"aspectRatio\"<\/span>: <span class=\"hljs-string\">\"16:9\"<\/span>\n    }\n  },\n  <span class=\"hljs-attr\">\"previewUrl\"<\/span>: <span class=\"hljs-string\">\"{{$.Create_Asset_URL.result.url}}\"<\/span>,\n  <span class=\"hljs-attr\">\"fullVideoUrl\"<\/span>: <span class=\"hljs-string\">\"{{$.Cloudinary_Upload.result.secure_url}}\"<\/span>\n}\n<\/code><\/span><small class=\"shcb-language\" id=\"shcb-language-2\"><span class=\"shcb-language__label\">Code language:<\/span> <span class=\"shcb-language__name\">JSON \/ JSON with Comments<\/span> <span class=\"shcb-language__paren\">(<\/span><span class=\"shcb-language__slug\">json<\/span><span class=\"shcb-language__paren\">)<\/span><\/small><\/pre>\n<h3>The App-Side Handoff<\/h3>\n<p>On the <strong>TanStack Start<\/strong> side, the webhook route receives this payload and maps it into your internal store. This closes the loop between cloud processing and the product UI.<\/p>\n<ul>\n<li>Webhook Route: <a href=\"https:\/\/github.com\/musebe\/MediaFlows-Video-Preview-Demo\/blob\/main\/src\/routes\/api\/mediaflows\/webhook.ts\"><code>src\/routes\/api\/mediaflows\/webhook.ts<\/code><\/a>\n<\/li>\n<li>Asset Store: <a href=\"https:\/\/github.com\/musebe\/MediaFlows-Video-Preview-Demo\/blob\/main\/src\/lib\/mediaflows\/store.ts\"><code>src\/lib\/mediaflows\/store.ts<\/code><\/a>\n<\/li>\n<\/ul>\n<h3>The Result<\/h3>\n<p>Once this loop is active, the workflow is seamless. A user uploads a video, Cloudinary stores it, MediaFlows generates the optimized preview, and the app instantly updates the hero. You get a high-motion background that loads nearly as fast as a static image.<\/p>\n<h2>Generating the Smart Preview URL<\/h2>\n<p>Instead of forcing the browser to struggle loading a 50MB background video, your system will generate a lightweight, \u201csmart\u201d preview clip. Cloudinary\u2019s transformation engine handles the trimming, resizing, and optimization in a single request, ensuring the hero stays fast without losing its cinematic feel.<\/p>\n<h3>The Transformation String<\/h3>\n<p>This is the exact string used in the <strong>Create Asset URL<\/strong> block inside MediaFlows. It defines the preview\u2019s DNA:<\/p>\n<p><code>so_1,du_8,c_fill,ar_16:9,w_1600,q_auto,f_auto,vc_auto<\/code><\/p>\n<ul>\n<li>\n<code>so_1<\/code> \/ <code>du_8<\/code> starts at the 1-second mark and captures an 8-second highlight. This avoids opening on a blank frame or a slow fade-in.<\/li>\n<li>\n<code>c_fill<\/code> \/ <code>ar_16:9<\/code> \/ <code>w_1600<\/code> forces the video into a 16:9 aspect ratio and crops it to fill the hero container at a crisp 1600px width.<\/li>\n<li>\n<code>q_auto<\/code> \/ <code>f_auto<\/code> \/ <code>vc_auto<\/code> are the performance trifecta. Cloudinary automatically picks the best quality, format (like WebM or MP4), and codec for the user\u2019s specific browser.<\/li>\n<\/ul>\n<h3>The Webhook Payload<\/h3>\n<p>The app doesn\u2019t guess how to find the preview. MediaFlows explicitly defines it in the \u201cSend HTTP Request\u201d block and hands it over via the webhook.<\/p>\n<pre class=\"js-syntax-highlighted\" aria-describedby=\"shcb-language-3\" data-shcb-language-name=\"JSON \/ JSON with Comments\" data-shcb-language-slug=\"json\"><span><code class=\"hljs language-json shcb-wrap-lines\">{\n  <span class=\"hljs-attr\">\"previewUrl\"<\/span>: <span class=\"hljs-string\">\"{{$.Create_Asset_URL.result.url}}\"<\/span>,\n  <span class=\"hljs-attr\">\"fullVideoUrl\"<\/span>: <span class=\"hljs-string\">\"{{$.Cloudinary_Upload.result.secure_url}}\"<\/span>\n}\n<\/code><\/span><small class=\"shcb-language\" id=\"shcb-language-3\"><span class=\"shcb-language__label\">Code language:<\/span> <span class=\"shcb-language__name\">JSON \/ JSON with Comments<\/span> <span class=\"shcb-language__paren\">(<\/span><span class=\"shcb-language__slug\">json<\/span><span class=\"shcb-language__paren\">)<\/span><\/small><\/pre>\n<p>This split is crucial: <code>previewUrl<\/code> is the fast track for the background, while <code>fullVideoUrl<\/code> is kept in reserve for the high-quality source.<\/p>\n<h3>Mapping the Asset<\/h3>\n<p>To make the data usable, you\u2019ll map the incoming webhook payload into a consistent frontend-friendly object. This logic ensures the Hero component doesn\u2019t need to know if the data came from a real-time upload or a fallback.<\/p>\n<blockquote>\n<p>View the file <a href=\"https:\/\/github.com\/musebe\/MediaFlows-Video-Preview-Demo\/blob\/main\/src\/lib\/video\/hero-mapper.ts\">here<\/a>.<\/p>\n<\/blockquote>\n<pre class=\"js-syntax-highlighted\" aria-describedby=\"shcb-language-4\" data-shcb-language-name=\"JavaScript\" data-shcb-language-slug=\"javascript\"><span><code class=\"hljs language-javascript shcb-wrap-lines\"><span class=\"hljs-keyword\">export<\/span> <span class=\"hljs-function\"><span class=\"hljs-keyword\">function<\/span> <span class=\"hljs-title\">mapMediaFlowsAssetToHeroVideoAsset<\/span>(<span class=\"hljs-params\">\n  asset: MediaFlowsPreviewAsset\n<\/span>): <span class=\"hljs-title\">HeroVideoAsset<\/span> <\/span>{\n  <span class=\"hljs-keyword\">return<\/span> {\n    <span class=\"hljs-attr\">publicId<\/span>: asset.publicId,\n    <span class=\"hljs-attr\">title<\/span>: asset.title,\n    <span class=\"hljs-attr\">posterPublicId<\/span>: asset.posterPublicId,\n    <span class=\"hljs-attr\">startOffset<\/span>: asset.preview.startOffset,\n    <span class=\"hljs-attr\">duration<\/span>: asset.preview.duration,\n    <span class=\"hljs-attr\">aspectRatio<\/span>: asset.preview.aspectRatio || <span class=\"hljs-string\">\"16:9\"<\/span>,\n    <span class=\"hljs-attr\">previewUrl<\/span>: asset.previewUrl,\n    <span class=\"hljs-attr\">fullVideoUrl<\/span>: asset.secureUrl,\n    <span class=\"hljs-attr\">posterImageUrl<\/span>: asset.previewUrl,\n  };\n}\n<\/code><\/span><small class=\"shcb-language\" id=\"shcb-language-4\"><span class=\"shcb-language__label\">Code language:<\/span> <span class=\"shcb-language__name\">JavaScript<\/span> <span class=\"shcb-language__paren\">(<\/span><span class=\"shcb-language__slug\">javascript<\/span><span class=\"shcb-language__paren\">)<\/span><\/small><\/pre>\n<h3>Building the Final Hero URLs<\/h3>\n<p>The rendering layer follows a two-path strategy for resilience. If the webhook provides a pregenerated URL, you\u2019ll use it directly. If not, fall back to generating responsive sources on the fly.<\/p>\n<blockquote>\n<p>View the file <a href=\"https:\/\/github.com\/musebe\/MediaFlows-Video-Preview-Demo\/blob\/main\/src\/lib\/cloudinary\/urls.ts\">here<\/a>.<\/p>\n<\/blockquote>\n<pre class=\"js-syntax-highlighted\" aria-describedby=\"shcb-language-5\" data-shcb-language-name=\"JavaScript\" data-shcb-language-slug=\"javascript\"><span><code class=\"hljs language-javascript shcb-wrap-lines\"><span class=\"hljs-keyword\">export<\/span> <span class=\"hljs-function\"><span class=\"hljs-keyword\">function<\/span> <span class=\"hljs-title\">buildHeroVideoUrls<\/span>(<span class=\"hljs-params\">asset: HeroVideoAsset<\/span>): <span class=\"hljs-title\">HeroVideoUrls<\/span> <\/span>{\n  <span class=\"hljs-comment\">\/\/ Path 1: Use the PowerFlow-generated preview directly<\/span>\n  <span class=\"hljs-keyword\">if<\/span> (asset.previewUrl &amp;&amp; asset.fullVideoUrl) {\n    <span class=\"hljs-keyword\">return<\/span> {\n      <span class=\"hljs-attr\">previewSources<\/span>: &#91;{ <span class=\"hljs-attr\">src<\/span>: asset.previewUrl, <span class=\"hljs-attr\">width<\/span>: <span class=\"hljs-number\">1600<\/span> }],\n      <span class=\"hljs-attr\">fullVideoUrl<\/span>: asset.fullVideoUrl,\n      <span class=\"hljs-attr\">posterImageUrl<\/span>: asset.posterImageUrl || asset.previewUrl,\n    };\n  }\n\n  <span class=\"hljs-comment\">\/\/ Path 2: Local fallback generation for demo assets<\/span>\n  <span class=\"hljs-keyword\">const<\/span> startOffset = asset.startOffset ?? <span class=\"hljs-number\">0<\/span>;\n  <span class=\"hljs-keyword\">const<\/span> duration = asset.duration ?? <span class=\"hljs-number\">5<\/span>;\n  <span class=\"hljs-keyword\">const<\/span> aspectRatio = asset.aspectRatio ?? <span class=\"hljs-string\">\"16:9\"<\/span>;\n\n  <span class=\"hljs-keyword\">const<\/span> previewSources: ResponsiveVideoSource&#91;] = &#91;\n    buildPreviewSource(\n      asset.publicId,\n      <span class=\"hljs-number\">640<\/span>,\n      startOffset,\n      duration,\n      aspectRatio,\n      <span class=\"hljs-string\">\"(max-width: 640px)\"<\/span>\n    ),\n    buildPreviewSource(\n      asset.publicId,\n      <span class=\"hljs-number\">960<\/span>,\n      startOffset,\n      duration,\n      aspectRatio,\n      <span class=\"hljs-string\">\"(max-width: 1024px)\"<\/span>\n    ),\n    buildPreviewSource(\n      asset.publicId,\n      <span class=\"hljs-number\">1600<\/span>,\n      startOffset,\n      duration,\n      aspectRatio\n    ),\n  ];\n\n  <span class=\"hljs-keyword\">return<\/span> {\n    previewSources,\n    <span class=\"hljs-attr\">fullVideoUrl<\/span>: buildVideoUrl(asset.publicId, fullTransform),\n    <span class=\"hljs-attr\">posterImageUrl<\/span>: buildVideoPosterUrl(posterPublicId, posterTransform),\n  };\n}\n<\/code><\/span><small class=\"shcb-language\" id=\"shcb-language-5\"><span class=\"shcb-language__label\">Code language:<\/span> <span class=\"shcb-language__name\">JavaScript<\/span> <span class=\"shcb-language__paren\">(<\/span><span class=\"shcb-language__slug\">javascript<\/span><span class=\"shcb-language__paren\">)<\/span><\/small><\/pre>\n<p>This works because most implementations either hardcode URLs or rebuild them entirely in the frontend. This hybrid approach is better:<\/p>\n<ol>\n<li>Real uploads use the PowerFlow-generated URL exactly as it was processed.<\/li>\n<li>The app still functions with fallback demo assets without waiting for a webhook.<\/li>\n<li>The complexity of video manipulation stays in MediaFlows, where it belongs.<\/li>\n<\/ol>\n<h2>Sending PowerFlow Data Back With a Webhook<\/h2>\n<p>Once MediaFlows generates the preview URL, the app needs a way to ingest it. This is the webhook\u2019s job: the <strong>Send HTTP Request<\/strong> block in PowerFlows posts the result back to our application, turning a cloud-processed asset into live UI state.<\/p>\n<h3>The Payload Contract<\/h3>\n<p>To keep the integration predictable, use a structured JSON payload in the PowerFlow. This ensures the app receives the PublicID, the optimized preview URL, and the full original source in one pass.<\/p>\n<pre class=\"js-syntax-highlighted\" aria-describedby=\"shcb-language-6\" data-shcb-language-name=\"JSON \/ JSON with Comments\" data-shcb-language-slug=\"json\"><span><code class=\"hljs language-json shcb-wrap-lines\">{\n  <span class=\"hljs-attr\">\"source\"<\/span>: <span class=\"hljs-string\">\"mediaflows\"<\/span>,\n  <span class=\"hljs-attr\">\"event\"<\/span>: <span class=\"hljs-string\">\"video_preview_created\"<\/span>,\n  <span class=\"hljs-attr\">\"asset\"<\/span>: {\n    <span class=\"hljs-attr\">\"publicId\"<\/span>: <span class=\"hljs-string\">\"{{$.Cloudinary_Upload.result.public_id}}\"<\/span>,\n    <span class=\"hljs-attr\">\"title\"<\/span>: <span class=\"hljs-string\">\"{{$.Cloudinary_Upload.result.public_id}}\"<\/span>,\n    <span class=\"hljs-attr\">\"posterPublicId\"<\/span>: <span class=\"hljs-string\">\"{{$.Cloudinary_Upload.result.public_id}}\"<\/span>,\n    <span class=\"hljs-attr\">\"preview\"<\/span>: {\n      <span class=\"hljs-attr\">\"startOffset\"<\/span>: <span class=\"hljs-number\">1<\/span>,\n      <span class=\"hljs-attr\">\"duration\"<\/span>: <span class=\"hljs-number\">8<\/span>,\n      <span class=\"hljs-attr\">\"aspectRatio\"<\/span>: <span class=\"hljs-string\">\"16:9\"<\/span>\n    }\n  },\n  <span class=\"hljs-attr\">\"previewUrl\"<\/span>: <span class=\"hljs-string\">\"{{$.Create_Asset_URL.result.url}}\"<\/span>,\n  <span class=\"hljs-attr\">\"fullVideoUrl\"<\/span>: <span class=\"hljs-string\">\"{{$.Cloudinary_Upload.result.secure_url}}\"<\/span>\n}\n<\/code><\/span><small class=\"shcb-language\" id=\"shcb-language-6\"><span class=\"shcb-language__label\">Code language:<\/span> <span class=\"shcb-language__name\">JSON \/ JSON with Comments<\/span> <span class=\"shcb-language__paren\">(<\/span><span class=\"shcb-language__slug\">json<\/span><span class=\"shcb-language__paren\">)<\/span><\/small><\/pre>\n<h3>The Webhook Route<\/h3>\n<p>The app receives this data at <code>src\/routes\/api\/mediaflows\/webhook.ts<\/code>. This route is intentionally lightweight; it doesn\u2019t process media or rebuild URLs. It simply validates the incoming JSON and maps it to our internal store.<\/p>\n<blockquote>\n<p>View the file <a href=\"https:\/\/github.com\/musebe\/MediaFlows-Video-Preview-Demo\/blob\/main\/src\/routes\/api\/mediaflows\/webhook.ts\">here<\/a>.<\/p>\n<\/blockquote>\n<pre class=\"js-syntax-highlighted\" aria-describedby=\"shcb-language-7\" data-shcb-language-name=\"JavaScript\" data-shcb-language-slug=\"javascript\"><span><code class=\"hljs language-javascript shcb-wrap-lines\"><span class=\"hljs-keyword\">export<\/span> <span class=\"hljs-keyword\">const<\/span> Route = createFileRoute(<span class=\"hljs-string\">\"\/api\/mediaflows\/webhook\"<\/span>)({\n  <span class=\"hljs-attr\">server<\/span>: {\n    <span class=\"hljs-attr\">handlers<\/span>: {\n      <span class=\"hljs-attr\">POST<\/span>: <span class=\"hljs-keyword\">async<\/span> ({ request }) =&gt; {\n        <span class=\"hljs-keyword\">const<\/span> body = (<span class=\"hljs-keyword\">await<\/span> request.json()) <span class=\"hljs-keyword\">as<\/span> WebhookBody\n\n        <span class=\"hljs-keyword\">const<\/span> asset: MediaFlowsPreviewAsset = {\n          <span class=\"hljs-attr\">publicId<\/span>: body.asset.publicId,\n          <span class=\"hljs-attr\">title<\/span>: body.asset.title,\n          <span class=\"hljs-attr\">posterPublicId<\/span>: body.asset.posterPublicId,\n          <span class=\"hljs-attr\">resourceType<\/span>: <span class=\"hljs-string\">\"video\"<\/span>,\n          <span class=\"hljs-attr\">secureUrl<\/span>: body.fullVideoUrl,\n          <span class=\"hljs-attr\">previewUrl<\/span>: body.previewUrl,\n          <span class=\"hljs-attr\">preview<\/span>: {\n            <span class=\"hljs-attr\">startOffset<\/span>: body.asset.preview.startOffset,\n            <span class=\"hljs-attr\">duration<\/span>: body.asset.preview.duration,\n            <span class=\"hljs-attr\">aspectRatio<\/span>: body.asset.preview.aspectRatio || <span class=\"hljs-string\">\"16:9\"<\/span>,\n          },\n        }\n\n        setLatestMediaFlowsAsset(asset)\n\n        <span class=\"hljs-keyword\">return<\/span> Response.json({ <span class=\"hljs-attr\">ok<\/span>: <span class=\"hljs-literal\">true<\/span>, <span class=\"hljs-attr\">saved<\/span>: <span class=\"hljs-literal\">true<\/span> })\n      },\n    },\n  },\n})\n<\/code><\/span><small class=\"shcb-language\" id=\"shcb-language-7\"><span class=\"shcb-language__label\">Code language:<\/span> <span class=\"shcb-language__name\">JavaScript<\/span> <span class=\"shcb-language__paren\">(<\/span><span class=\"shcb-language__slug\">javascript<\/span><span class=\"shcb-language__paren\">)<\/span><\/small><\/pre>\n<h3>State Management and Handoff<\/h3>\n<p>Once the webhook is called, the asset is saved in a simple store at <code>src\/lib\/mediaflows\/store.ts<\/code> (<a href=\"https:\/\/github.com\/musebe\/MediaFlows-Video-Preview-Demo\/blob\/main\/src\/lib\/mediaflows\/store.ts\">GitHub<\/a>).<\/p>\n<p>For this demo, you\u2019ll use an in-memory variable. While a production app would use Redis or Postgres, this is enough to prove the loop: The webhook writes, and the homepage loader reads.<\/p>\n<p>The final bridge to the UI happens in <code>src\/lib\/video\/preview-payload.ts<\/code>. This logic ensures the page stays stable by defaulting to a fallback asset if no recent upload is found.<\/p>\n<blockquote>\n<p>View the file <a href=\"https:\/\/github.com\/musebe\/MediaFlows-Video-Preview-Demo\/blob\/main\/src\/lib\/video\/preview-payload.ts\">here<\/a>.<\/p>\n<\/blockquote>\n<pre class=\"js-syntax-highlighted\" aria-describedby=\"shcb-language-8\" data-shcb-language-name=\"JavaScript\" data-shcb-language-slug=\"javascript\"><span><code class=\"hljs language-javascript shcb-wrap-lines\"><span class=\"hljs-keyword\">export<\/span> <span class=\"hljs-function\"><span class=\"hljs-keyword\">function<\/span> <span class=\"hljs-title\">getHeroPreviewPayload<\/span>(<span class=\"hljs-params\"><\/span>): <span class=\"hljs-title\">HeroPreviewPayload<\/span> <\/span>{\n  <span class=\"hljs-keyword\">const<\/span> latestAsset = getLatestMediaFlowsAsset();\n  <span class=\"hljs-keyword\">const<\/span> mediaFlowsAsset = latestAsset || getMockMediaFlowsAsset();\n  <span class=\"hljs-keyword\">const<\/span> asset = mapMediaFlowsAssetToHeroVideoAsset(mediaFlowsAsset);\n\n  <span class=\"hljs-keyword\">return<\/span> {\n    asset,\n    <span class=\"hljs-attr\">eyebrow<\/span>: <span class=\"hljs-string\">\"Smart video previews\"<\/span>,\n    <span class=\"hljs-attr\">heading<\/span>: <span class=\"hljs-string\">\"High-performance video hero previews\"<\/span>,\n    <span class=\"hljs-attr\">subheading<\/span>:\n      <span class=\"hljs-string\">\"Use a short Cloudinary clip for the first impression, then load the full video later.\"<\/span>,\n  };\n}\n<\/code><\/span><small class=\"shcb-language\" id=\"shcb-language-8\"><span class=\"shcb-language__label\">Code language:<\/span> <span class=\"shcb-language__name\">JavaScript<\/span> <span class=\"shcb-language__paren\">(<\/span><span class=\"shcb-language__slug\">javascript<\/span><span class=\"shcb-language__paren\">)<\/span><\/small><\/pre>\n<p>This webhook loop is what makes the UI dynamic. Without it, your upload and app are disconnected silos. With it, the flow is complete: The user uploads, MediaFlows automates the heavy lifting, and the app instantly reflects the optimized result.<\/p>\n<h2>Rendering the Hero Video Responsively<\/h2>\n<p>At this stage, the app has exactly what it needs: a preview URL, a full video URL, and the layout metadata. The final step is turning that data into a responsive, lightweight hero background that can update the moment a new upload is processed.<\/p>\n<h3>The Rendering Strategy<\/h3>\n<p>The hero uses a dual-path strategy to ensure the homepage remains stable.<\/p>\n<ul>\n<li>\n<strong>Webhook path.<\/strong> If a fresh upload exists, the hero uses the <code>previewUrl<\/code> provided by MediaFlows.<\/li>\n<li>\n<strong>Fallback path.<\/strong> If no recent upload is found, the hero defaults to a mock asset and generates the delivery URLs locally.<\/li>\n<\/ul>\n<h3>Mapping the Asset<\/h3>\n<p>To keep the component logic \u201cdumb\u201d and focused only on display, map the raw data into a normalized shape. This ensures the Hero component never has to parse a PowerFlows payload directly.<\/p>\n<blockquote>\n<p>View the file <a href=\"https:\/\/github.com\/musebe\/MediaFlows-Video-Preview-Demo\/blob\/main\/src\/lib\/video\/hero-mapper.ts\">here<\/a>.<\/p>\n<\/blockquote>\n<pre class=\"js-syntax-highlighted\" aria-describedby=\"shcb-language-9\" data-shcb-language-name=\"JavaScript\" data-shcb-language-slug=\"javascript\"><span><code class=\"hljs language-javascript shcb-wrap-lines\"><span class=\"hljs-keyword\">export<\/span> <span class=\"hljs-function\"><span class=\"hljs-keyword\">function<\/span> <span class=\"hljs-title\">mapMediaFlowsAssetToHeroVideoAsset<\/span>(<span class=\"hljs-params\">\n  asset: MediaFlowsPreviewAsset\n<\/span>): <span class=\"hljs-title\">HeroVideoAsset<\/span> <\/span>{\n  <span class=\"hljs-keyword\">return<\/span> {\n    <span class=\"hljs-attr\">publicId<\/span>: asset.publicId,\n    <span class=\"hljs-attr\">title<\/span>: asset.title,\n    <span class=\"hljs-attr\">posterPublicId<\/span>: asset.posterPublicId,\n    <span class=\"hljs-attr\">startOffset<\/span>: asset.preview.startOffset,\n    <span class=\"hljs-attr\">duration<\/span>: asset.preview.duration,\n    <span class=\"hljs-attr\">aspectRatio<\/span>: asset.preview.aspectRatio || <span class=\"hljs-string\">\"16:9\"<\/span>,\n    <span class=\"hljs-attr\">previewUrl<\/span>: asset.previewUrl,\n    <span class=\"hljs-attr\">fullVideoUrl<\/span>: asset.secureUrl,\n    <span class=\"hljs-attr\">posterImageUrl<\/span>: asset.previewUrl,\n  };\n}\n<\/code><\/span><small class=\"shcb-language\" id=\"shcb-language-9\"><span class=\"shcb-language__label\">Code language:<\/span> <span class=\"shcb-language__name\">JavaScript<\/span> <span class=\"shcb-language__paren\">(<\/span><span class=\"shcb-language__slug\">javascript<\/span><span class=\"shcb-language__paren\">)<\/span><\/small><\/pre>\n<h3>Building Responsive Sources<\/h3>\n<p>The URL builder handles the heavy lifting for different screen sizes. If MediaFlows has already done the work, you\u2019ll use that URL. Otherwise, generate a responsive array of sources to ensure mobile users aren\u2019t downloading desktop-sized clips.<\/p>\n<blockquote>\n<p>View the file <a href=\"https:\/\/github.com\/musebe\/MediaFlows-Video-Preview-Demo\/blob\/main\/src\/lib\/cloudinary\/urls.ts\">here<\/a>.<\/p>\n<\/blockquote>\n<pre class=\"js-syntax-highlighted\" aria-describedby=\"shcb-language-10\" data-shcb-language-name=\"JavaScript\" data-shcb-language-slug=\"javascript\"><span><code class=\"hljs language-javascript shcb-wrap-lines\"><span class=\"hljs-keyword\">export<\/span> <span class=\"hljs-function\"><span class=\"hljs-keyword\">function<\/span> <span class=\"hljs-title\">buildHeroVideoUrls<\/span>(<span class=\"hljs-params\">asset: HeroVideoAsset<\/span>): <span class=\"hljs-title\">HeroVideoUrls<\/span> <\/span>{\n  <span class=\"hljs-keyword\">if<\/span> (asset.previewUrl &amp;&amp; asset.fullVideoUrl) {\n    <span class=\"hljs-keyword\">return<\/span> {\n      <span class=\"hljs-attr\">previewSources<\/span>: &#91;{ <span class=\"hljs-attr\">src<\/span>: asset.previewUrl, <span class=\"hljs-attr\">width<\/span>: <span class=\"hljs-number\">1600<\/span> }],\n      <span class=\"hljs-attr\">fullVideoUrl<\/span>: asset.fullVideoUrl,\n      <span class=\"hljs-attr\">posterImageUrl<\/span>: asset.posterImageUrl || asset.previewUrl,\n    };\n  }\n\n  <span class=\"hljs-keyword\">const<\/span> startOffset = asset.startOffset ?? <span class=\"hljs-number\">0<\/span>;\n  <span class=\"hljs-keyword\">const<\/span> duration = asset.duration ?? <span class=\"hljs-number\">5<\/span>;\n  <span class=\"hljs-keyword\">const<\/span> aspectRatio = asset.aspectRatio ?? <span class=\"hljs-string\">\"16:9\"<\/span>;\n\n  <span class=\"hljs-keyword\">const<\/span> previewSources: ResponsiveVideoSource&#91;] = &#91;\n    buildPreviewSource(\n      asset.publicId,\n      <span class=\"hljs-number\">640<\/span>,\n      startOffset,\n      duration,\n      aspectRatio,\n      <span class=\"hljs-string\">\"(max-width: 640px)\"<\/span>\n    ),\n    buildPreviewSource(\n      asset.publicId,\n      <span class=\"hljs-number\">960<\/span>,\n      startOffset,\n      duration,\n      aspectRatio,\n      <span class=\"hljs-string\">\"(max-width: 1024px)\"<\/span>\n    ),\n    buildPreviewSource(\n      asset.publicId,\n      <span class=\"hljs-number\">1600<\/span>,\n      startOffset,\n      duration,\n      aspectRatio\n    ),\n  ];\n\n  <span class=\"hljs-keyword\">return<\/span> {\n    previewSources,\n    <span class=\"hljs-attr\">fullVideoUrl<\/span>: buildVideoUrl(asset.publicId, fullTransform),\n    <span class=\"hljs-attr\">posterImageUrl<\/span>: buildVideoPosterUrl(posterPublicId, posterTransform),\n  };\n}\n<\/code><\/span><small class=\"shcb-language\" id=\"shcb-language-10\"><span class=\"shcb-language__label\">Code language:<\/span> <span class=\"shcb-language__name\">JavaScript<\/span> <span class=\"shcb-language__paren\">(<\/span><span class=\"shcb-language__slug\">javascript<\/span><span class=\"shcb-language__paren\">)<\/span><\/small><\/pre>\n<h3>The Hero Component Logic<\/h3>\n<p>For the actual video tag, use standard HTML5 attributes to ensure a smooth, mobile-friendly background. <code>muted<\/code> is non-negotiable for autoplay, while <code>playsInline<\/code> prevents mobile browsers from forcing the video into fullscreen.<\/p>\n<blockquote>\n<p>View the file <a href=\"https:\/\/github.com\/musebe\/MediaFlows-Video-Preview-Demo\/blob\/main\/src\/components\/hero\/HeroVideoBackground.tsx\">here<\/a>.<\/p>\n<\/blockquote>\n<pre class=\"js-syntax-highlighted\" aria-describedby=\"shcb-language-11\" data-shcb-language-name=\"HTML, XML\" data-shcb-language-slug=\"xml\"><span><code class=\"hljs language-xml shcb-wrap-lines\">{\n  asset.previewUrl ? (\n    <span class=\"hljs-tag\">&lt;<span class=\"hljs-name\">video<\/span>\n      <span class=\"hljs-attr\">className<\/span>=<span class=\"hljs-string\">\"absolute inset-0 h-full w-full object-cover\"<\/span>\n      <span class=\"hljs-attr\">autoPlay<\/span>\n      <span class=\"hljs-attr\">muted<\/span>\n      <span class=\"hljs-attr\">loop<\/span>\n      <span class=\"hljs-attr\">playsInline<\/span>\n      <span class=\"hljs-attr\">preload<\/span>=<span class=\"hljs-string\">\"metadata\"<\/span>\n      <span class=\"hljs-attr\">poster<\/span>=<span class=\"hljs-string\">{urls.posterImageUrl}<\/span>\n      <span class=\"hljs-attr\">aria-label<\/span>=<span class=\"hljs-string\">{asset.title}<\/span>\n    &gt;<\/span>\n      {urls.previewSources.map((source) =&gt; (\n        <span class=\"hljs-tag\">&lt;<span class=\"hljs-name\">source<\/span>\n          <span class=\"hljs-attr\">key<\/span>=<span class=\"hljs-string\">{<\/span>`${<span class=\"hljs-attr\">source.width<\/span>}<span class=\"hljs-attr\">-<\/span>${<span class=\"hljs-attr\">source.media<\/span> || \"<span class=\"hljs-attr\">default<\/span>\"}`}\n          <span class=\"hljs-attr\">src<\/span>=<span class=\"hljs-string\">{source.src}<\/span>\n          <span class=\"hljs-attr\">media<\/span>=<span class=\"hljs-string\">{source.media}<\/span>\n          <span class=\"hljs-attr\">type<\/span>=<span class=\"hljs-string\">\"video\/mp4\"<\/span>\n        \/&gt;<\/span>\n      ))}\n    <span class=\"hljs-tag\">&lt;\/<span class=\"hljs-name\">video<\/span>&gt;<\/span>\n  ) : (\n    <span class=\"hljs-tag\">&lt;<span class=\"hljs-name\">video<\/span>\n      <span class=\"hljs-attr\">id<\/span>=<span class=\"hljs-string\">{playerId}<\/span>\n      <span class=\"hljs-attr\">className<\/span>=<span class=\"hljs-string\">\"cld-video-player absolute inset-0 h-full w-full object-cover\"<\/span>\n      <span class=\"hljs-attr\">muted<\/span>\n      <span class=\"hljs-attr\">playsInline<\/span>\n      <span class=\"hljs-attr\">preload<\/span>=<span class=\"hljs-string\">\"metadata\"<\/span>\n      <span class=\"hljs-attr\">poster<\/span>=<span class=\"hljs-string\">{urls.posterImageUrl}<\/span>\n    \/&gt;<\/span>\n  );\n}\n<\/code><\/span><small class=\"shcb-language\" id=\"shcb-language-11\"><span class=\"shcb-language__label\">Code language:<\/span> <span class=\"shcb-language__name\">HTML, XML<\/span> <span class=\"shcb-language__paren\">(<\/span><span class=\"shcb-language__slug\">xml<\/span><span class=\"shcb-language__paren\">)<\/span><\/small><\/pre>\n<h3>Live Updates Without Reloading<\/h3>\n<p>The \u201cwow\u201d factor of this demo is the real-time update. Instead of a hard refresh, the <strong>Upload Widget<\/strong> polls the app until it sees the new asset in the store. It then dispatches a custom event that the homepage route listens for to trigger an invalidation.<\/p>\n<blockquote>\n<p>View the file <a href=\"https:\/\/github.com\/musebe\/MediaFlows-Video-Preview-Demo\/blob\/main\/src\/components\/cloudinary\/UploadWidgetButton.tsx\">here<\/a>.<\/p>\n<\/blockquote>\n<pre class=\"js-syntax-highlighted\" aria-describedby=\"shcb-language-12\" data-shcb-language-name=\"JavaScript\" data-shcb-language-slug=\"javascript\"><span><code class=\"hljs language-javascript shcb-wrap-lines\"><span class=\"hljs-keyword\">if<\/span> (result?.event === <span class=\"hljs-string\">\"success\"<\/span>) {\n  <span class=\"hljs-keyword\">const<\/span> uploadedPublicId = result?.info?.public_id;\n  <span class=\"hljs-keyword\">if<\/span> (!uploadedPublicId) <span class=\"hljs-keyword\">return<\/span>;\n\n  <span class=\"hljs-keyword\">const<\/span> found = <span class=\"hljs-keyword\">await<\/span> waitForLatestAsset(uploadedPublicId);\n  <span class=\"hljs-keyword\">if<\/span> (found) {\n    notifyLatestAssetReady(uploadedPublicId);\n  }\n}\n<\/code><\/span><small class=\"shcb-language\" id=\"shcb-language-12\"><span class=\"shcb-language__label\">Code language:<\/span> <span class=\"shcb-language__name\">JavaScript<\/span> <span class=\"shcb-language__paren\">(<\/span><span class=\"shcb-language__slug\">javascript<\/span><span class=\"shcb-language__paren\">)<\/span><\/small><\/pre>\n<blockquote>\n<p>View the file <a href=\"https:\/\/github.com\/musebe\/MediaFlows-Video-Preview-Demo\/blob\/main\/src\/routes\/index.tsx\">here<\/a>.<\/p>\n<\/blockquote>\n<pre class=\"js-syntax-highlighted\" aria-describedby=\"shcb-language-13\" data-shcb-language-name=\"JavaScript\" data-shcb-language-slug=\"javascript\"><span><code class=\"hljs language-javascript shcb-wrap-lines\">useEffect(<span class=\"hljs-function\"><span class=\"hljs-params\">()<\/span> =&gt;<\/span> {\n  <span class=\"hljs-keyword\">const<\/span> handler = <span class=\"hljs-function\"><span class=\"hljs-params\">()<\/span> =&gt;<\/span> {\n    router.invalidate();\n  };\n  <span class=\"hljs-built_in\">window<\/span>.addEventListener(<span class=\"hljs-string\">\"mediaflows:asset-ready\"<\/span>, handler);\n  <span class=\"hljs-keyword\">return<\/span> <span class=\"hljs-function\"><span class=\"hljs-params\">()<\/span> =&gt;<\/span> {\n    <span class=\"hljs-built_in\">window<\/span>.removeEventListener(<span class=\"hljs-string\">\"mediaflows:asset-ready\"<\/span>, handler);\n  };\n}, &#91;router]);\n<\/code><\/span><small class=\"shcb-language\" id=\"shcb-language-13\"><span class=\"shcb-language__label\">Code language:<\/span> <span class=\"shcb-language__name\">JavaScript<\/span> <span class=\"shcb-language__paren\">(<\/span><span class=\"shcb-language__slug\">javascript<\/span><span class=\"shcb-language__paren\">)<\/span><\/small><\/pre>\n<p>The UI stays performant because it isn\u2019t burdened by media logic. <strong>Cloudinary<\/strong> handles the delivery, <strong>PowerFlows<\/strong> manages the preview generation, and <strong>TanStack Start<\/strong> coordinates the state. The result is a hero background that feels instantaneous and cinematic.<\/p>\n<h2>Making the Upload Feel Instant<\/h2>\n<p>Most upload demos end at the storage bucket. You upload a file, the UI shows it\u2019s a \u201cSuccess\u201d, and then\u2026 nothing. You\u2019re left manually refreshing to see the results. This breaks the user experience.<\/p>\n<p>This demo treats the upload as the trigger for a larger workflow. The goal is to bridge the gap between the file reaching Cloudinary and the processed preview appearing in the hero section.<\/p>\n<h3>The UX Problem: The Processing Gap<\/h3>\n<p>There\u2019s a distinct window of time between the widget finishing the upload and the PowerFlow completing its processing. If you reload too early, you still see the old asset. Solve this by waiting for proof of the update rather than using a blind timeout.<\/p>\n<h3>The Upload Callback<\/h3>\n<p>The logic begins in the <strong>Upload Widget<\/strong> callback. Instead of finishing when the file is sent, the component enters a \u201cwaiting\u201d state while it polls the application for the processed result.<\/p>\n<blockquote>\n<p>View the file <a href=\"https:\/\/github.com\/musebe\/MediaFlows-Video-Preview-Demo\/blob\/main\/src\/components\/cloudinary\/UploadWidgetButton.tsx\">here<\/a>.<\/p>\n<\/blockquote>\n<pre class=\"js-syntax-highlighted\" aria-describedby=\"shcb-language-14\" data-shcb-language-name=\"JavaScript\" data-shcb-language-slug=\"javascript\"><span><code class=\"hljs language-javascript shcb-wrap-lines\"><span class=\"hljs-keyword\">if<\/span> (result?.event === <span class=\"hljs-string\">\"success\"<\/span>) {\n  <span class=\"hljs-keyword\">const<\/span> uploadedPublicId = result?.info?.public_id;\n  <span class=\"hljs-keyword\">if<\/span> (!uploadedPublicId) <span class=\"hljs-keyword\">return<\/span>;\n\n  <span class=\"hljs-keyword\">if<\/span> (mounted) setStatus(<span class=\"hljs-string\">\"waiting\"<\/span>);\n\n  <span class=\"hljs-comment\">\/\/ The wait: Poll the app until the backend store matches our new upload<\/span>\n  <span class=\"hljs-keyword\">const<\/span> found = <span class=\"hljs-keyword\">await<\/span> waitForLatestAsset(uploadedPublicId);\n\n  <span class=\"hljs-keyword\">if<\/span> (found) {\n    notifyLatestAssetReady(uploadedPublicId);\n  }\n\n  <span class=\"hljs-keyword\">if<\/span> (mounted) setStatus(<span class=\"hljs-string\">\"ready\"<\/span>);\n}\n<\/code><\/span><small class=\"shcb-language\" id=\"shcb-language-14\"><span class=\"shcb-language__label\">Code language:<\/span> <span class=\"shcb-language__name\">JavaScript<\/span> <span class=\"shcb-language__paren\">(<\/span><span class=\"shcb-language__slug\">javascript<\/span><span class=\"shcb-language__paren\">)<\/span><\/small><\/pre>\n<h3>The Polling Logic<\/h3>\n<p>Use a small helper to check our internal API. This ensures the UI only updates once the webhook has successfully saved the MediaFlows payload to the store.<\/p>\n<pre class=\"js-syntax-highlighted\" aria-describedby=\"shcb-language-15\" data-shcb-language-name=\"JavaScript\" data-shcb-language-slug=\"javascript\"><span><code class=\"hljs language-javascript shcb-wrap-lines\"><span class=\"hljs-keyword\">async<\/span> <span class=\"hljs-function\"><span class=\"hljs-keyword\">function<\/span> <span class=\"hljs-title\">waitForLatestAsset<\/span>(<span class=\"hljs-params\">publicId: string, tries = <span class=\"hljs-number\">15<\/span>, delay = <span class=\"hljs-number\">2000<\/span><\/span>) <\/span>{\n  <span class=\"hljs-keyword\">for<\/span> (<span class=\"hljs-keyword\">let<\/span> i = <span class=\"hljs-number\">0<\/span>; i &lt; tries; i++) {\n    <span class=\"hljs-keyword\">try<\/span> {\n      <span class=\"hljs-keyword\">const<\/span> res = <span class=\"hljs-keyword\">await<\/span> fetch(<span class=\"hljs-string\">\"\/api\/debug\/latest-asset\"<\/span>, { <span class=\"hljs-attr\">cache<\/span>: <span class=\"hljs-string\">\"no-store\"<\/span> });\n      <span class=\"hljs-keyword\">const<\/span> data = <span class=\"hljs-keyword\">await<\/span> res.json();\n\n      <span class=\"hljs-keyword\">if<\/span> (data?.asset?.publicId === publicId) {\n        <span class=\"hljs-keyword\">return<\/span> <span class=\"hljs-literal\">true<\/span>;\n      }\n    } <span class=\"hljs-keyword\">catch<\/span> (error) {\n      <span class=\"hljs-built_in\">console<\/span>.error(<span class=\"hljs-string\">\"&#91;upload-widget] polling error:\"<\/span>, error);\n    }\n    <span class=\"hljs-keyword\">await<\/span> <span class=\"hljs-keyword\">new<\/span> <span class=\"hljs-built_in\">Promise<\/span>(<span class=\"hljs-function\">(<span class=\"hljs-params\">resolve<\/span>) =&gt;<\/span> setTimeout(resolve, delay));\n  }\n  <span class=\"hljs-keyword\">return<\/span> <span class=\"hljs-literal\">false<\/span>;\n}\n<\/code><\/span><small class=\"shcb-language\" id=\"shcb-language-15\"><span class=\"shcb-language__label\">Code language:<\/span> <span class=\"shcb-language__name\">JavaScript<\/span> <span class=\"shcb-language__paren\">(<\/span><span class=\"shcb-language__slug\">javascript<\/span><span class=\"shcb-language__paren\">)<\/span><\/small><\/pre>\n<h3>Route Invalidation Without a Refresh<\/h3>\n<p>Once the asset is confirmed, dispatch a custom browser event: <code>mediaflows:asset-ready<\/code>. The homepage listens for this event and tells the <strong>TanStack Start<\/strong> router to invalidate the current route.<\/p>\n<blockquote>\n<p>View the file <a href=\"https:\/\/github.com\/musebe\/MediaFlows-Video-Preview-Demo\/blob\/main\/src\/routes\/index.tsx\">here<\/a>.<\/p>\n<\/blockquote>\n<pre class=\"js-syntax-highlighted\" aria-describedby=\"shcb-language-16\" data-shcb-language-name=\"JavaScript\" data-shcb-language-slug=\"javascript\"><span><code class=\"hljs language-javascript shcb-wrap-lines\">useEffect(<span class=\"hljs-function\"><span class=\"hljs-params\">()<\/span> =&gt;<\/span> {\n  <span class=\"hljs-keyword\">const<\/span> handler = <span class=\"hljs-function\"><span class=\"hljs-params\">()<\/span> =&gt;<\/span> {\n    router.invalidate(); <span class=\"hljs-comment\">\/\/ Triggers a re-fetch of the route data<\/span>\n  };\n\n  <span class=\"hljs-built_in\">window<\/span>.addEventListener(<span class=\"hljs-string\">\"mediaflows:asset-ready\"<\/span>, handler);\n\n  <span class=\"hljs-keyword\">return<\/span> <span class=\"hljs-function\"><span class=\"hljs-params\">()<\/span> =&gt;<\/span> {\n    <span class=\"hljs-built_in\">window<\/span>.removeEventListener(<span class=\"hljs-string\">\"mediaflows:asset-ready\"<\/span>, handler);\n  };\n}, &#91;router]);\n<\/code><\/span><small class=\"shcb-language\" id=\"shcb-language-16\"><span class=\"shcb-language__label\">Code language:<\/span> <span class=\"shcb-language__name\">JavaScript<\/span> <span class=\"shcb-language__paren\">(<\/span><span class=\"shcb-language__slug\">javascript<\/span><span class=\"shcb-language__paren\">)<\/span><\/small><\/pre>\n<p>When the route invalidates, the loader fetches the newest asset from the store, and the Hero component rerenders with the fresh preview URL. No full browser refresh is required.<\/p>\n<h3>The Roadmap: What\u2019s Next?<\/h3>\n<p>This is a functional demo, but moving toward production would mean hardening a few areas:<\/p>\n<ul>\n<li>Swapping the in-memory store for Redis, Postgres, or Supabase for more durable storage.<\/li>\n<li>Saving cleaner meta titles and richer meta descriptions beyond the <code>public_id<\/code>.<\/li>\n<li>Adding granular upload progress bars and persistent state across deploys for cleaner UX design.<\/li>\n<\/ul>\n<p>The real value here isn\u2019t the hero section itself. It\u2019s the fact that a raw file becomes a product-ready asset automatically. <strong>PowerFlows<\/strong> turns Cloudinary from a storage bucket into a live media workflow engine.<\/p>\n<p>Ready to start building? <a href=\"https:\/\/cloudinary.com\/users\/register_free\">Sign up<\/a> for a Cloudinary account today.<\/p>\n<p><strong>Resources:<\/strong><\/p>\n<ul>\n<li>\n<strong>Live Demo:<\/strong> <a href=\"https:\/\/media-flows-video-preview-demo.vercel.app\/\">media-flows-video-preview-demo.vercel.app<\/a>\n<\/li>\n<li>\n<strong>Source Code:<\/strong> <a href=\"https:\/\/github.com\/musebe\/MediaFlows-Video-Preview-Demo\">GitHub Repository<\/a>\n<\/li>\n<\/ul>\n<\/div>","protected":false},"excerpt":{"rendered":"","protected":false},"author":87,"featured_media":40000,"comment_status":"closed","ping_status":"open","sticky":false,"template":"","format":"standard","meta":{"_acf_changed":false,"_cloudinary_featured_overwrite":false,"footnotes":""},"categories":[1],"tags":[363,303],"class_list":["post-39999","post","type-post","status-publish","format-standard","has-post-thumbnail","hentry","category-uncategorized","tag-media-flows","tag-video"],"acf":[],"yoast_head":"<!-- This site is optimized with the Yoast SEO Premium plugin v25.6 (Yoast SEO v26.9) - https:\/\/yoast.com\/product\/yoast-seo-premium-wordpress\/ -->\n<title>High-Performance Video Backgrounds: Automating Smart Previews With TanStack Start and PowerFlows<\/title>\n<meta name=\"robots\" content=\"index, follow, max-snippet:-1, max-image-preview:large, max-video-preview:-1\" \/>\n<link rel=\"canonical\" href=\"https:\/\/cloudinary.com\/blog\/high-performance-video-backgrounds-smart-previews\" \/>\n<meta property=\"og:locale\" content=\"en_US\" \/>\n<meta property=\"og:type\" content=\"article\" \/>\n<meta property=\"og:title\" content=\"High-Performance Video Backgrounds: Automating Smart Previews With TanStack Start and PowerFlows\" \/>\n<meta property=\"og:url\" content=\"https:\/\/cloudinary.com\/blog\/high-performance-video-backgrounds-smart-previews\" \/>\n<meta property=\"og:site_name\" content=\"Cloudinary Blog\" \/>\n<meta property=\"article:published_time\" content=\"2026-04-23T14:00:00+00:00\" \/>\n<meta property=\"article:modified_time\" content=\"2026-04-23T22:19:20+00:00\" \/>\n<meta property=\"og:image\" content=\"https:\/\/res.cloudinary.com\/cloudinary-marketing\/images\/f_auto,q_auto\/v1775775445\/Blog_High-Performance_Video_Backgrounds__Automating_Smart_Previews_with_TanStack_Start_and_PowerFlows\/Blog_High-Performance_Video_Backgrounds__Automating_Smart_Previews_with_TanStack_Start_and_PowerFlows.jpg?_i=AA\" \/>\n\t<meta property=\"og:image:width\" content=\"2000\" \/>\n\t<meta property=\"og:image:height\" content=\"1100\" \/>\n\t<meta property=\"og:image:type\" content=\"image\/jpeg\" \/>\n<meta name=\"author\" content=\"melindapham\" \/>\n<meta name=\"twitter:card\" content=\"summary_large_image\" \/>\n<script type=\"application\/ld+json\" class=\"yoast-schema-graph\">{\"@context\":\"https:\/\/schema.org\",\"@graph\":[{\"@type\":\"NewsArticle\",\"@id\":\"https:\/\/cloudinary.com\/blog\/high-performance-video-backgrounds-smart-previews#article\",\"isPartOf\":{\"@id\":\"https:\/\/cloudinary.com\/blog\/high-performance-video-backgrounds-smart-previews\"},\"author\":{\"name\":\"melindapham\",\"@id\":\"https:\/\/cloudinary.com\/blog\/#\/schema\/person\/0d5ad601e4c3b5be89245dfb14be42d9\"},\"headline\":\"High-Performance Video Backgrounds: Automating Smart Previews With TanStack Start and PowerFlows\",\"datePublished\":\"2026-04-23T14:00:00+00:00\",\"dateModified\":\"2026-04-23T22:19:20+00:00\",\"mainEntityOfPage\":{\"@id\":\"https:\/\/cloudinary.com\/blog\/high-performance-video-backgrounds-smart-previews\"},\"wordCount\":11,\"publisher\":{\"@id\":\"https:\/\/cloudinary.com\/blog\/#organization\"},\"image\":{\"@id\":\"https:\/\/cloudinary.com\/blog\/high-performance-video-backgrounds-smart-previews#primaryimage\"},\"thumbnailUrl\":\"https:\/\/res.cloudinary.com\/cloudinary-marketing\/images\/f_auto,q_auto\/v1775775445\/Blog_High-Performance_Video_Backgrounds__Automating_Smart_Previews_with_TanStack_Start_and_PowerFlows\/Blog_High-Performance_Video_Backgrounds__Automating_Smart_Previews_with_TanStack_Start_and_PowerFlows.jpg?_i=AA\",\"keywords\":[\"MediaFlows\",\"Video\"],\"inLanguage\":\"en-US\",\"copyrightYear\":\"2026\",\"copyrightHolder\":{\"@id\":\"https:\/\/cloudinary.com\/#organization\"}},{\"@type\":\"WebPage\",\"@id\":\"https:\/\/cloudinary.com\/blog\/high-performance-video-backgrounds-smart-previews\",\"url\":\"https:\/\/cloudinary.com\/blog\/high-performance-video-backgrounds-smart-previews\",\"name\":\"High-Performance Video Backgrounds: Automating Smart Previews With TanStack Start and PowerFlows\",\"isPartOf\":{\"@id\":\"https:\/\/cloudinary.com\/blog\/#website\"},\"primaryImageOfPage\":{\"@id\":\"https:\/\/cloudinary.com\/blog\/high-performance-video-backgrounds-smart-previews#primaryimage\"},\"image\":{\"@id\":\"https:\/\/cloudinary.com\/blog\/high-performance-video-backgrounds-smart-previews#primaryimage\"},\"thumbnailUrl\":\"https:\/\/res.cloudinary.com\/cloudinary-marketing\/images\/f_auto,q_auto\/v1775775445\/Blog_High-Performance_Video_Backgrounds__Automating_Smart_Previews_with_TanStack_Start_and_PowerFlows\/Blog_High-Performance_Video_Backgrounds__Automating_Smart_Previews_with_TanStack_Start_and_PowerFlows.jpg?_i=AA\",\"datePublished\":\"2026-04-23T14:00:00+00:00\",\"dateModified\":\"2026-04-23T22:19:20+00:00\",\"breadcrumb\":{\"@id\":\"https:\/\/cloudinary.com\/blog\/high-performance-video-backgrounds-smart-previews#breadcrumb\"},\"inLanguage\":\"en-US\",\"potentialAction\":[{\"@type\":\"ReadAction\",\"target\":[\"https:\/\/cloudinary.com\/blog\/high-performance-video-backgrounds-smart-previews\"]}]},{\"@type\":\"ImageObject\",\"inLanguage\":\"en-US\",\"@id\":\"https:\/\/cloudinary.com\/blog\/high-performance-video-backgrounds-smart-previews#primaryimage\",\"url\":\"https:\/\/res.cloudinary.com\/cloudinary-marketing\/images\/f_auto,q_auto\/v1775775445\/Blog_High-Performance_Video_Backgrounds__Automating_Smart_Previews_with_TanStack_Start_and_PowerFlows\/Blog_High-Performance_Video_Backgrounds__Automating_Smart_Previews_with_TanStack_Start_and_PowerFlows.jpg?_i=AA\",\"contentUrl\":\"https:\/\/res.cloudinary.com\/cloudinary-marketing\/images\/f_auto,q_auto\/v1775775445\/Blog_High-Performance_Video_Backgrounds__Automating_Smart_Previews_with_TanStack_Start_and_PowerFlows\/Blog_High-Performance_Video_Backgrounds__Automating_Smart_Previews_with_TanStack_Start_and_PowerFlows.jpg?_i=AA\",\"width\":2000,\"height\":1100},{\"@type\":\"BreadcrumbList\",\"@id\":\"https:\/\/cloudinary.com\/blog\/high-performance-video-backgrounds-smart-previews#breadcrumb\",\"itemListElement\":[{\"@type\":\"ListItem\",\"position\":1,\"name\":\"Home\",\"item\":\"https:\/\/cloudinary.com\/blog\/\"},{\"@type\":\"ListItem\",\"position\":2,\"name\":\"High-Performance Video Backgrounds: Automating Smart Previews With TanStack Start and PowerFlows\"}]},{\"@type\":\"WebSite\",\"@id\":\"https:\/\/cloudinary.com\/blog\/#website\",\"url\":\"https:\/\/cloudinary.com\/blog\/\",\"name\":\"Cloudinary Blog\",\"description\":\"\",\"publisher\":{\"@id\":\"https:\/\/cloudinary.com\/blog\/#organization\"},\"potentialAction\":[{\"@type\":\"SearchAction\",\"target\":{\"@type\":\"EntryPoint\",\"urlTemplate\":\"https:\/\/cloudinary.com\/blog\/?s={search_term_string}\"},\"query-input\":{\"@type\":\"PropertyValueSpecification\",\"valueRequired\":true,\"valueName\":\"search_term_string\"}}],\"inLanguage\":\"en-US\"},{\"@type\":\"Organization\",\"@id\":\"https:\/\/cloudinary.com\/blog\/#organization\",\"name\":\"Cloudinary Blog\",\"url\":\"https:\/\/cloudinary.com\/blog\/\",\"logo\":{\"@type\":\"ImageObject\",\"inLanguage\":\"en-US\",\"@id\":\"https:\/\/cloudinary.com\/blog\/#\/schema\/logo\/image\/\",\"url\":\"https:\/\/res.cloudinary.com\/cloudinary-marketing\/images\/f_auto,q_auto\/v1649718331\/Web_Assets\/blog\/cloudinary_logo_for_white_bg_1937437aa7_19374666c7_193742f877\/cloudinary_logo_for_white_bg_1937437aa7_19374666c7_193742f877.png?_i=AA\",\"contentUrl\":\"https:\/\/res.cloudinary.com\/cloudinary-marketing\/images\/f_auto,q_auto\/v1649718331\/Web_Assets\/blog\/cloudinary_logo_for_white_bg_1937437aa7_19374666c7_193742f877\/cloudinary_logo_for_white_bg_1937437aa7_19374666c7_193742f877.png?_i=AA\",\"width\":312,\"height\":60,\"caption\":\"Cloudinary Blog\"},\"image\":{\"@id\":\"https:\/\/cloudinary.com\/blog\/#\/schema\/logo\/image\/\"}},{\"@type\":\"Person\",\"@id\":\"https:\/\/cloudinary.com\/blog\/#\/schema\/person\/0d5ad601e4c3b5be89245dfb14be42d9\",\"name\":\"melindapham\",\"image\":{\"@type\":\"ImageObject\",\"inLanguage\":\"en-US\",\"@id\":\"https:\/\/cloudinary.com\/blog\/#\/schema\/person\/image\/\",\"url\":\"https:\/\/secure.gravatar.com\/avatar\/e6f989fa97fe94be61596259d8629c3df65aec4c7da5c0000f90d810f313d4f4?s=96&d=mm&r=g\",\"contentUrl\":\"https:\/\/secure.gravatar.com\/avatar\/e6f989fa97fe94be61596259d8629c3df65aec4c7da5c0000f90d810f313d4f4?s=96&d=mm&r=g\",\"caption\":\"melindapham\"}}]}<\/script>\n<!-- \/ Yoast SEO Premium plugin. -->","yoast_head_json":{"title":"High-Performance Video Backgrounds: Automating Smart Previews With TanStack Start and PowerFlows","robots":{"index":"index","follow":"follow","max-snippet":"max-snippet:-1","max-image-preview":"max-image-preview:large","max-video-preview":"max-video-preview:-1"},"canonical":"https:\/\/cloudinary.com\/blog\/high-performance-video-backgrounds-smart-previews","og_locale":"en_US","og_type":"article","og_title":"High-Performance Video Backgrounds: Automating Smart Previews With TanStack Start and PowerFlows","og_url":"https:\/\/cloudinary.com\/blog\/high-performance-video-backgrounds-smart-previews","og_site_name":"Cloudinary Blog","article_published_time":"2026-04-23T14:00:00+00:00","article_modified_time":"2026-04-23T22:19:20+00:00","og_image":[{"width":2000,"height":1100,"url":"https:\/\/res.cloudinary.com\/cloudinary-marketing\/images\/f_auto,q_auto\/v1775775445\/Blog_High-Performance_Video_Backgrounds__Automating_Smart_Previews_with_TanStack_Start_and_PowerFlows\/Blog_High-Performance_Video_Backgrounds__Automating_Smart_Previews_with_TanStack_Start_and_PowerFlows.jpg?_i=AA","type":"image\/jpeg"}],"author":"melindapham","twitter_card":"summary_large_image","schema":{"@context":"https:\/\/schema.org","@graph":[{"@type":"NewsArticle","@id":"https:\/\/cloudinary.com\/blog\/high-performance-video-backgrounds-smart-previews#article","isPartOf":{"@id":"https:\/\/cloudinary.com\/blog\/high-performance-video-backgrounds-smart-previews"},"author":{"name":"melindapham","@id":"https:\/\/cloudinary.com\/blog\/#\/schema\/person\/0d5ad601e4c3b5be89245dfb14be42d9"},"headline":"High-Performance Video Backgrounds: Automating Smart Previews With TanStack Start and PowerFlows","datePublished":"2026-04-23T14:00:00+00:00","dateModified":"2026-04-23T22:19:20+00:00","mainEntityOfPage":{"@id":"https:\/\/cloudinary.com\/blog\/high-performance-video-backgrounds-smart-previews"},"wordCount":11,"publisher":{"@id":"https:\/\/cloudinary.com\/blog\/#organization"},"image":{"@id":"https:\/\/cloudinary.com\/blog\/high-performance-video-backgrounds-smart-previews#primaryimage"},"thumbnailUrl":"https:\/\/res.cloudinary.com\/cloudinary-marketing\/images\/f_auto,q_auto\/v1775775445\/Blog_High-Performance_Video_Backgrounds__Automating_Smart_Previews_with_TanStack_Start_and_PowerFlows\/Blog_High-Performance_Video_Backgrounds__Automating_Smart_Previews_with_TanStack_Start_and_PowerFlows.jpg?_i=AA","keywords":["MediaFlows","Video"],"inLanguage":"en-US","copyrightYear":"2026","copyrightHolder":{"@id":"https:\/\/cloudinary.com\/#organization"}},{"@type":"WebPage","@id":"https:\/\/cloudinary.com\/blog\/high-performance-video-backgrounds-smart-previews","url":"https:\/\/cloudinary.com\/blog\/high-performance-video-backgrounds-smart-previews","name":"High-Performance Video Backgrounds: Automating Smart Previews With TanStack Start and PowerFlows","isPartOf":{"@id":"https:\/\/cloudinary.com\/blog\/#website"},"primaryImageOfPage":{"@id":"https:\/\/cloudinary.com\/blog\/high-performance-video-backgrounds-smart-previews#primaryimage"},"image":{"@id":"https:\/\/cloudinary.com\/blog\/high-performance-video-backgrounds-smart-previews#primaryimage"},"thumbnailUrl":"https:\/\/res.cloudinary.com\/cloudinary-marketing\/images\/f_auto,q_auto\/v1775775445\/Blog_High-Performance_Video_Backgrounds__Automating_Smart_Previews_with_TanStack_Start_and_PowerFlows\/Blog_High-Performance_Video_Backgrounds__Automating_Smart_Previews_with_TanStack_Start_and_PowerFlows.jpg?_i=AA","datePublished":"2026-04-23T14:00:00+00:00","dateModified":"2026-04-23T22:19:20+00:00","breadcrumb":{"@id":"https:\/\/cloudinary.com\/blog\/high-performance-video-backgrounds-smart-previews#breadcrumb"},"inLanguage":"en-US","potentialAction":[{"@type":"ReadAction","target":["https:\/\/cloudinary.com\/blog\/high-performance-video-backgrounds-smart-previews"]}]},{"@type":"ImageObject","inLanguage":"en-US","@id":"https:\/\/cloudinary.com\/blog\/high-performance-video-backgrounds-smart-previews#primaryimage","url":"https:\/\/res.cloudinary.com\/cloudinary-marketing\/images\/f_auto,q_auto\/v1775775445\/Blog_High-Performance_Video_Backgrounds__Automating_Smart_Previews_with_TanStack_Start_and_PowerFlows\/Blog_High-Performance_Video_Backgrounds__Automating_Smart_Previews_with_TanStack_Start_and_PowerFlows.jpg?_i=AA","contentUrl":"https:\/\/res.cloudinary.com\/cloudinary-marketing\/images\/f_auto,q_auto\/v1775775445\/Blog_High-Performance_Video_Backgrounds__Automating_Smart_Previews_with_TanStack_Start_and_PowerFlows\/Blog_High-Performance_Video_Backgrounds__Automating_Smart_Previews_with_TanStack_Start_and_PowerFlows.jpg?_i=AA","width":2000,"height":1100},{"@type":"BreadcrumbList","@id":"https:\/\/cloudinary.com\/blog\/high-performance-video-backgrounds-smart-previews#breadcrumb","itemListElement":[{"@type":"ListItem","position":1,"name":"Home","item":"https:\/\/cloudinary.com\/blog\/"},{"@type":"ListItem","position":2,"name":"High-Performance Video Backgrounds: Automating Smart Previews With TanStack Start and PowerFlows"}]},{"@type":"WebSite","@id":"https:\/\/cloudinary.com\/blog\/#website","url":"https:\/\/cloudinary.com\/blog\/","name":"Cloudinary Blog","description":"","publisher":{"@id":"https:\/\/cloudinary.com\/blog\/#organization"},"potentialAction":[{"@type":"SearchAction","target":{"@type":"EntryPoint","urlTemplate":"https:\/\/cloudinary.com\/blog\/?s={search_term_string}"},"query-input":{"@type":"PropertyValueSpecification","valueRequired":true,"valueName":"search_term_string"}}],"inLanguage":"en-US"},{"@type":"Organization","@id":"https:\/\/cloudinary.com\/blog\/#organization","name":"Cloudinary Blog","url":"https:\/\/cloudinary.com\/blog\/","logo":{"@type":"ImageObject","inLanguage":"en-US","@id":"https:\/\/cloudinary.com\/blog\/#\/schema\/logo\/image\/","url":"https:\/\/res.cloudinary.com\/cloudinary-marketing\/images\/f_auto,q_auto\/v1649718331\/Web_Assets\/blog\/cloudinary_logo_for_white_bg_1937437aa7_19374666c7_193742f877\/cloudinary_logo_for_white_bg_1937437aa7_19374666c7_193742f877.png?_i=AA","contentUrl":"https:\/\/res.cloudinary.com\/cloudinary-marketing\/images\/f_auto,q_auto\/v1649718331\/Web_Assets\/blog\/cloudinary_logo_for_white_bg_1937437aa7_19374666c7_193742f877\/cloudinary_logo_for_white_bg_1937437aa7_19374666c7_193742f877.png?_i=AA","width":312,"height":60,"caption":"Cloudinary Blog"},"image":{"@id":"https:\/\/cloudinary.com\/blog\/#\/schema\/logo\/image\/"}},{"@type":"Person","@id":"https:\/\/cloudinary.com\/blog\/#\/schema\/person\/0d5ad601e4c3b5be89245dfb14be42d9","name":"melindapham","image":{"@type":"ImageObject","inLanguage":"en-US","@id":"https:\/\/cloudinary.com\/blog\/#\/schema\/person\/image\/","url":"https:\/\/secure.gravatar.com\/avatar\/e6f989fa97fe94be61596259d8629c3df65aec4c7da5c0000f90d810f313d4f4?s=96&d=mm&r=g","contentUrl":"https:\/\/secure.gravatar.com\/avatar\/e6f989fa97fe94be61596259d8629c3df65aec4c7da5c0000f90d810f313d4f4?s=96&d=mm&r=g","caption":"melindapham"}}]}},"jetpack_featured_media_url":"https:\/\/res.cloudinary.com\/cloudinary-marketing\/images\/f_auto,q_auto\/v1775775445\/Blog_High-Performance_Video_Backgrounds__Automating_Smart_Previews_with_TanStack_Start_and_PowerFlows\/Blog_High-Performance_Video_Backgrounds__Automating_Smart_Previews_with_TanStack_Start_and_PowerFlows.jpg?_i=AA","_links":{"self":[{"href":"https:\/\/cloudinary.com\/blog\/wp-json\/wp\/v2\/posts\/39999","targetHints":{"allow":["GET"]}}],"collection":[{"href":"https:\/\/cloudinary.com\/blog\/wp-json\/wp\/v2\/posts"}],"about":[{"href":"https:\/\/cloudinary.com\/blog\/wp-json\/wp\/v2\/types\/post"}],"author":[{"embeddable":true,"href":"https:\/\/cloudinary.com\/blog\/wp-json\/wp\/v2\/users\/87"}],"replies":[{"embeddable":true,"href":"https:\/\/cloudinary.com\/blog\/wp-json\/wp\/v2\/comments?post=39999"}],"version-history":[{"count":1,"href":"https:\/\/cloudinary.com\/blog\/wp-json\/wp\/v2\/posts\/39999\/revisions"}],"predecessor-version":[{"id":40001,"href":"https:\/\/cloudinary.com\/blog\/wp-json\/wp\/v2\/posts\/39999\/revisions\/40001"}],"wp:featuredmedia":[{"embeddable":true,"href":"https:\/\/cloudinary.com\/blog\/wp-json\/wp\/v2\/media\/40000"}],"wp:attachment":[{"href":"https:\/\/cloudinary.com\/blog\/wp-json\/wp\/v2\/media?parent=39999"}],"wp:term":[{"taxonomy":"category","embeddable":true,"href":"https:\/\/cloudinary.com\/blog\/wp-json\/wp\/v2\/categories?post=39999"},{"taxonomy":"post_tag","embeddable":true,"href":"https:\/\/cloudinary.com\/blog\/wp-json\/wp\/v2\/tags?post=39999"}],"curies":[{"name":"wp","href":"https:\/\/api.w.org\/{rel}","templated":true}]}}