Files
bitvid/middleware.js
thePR0M3TH3ANandClaude Opus 5 04075ff56b [og] Route crawlers via middleware, not a rewrite
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]>
2026-07-28 14:40:20 -04:00

44 lines
1.4 KiB
JavaScript

// Routes crawler requests for `/?v=<nevent>` to the per-video OpenGraph
// renderer (api/og.js).
//
// This has to be middleware rather than a `rewrites` entry in vercel.json:
// rewrites are evaluated AFTER the filesystem, and `/` matches the static
// index.html, so the rewrite never fired and crawlers kept getting the
// site-wide card. Middleware runs before the filesystem check.
//
// Deliberately minimal. It matches only `/`, bails immediately when there is no
// `v` param or the visitor is not a known preview crawler, and never touches
// the response for a real viewer — they continue to the untouched SPA. If this
// file is removed the site keeps working, just with the generic card.
import { next, rewrite } from "@vercel/edge";
import { isCrawlerUserAgent } from "./api/_lib/ogRenderer.mjs";
export const config = {
// Only the share-link path. Every other route skips middleware entirely.
matcher: "/",
};
export default function middleware(request) {
let url;
try {
url = new URL(request.url);
} catch (err) {
return next();
}
// A bare homepage visit is not a share link.
if (!url.searchParams.get("v")) {
return next();
}
const userAgent = request.headers?.get?.("user-agent") || "";
if (!isCrawlerUserAgent(userAgent)) {
return next();
}
const target = new URL(url.toString());
target.pathname = "/api/og";
return rewrite(target);
}