Files
wiki/netlify/edge-functions/botMeta.ts
Wayne Sutton 462729de58 chore: prepare v1.0.0 for Netlify deployment
Update version to 1.0.0 across package.json and changelog. Configure netlify.toml with Convex deployment URL (agreeable-trout-200.convex.site). Verify TypeScript type-safety for src and convex directories. Confirm Netlify build passes with SPA 404 fallback configured. Update TASK.md with deployment steps and files.md with complete file structure.
2025-12-14 11:30:22 -08:00

96 lines
2.1 KiB
TypeScript

import type { Context } from "@netlify/edge-functions";
// List of known social media and search engine bots
const BOTS = [
"facebookexternalhit",
"twitterbot",
"linkedinbot",
"slackbot",
"discordbot",
"telegrambot",
"whatsapp",
"pinterest",
"opengraph",
"opengraphbot",
"bot ",
"crawler",
"embedly",
"vkshare",
"quora link preview",
"redditbot",
"rogerbot",
"showyoubot",
"google",
"bingbot",
"baiduspider",
"duckduckbot",
];
function isBot(userAgent: string | null): boolean {
if (!userAgent) return false;
const ua = userAgent.toLowerCase();
return BOTS.some((bot) => ua.includes(bot));
}
export default async function handler(
request: Request,
context: Context,
): Promise<Response> {
const url = new URL(request.url);
const userAgent = request.headers.get("user-agent");
// Only intercept post pages for bots
const pathParts = url.pathname.split("/").filter(Boolean);
// Skip if it's the home page, static assets, or API routes
if (
pathParts.length === 0 ||
pathParts[0].includes(".") ||
pathParts[0] === "api" ||
pathParts[0] === "_next"
) {
return context.next();
}
// If not a bot, continue to the SPA
if (!isBot(userAgent)) {
return context.next();
}
// For bots, fetch the Open Graph metadata from Convex
const slug = pathParts[0];
const convexUrl =
Deno.env.get("VITE_CONVEX_URL") || Deno.env.get("CONVEX_URL");
if (!convexUrl) {
return context.next();
}
try {
// Construct the Convex site URL for the HTTP endpoint
const convexSiteUrl = convexUrl.replace(".cloud", ".site");
const metaUrl = `${convexSiteUrl}/meta/post?slug=${encodeURIComponent(slug)}`;
const response = await fetch(metaUrl, {
headers: {
Accept: "text/html",
},
});
if (response.ok) {
const html = await response.text();
return new Response(html, {
headers: {
"Content-Type": "text/html; charset=utf-8",
"Cache-Control": "public, max-age=60, s-maxage=300",
},
});
}
// If meta endpoint fails, fall back to SPA
return context.next();
} catch {
return context.next();
}
}