CDN

Video CDN

How the Laioutr CDN stores, transforms, and delivers video, what the upload envelope allows, and which seams let you put an adaptive-streaming provider behind the same storefront components.
Available since v0.35.0 in @laioutr-app/cms and @laioutr-core/frontend-core

A product page wants a 20-second demo clip. A hero wants a muted loop behind the headline. A campaign page wants a vertical feed of short clips that plays as the shopper scrolls. All three are the same job for the platform: an editor picks a video in Studio, and the storefront plays it fast enough that nobody waits.

The Laioutr CDN handles that job on the same storage, hostname, and cache path it uses for images. This page describes what that path does, where it stops, and which seams exist for video that needs more than it offers.

The delivery path

Uploads go from the browser straight to the project's bucket through a short-lived presigned URL. Nothing is written to the index until the object has landed, so an abandoned upload leaves nothing behind. Delivery runs through the project's own hostname with the edge cache in front, and the origin is only touched on a miss.

There is no transcoding queue and no processing state to poll. A video is playable as soon as it is finalized, because every derived variant is produced on request by the transform layer, not ahead of time.

The upload envelope

Video is accepted only inside a fixed envelope. The limits come from the transform layer, and the CDN rejects an out-of-envelope file at the moment upload targets are requested rather than accepting it and degrading to an untransformable raw file.

ConstraintValue
Container and codecsMP4 with H.264 video and AAC or MP3 audio
File size100 MB
Duration10 minutes
OutputProgressive MP4

A rejected file comes back as a per-file too_large or unsupported_type outcome, so one bad file in a batch does not sink the rest. The limits are enforced server-side; a client-side hint in the picker is a convenience, not the boundary.

This envelope is the short-form profile: product clips, hero loops, feed items. Long-form video, files past 100 MB, and true adaptive bitrate are covered by Beyond the envelope.

Delivery URLs and caching

A finalized video is served from the project's delivery host at a key minted once for that upload:

https://{projectId}.cdn.laioutr.cloud/{key}.mp4

Two properties of that URL matter when you build on it.

A key is never reused. Each upload mints a fresh key, and the bytes behind a key never change. Replacing a video writes a new key and repoints the reference; it does not overwrite the old object.

Responses are cached for a year, immutably. A successful delivery carries cache-control: public, max-age=31536000, immutable. That is what makes a repeat view free, and it is also why keys are immutable: a browser cache cannot be purged, so a swapped object would keep serving the old bytes to returning visitors for as long as the entry lives. Error responses are excluded from that header.

A branded delivery hostname (cdn.example.com) can replace the default. It changes the host in the URL and nothing else about this section.

Transformations

Derived variants are produced at the edge from the stored MP4 by prefixing the key with a transform path:

https://{projectId}.cdn.laioutr.cloud/cdn-cgi/media/{options}/{key}.mp4
ModeProducesTypical use
mode=videoRe-encoded progressive MP4 at a requested width, height, and fitServing a smaller rendition to a small element
mode=frameA single JPEG frame at a given timePoster images and picker thumbnails
mode=spritesheetA grid of framesScrub previews on a timeline

Each distinct transform URL is cached like any other object, so a variant is generated once and then served from cache.

Posters are wired up for you. A video picked from the Laioutr CDN carries a poster whose sources use the laioutrCmsPoster Nuxt Image provider, which rebuilds a mode=frame URL at whatever width the layout asks for. The poster therefore gets responsive widths in the same way an image does.

The video source itself is delivered at its stored size today. The mode=video width ladder is available on the delivery host but is not yet applied automatically by the built-in player, so upload video at the size you intend to serve.

Rendering video in the storefront

An asset picked from the CDN is a canonical MediaVideo value, so it renders through the same component as any other video:

<template>
  <Media :media="section.clip" playback="background" />
</template>

The parts of that already have their own pages:

  • Media dispatches on media.type, plays video with a built-in native player, and holds video back from fetching until it is within a screen of the viewport.
  • playback="interactive" gives a native player with controls; playback="background" gives the muted autoplay loop, with autoplay suppressed under reduced motion.
  • MediaSourceVideo carries the source URL, dimensions, optional focal point, and the streaming discriminator described below.
  • Media and Media Library covers the picker, the connector contract, and staged uploads.

What the CDN meters

Delivered bytes are counted per project, off the request path, and rolled up daily and monthly. Image and video traffic share one line; there is no separate video-minutes meter on this path.

Metering is record-only. Passing the volume included in your plan raises a notification and an upgrade conversation; it does not throttle delivery and it does not break media on a live storefront. Enforcement is reserved for abuse, not for ordinary overage.

Beyond the envelope

Long-form video, adaptive bitrate for weak networks, DRM, and per-video analytics are outside what the progressive path does. The platform does not pretend otherwise, and it does not require a migration to get them: the contract and the render path were built with the seams already open, so an adaptive provider is a per-asset choice rather than a platform switch.

The source already declares its streaming format

MediaSourceVideo.streaming is part of the canonical type, not an extension:

interface MediaSourceVideo {
  provider: string;
  src: string;
  width: number;
  height: number;
  streaming?: 'progressive' | 'hls' | 'dash';
  // ...
}

A source produced by the Laioutr CDN is progressive. A source produced by an adaptive backend points src at an HLS manifest and sets streaming: 'hls'. Both are valid MediaVideo values, both are stored the same way, and both can sit side by side in one project. See Streaming formats for what each value tells a renderer.

The player is a registration, not a fork

The built-in <video> element plays progressive sources. HLS and DASH need a JavaScript player, and <Media> takes one through provideMediaRenderers. A renderer registered for video replaces the built-in for every video in the app, with no change at any call site:

plugins/media-renderers.ts
import { provideMediaRenderers } from '#ui-kit/components/Media/MediaRenderersProvider';
import StreamingVideo from '~/components/StreamingVideo.vue';

export default defineNuxtPlugin((nuxtApp) => {
  provideMediaRenderers(nuxtApp.vueApp, {
    video: StreamingVideo,
  });
});

The renderer receives the narrowed media object and the playback props, reads media.sources and media.streaming, and decides whether a source plays natively or needs the player's engine. That is where a Mux, Bunny, Cloudflare Stream, or Vidstack player goes. See Overriding with a custom renderer for the full contract and a worked example.

The library is a connector

An external video service can appear in Studio as its own picker alongside the Laioutr CDN. That is the media-library connector interface: declare capabilities, answer list, and optionally handle uploads. Your list maps the provider's assets onto MediaVideo values with streaming: 'hls' and a poster URL, and every downstream consumer keeps working, because the shape is canonical.

The full contract, including staged uploads and the transient processing state a transcoding backend needs, is in Media and Media Library.

What changes when video leaves the Laioutr CDN

Adaptive providers buy capability at a cost that is worth naming before you commit:

  • Delivery hostname. Managed streaming services usually serve from a shared, account-wide playback domain. A branded per-project delivery host is a property of the Laioutr CDN path and does not carry over.
  • Isolation. On the Laioutr CDN, one project's assets are in one bucket bound to one hostname. On a shared streaming account, separation depends on tagging and identifier secrecy instead.
  • Metering. Streaming services bill stored and delivered minutes, and that traffic never touches the Laioutr CDN, so it does not appear in the delivered-bytes rollup. It is a separate line on a separate bill.
  • Upload shape. Presigned single-PUT uploads are what the Laioutr CDN does. Resumable and multipart protocols used by streaming services are a different upload path, and the connector's finalize step waits for a transcode instead of confirming an object.

A useful middle path keeps the file in the project's bucket as the source of truth and lets the streaming provider pull from it, so storage and delivery stay separable.

Not on this path today

Stated plainly, so nothing is inferred from silence. The Laioutr CDN video path does not provide:

  • Adaptive bitrate, HLS, or DASH from Laioutr's own delivery, which is what Beyond the envelope is for
  • Signed or expiring delivery URLs, hotlink restrictions, or DRM; delivery is public by design, as storefront media is public content
  • Watermarking, automatic content tagging, or a hosted player skin
  • Transcode webhooks or a processing-status API, which the synchronous transform path has no need for
  • Asset deletion from the picker
Copyright © 2026 Laioutr GmbH