The OG function deployed and worked (`/api/og?v=<nevent>` returns the real
title, description, thumbnail and player card), but crawlers hitting
`/?v=<nevent>` still got the site-wide card: Vercel evaluates `rewrites` AFTER
the filesystem, and `/` matches the static index.html, so the rewrite never
fired.
Middleware runs before the filesystem check, so that is where the routing
belongs. Replace the dead `rewrites` block with a root middleware that matches
only `/`, bails immediately when there is no `v` param or the user-agent is not
a known preview crawler, and otherwise rewrites to /api/og.
Real viewers are unaffected -- they fall through to the untouched SPA. Note the
one behavioral difference from the rewrite design: middleware now executes on
homepage requests (matcher is `/` only) rather than being invoked solely for
crawlers. It returns next() after two cheap checks.
Verified on the live unstable deployment that /api/og already renders
`x-bitvid-og: video` with og:type video.other for the real note; this only
fixes how crawlers reach it.
Co-Authored-By: Claude Opus 5 (1M context) <[email protected]>
index.html carries one set of site-wide OG tags, so every /?v=<nevent> link
previewed as the same generic bitvid card -- never the video's title or
thumbnail. bitvid is a static SPA and crawlers do not run JS, so the client
cannot fix this.
api/og.js renders the tags on demand. Nothing is pre-generated and nothing is
stored: the video is a query param, not a path, so one stateless function
serves every video and dist/ is byte-identical with or without it. The only
caching is the CDN edge (s-maxage=300) plus a per-container memo of the admin
lists.
This is never load-bearing. Only crawler user-agents are rewritten to it
(vercel.json), humans get a 302 to the untouched SPA, and a static/nsite deploy
with no functions keeps working -- it just serves the site-wide card. Rollback
is deleting the `rewrites` block.
- Emits twitter:player / og:video:url pointing at embed.html?pointer=<nevent>,
so a shared link plays inline on Twitter/Discord/Telegram rather than showing
a still. frame-ancestors * already permits the embed.
- Fails closed. A crawler never authenticates, so anything emitted is public
forever in third-party caches: deleted, isPrivate, isNsfw/content-warning,
blacklisted authors, and -- while whitelist mode is on -- an unavailable or
empty whitelist all fall back to the generic card.
- Escapes every attribute. Titles come from arbitrary nostr events and this HTML
is served from bitvid's own origin; only http(s) URLs reach a meta tag.
- Parses BOTH event shapes: bitvid's kind-30078 v3 JSON and the NIP-71 tag shape
(kinds 21/22/34235/34236) that nostube/slidestr publish, where the title is a
`title` tag and the thumbnail an `imeta image` entry. Reading only the JSON
shape made every NIP-71 video fall back to the generic card.
- Relay budget is 4s: a cold pool's first read measured ~3s, and 1.5s silently
degraded every preview. Warm containers answer in <600ms.
Admin-list identity and the relay set are imported from js/config.js,
js/nostr/toolkit.js and js/nostrEventSchemas.js rather than duplicated -- a
hardcoded copy of the namespace gated every preview during development, and a
test now fails if any npub or wss:// literal reappears in the handler.
Co-Authored-By: Claude Opus 5 (1M context) <[email protected]>
Pushing to unstable triggered a Vercel Preview build on every commit (and the
manual promote rebuilt again) — wasted build time on a fast-moving dev branch we
deploy on demand anyway. Set git.deploymentEnabled.unstable=false so pushes no
longer auto-build; deploys are now explicit via `vercel deploy --prod` when we
want unstable.bitvid.network updated. beta/main keep auto-deploying (intentional,
low-frequency promotions).
Co-Authored-By: Claude Opus 4.8 <[email protected]>
Adds Cache-Control: no-cache,must-revalidate for the entry HTML (/ , *.html)
and sw.min.js so Vercel-served domains (and any CDN/browser in front) always
revalidate the shell and pick up new deploys, mirroring netlify.toml. Hashed
JS/CSS keep far-future caching via their content-hashed filenames.
Co-Authored-By: Claude Opus 4.8 <[email protected]>
Both package-lock.json and pnpm-lock.yaml are present, so Vercel auto-selected
pnpm, whose install failed on the new bitvid-unstable/bitvid-beta projects (the
existing bitvid project only worked via a dashboard install-command override).
Pinning installCommand makes every project/branch build with npm regardless of
dashboard settings.
Co-Authored-By: Claude Opus 4.8 <[email protected]>