From 85ad3a8d66181ce4a0ecab1aa2cc4001ad561b0b Mon Sep 17 00:00:00 2001 From: Vivek Date: Thu, 20 Aug 2026 22:13:59 +0530 Subject: [PATCH 1/4] fix(website): self-host the intro video, drop the YouTube iframe The embed's every property was a workaround for being cross-origin: a frame hidden until load to cover the white canvas some engines paint before YouTube's stylesheet lands, a plain onload attribute to reveal it because this page never hydrates, and a noscript rule that deleted the whole section for a JS-off reader, since their player needs JS inside the frame and nothing this page does can supply it. That last one is the real cost. WebJs sells progressive enhancement on its own landing page, and the landing page was deleting its video for anyone without JS. A native player needs none of it. The file is served from our own R2 bucket at videos.webjs.dev, with preload="metadata" keeping the bytes off the wire until someone presses play. --- website/app/page.ts | 115 +++++++++++++++-------- website/test/ssr/intro-video-ssr.test.ts | 61 +++++++----- 2 files changed, 114 insertions(+), 62 deletions(-) diff --git a/website/app/page.ts b/website/app/page.ts index c7510707f..e2164aa2f 100644 --- a/website/app/page.ts +++ b/website/app/page.ts @@ -246,6 +246,81 @@ function sourceWindow(title: string, sample: string) { `; } +/** + * The landing page's intro video, self-hosted rather than embedded. + * + * The player is the browser's own, so it works with scripting disabled. The + * YouTube iframe this replaced could not, because its player needs JS inside + * the frame, so the section used to hide itself from a JS-off reader rather + * than show a broken embed. + * + * REPLACING THE VIDEO, for whoever does it next. + * + * The bytes live in the Cloudflare R2 bucket `webjs-videos`, served from + * videos.webjs.dev. The key carries no version, so a new cut overwrites + * `intro.mp4` in place and nothing in this file changes: + * + * env -u CLOUDFLARE_API_TOKEN npx wrangler r2 object put \ + * webjs-videos/intro.mp4 --file \ + * --content-type video/mp4 \ + * --cache-control "public, max-age=86400, s-maxage=31536000" \ + * --remote + * + * Then purge that URL at the edge (Caching, then Purge Cache, then the custom + * single-file purge), or the old bytes keep serving for the `s-maxage` year. + * + * Three things about this are easy to get wrong. + * + * 1. R2 object metadata cannot be edited after upload. `Cache-Control` is set + * at write time or not at all, so every upload has to pass it again. There + * is no bucket-level setting, and the dashboard uploader has no field for + * it, which is why an upload made there serves no header of its own. + * + * 2. The `env -u` is load bearing. A `CLOUDFLARE_API_TOKEN` in the environment + * overrides wrangler's OAuth credentials, and that token carries no R2 + * permission, so the upload fails with an authentication error that names + * nothing. Unsetting it for the one call falls back to the OAuth login. + * + * 3. A purge clears the edge and never a browser. That is why `max-age` is a + * day rather than a year, and why the header is deliberately not + * `immutable`: anyone holding the old file needs a way to pick the new one + * up, and 24 hours is that way. `s-maxage` keeps the edge copy long lived + * regardless, since the edge can be purged and a browser cannot. + * + * A versioned key (`intro-2026-08-21.mp4`) is the other valid shape. Take it + * and the tradeoff inverts: put `immutable` back, raise `max-age` to a year, + * and update the src below on every cut. + * + * The poster is the video's own first frame at 1080p, uploaded the same way + * with `--content-type image/webp`. + */ +const INTRO_VIDEO = html` +
+
+ +
+ +
+
+
+`; + + export default function LandingPage() { return html` - - + ${INTRO_VIDEO}
diff --git a/website/test/ssr/intro-video-ssr.test.ts b/website/test/ssr/intro-video-ssr.test.ts index eac856803..be7571fa6 100644 --- a/website/test/ssr/intro-video-ssr.test.ts +++ b/website/test/ssr/intro-video-ssr.test.ts @@ -1,14 +1,16 @@ /** - * The landing page's intro video is hidden until its frame fires load. + * The landing page's intro video is self-hosted, not embedded. * - * A cross-origin iframe paints its own canvas before the embedded stylesheet - * applies, and on some engines that canvas is opaque white, which no - * background on the iframe element can cover. Hiding the frame until load - * removes the question: what it paints early is off screen, and the box under - * it is already black. + * It replaced a YouTube iframe whose every property was a workaround for + * being cross-origin: a frame hidden until load (an iframe paints its own + * canvas, opaque white on some engines, before the embedded stylesheet + * lands), a plain onload attribute to reveal it (this page never hydrates, so + * an event hole would be dropped at SSR), and a noscript rule that deleted + * the whole section for a JS-off reader (their player needs JS inside the + * frame, so revealing it would have shown their own error). * - * Each assertion here is a piece that silently breaks the whole thing if it - * goes missing, which is why they are pinned rather than left to review. + * A native player needs none of that, and the assertions below pin the pieces + * that silently break it if they go missing. */ import test from 'node:test'; import assert from 'node:assert/strict'; @@ -17,26 +19,39 @@ import LandingPage from '#app/page.ts'; const render = () => renderToString(LandingPage()); -test('the intro frame ships hidden, over a black box', async () => { +test('the intro video is a native player pointed at our own host', async () => { const out = await render(); - assert.match(out, /class="intro-video-frame [^"]*\binvisible\b/, 'the frame must start hidden'); - assert.match(out, /aspect-video[^"]*\bbg-black\b/, 'the box under it must be black'); + assert.match(out, /]*\ssrc="https:\/\/videos\.webjs\.dev\/intro\.mp4"/); + assert.match(out, /]*\scontrols/, 'the controls are the whole no-JS story'); }); -test('the frame reveals itself with a plain onload attribute', async () => { +test('the first paint shows a poster rather than a black box', async () => { const out = await render(); - // A plain HTML attribute, not an @event hole: this page never hydrates, so - // a template event binding would be dropped at SSR and the frame would stay - // hidden forever. - assert.match(out, /onload="this\.classList\.remove\('invisible'\)"/); + // Without this the box is empty until someone presses play, which is worse + // than the embed it replaced. + assert.match(out, /]*\sposter="https:\/\/videos\.webjs\.dev\/intro-poster\.webp"/); }); -test('a JS-off reader gets no embed at all', async () => { +test('the bytes stay off the wire until someone plays it', async () => { const out = await render(); - // Without JS the load handler never runs AND YouTube's player cannot run - // inside the frame either, so revealing it would show their own noscript - // error rather than a video. Hide the whole section instead. The rule must - // live in noscript, which a browser with scripting on parses as inert text. - assert.match(out, /