Files
spsays-training/app/api/og/route.tsx

218 lines
5.8 KiB
TypeScript

import { ImageResponse } from "next/og";
import { readFile } from "node:fs/promises";
import { join } from "node:path";
export const runtime = "nodejs";
// Cache font data at module level so they're only loaded once
let notoRegularData: Buffer | null = null;
let notoBoldData: Buffer | null = null;
async function loadFonts() {
const fontsDir = join(process.cwd(), "public/fonts/Noto_Sans/static");
if (!notoRegularData) {
notoRegularData = await readFile(join(fontsDir, "NotoSans-Regular.ttf"));
}
if (!notoBoldData) {
notoBoldData = await readFile(join(fontsDir, "NotoSans-Bold.ttf"));
}
return { regular: notoRegularData, bold: notoBoldData };
}
export async function GET(request: Request) {
try {
const { searchParams } = new URL(request.url);
const title = searchParams.get("title") || "Prabhupāda Says";
const detail = searchParams.get("detail") || "";
const rawQuote = searchParams.get("quote") || "";
// Fallback: if no quote provided, generate one from context
const quote =
rawQuote ||
(title === "Prabhupāda Says"
? "Browse the original, unedited books, letters, and teachings of His Divine Grace A.C. Bhaktivedanta Swami Prabhupāda — presented as they are."
: `Read ${title}${detail ? ` — ${detail}` : ""} by His Divine Grace A.C. Bhaktivedanta Swami Prabhupāda`);
// Truncate for readability and to prevent overflow
const displayTitle = title.length > 60 ? title.slice(0, 57) + "..." : title;
const displayDetail =
detail.length > 80 ? detail.slice(0, 77) + "..." : detail;
const displayQuote =
quote.length > 220 ? quote.slice(0, 217) + "..." : quote;
// Load fonts and image
const fonts = await loadFonts();
const imgData = await readFile(
join(process.cwd(), "public/images/og-left-prabhupada-reading.png"),
"base64",
);
const imgSrc = `data:image/png;base64,${imgData}`;
return new ImageResponse(
<div
style={{
width: 1200,
height: 630,
display: "flex",
flexDirection: "row",
backgroundColor: "#faf5f0",
fontFamily: "Noto Sans, sans-serif",
overflow: "hidden",
position: "relative",
}}
>
{/* Left side — image + branding */}
<div
style={{
width: 460,
height: 630,
display: "flex",
alignItems: "center",
justifyContent: "center",
flexShrink: 0,
padding: 20,
}}
>
{/* Prabhupāda image — already contains quote mark and branding */}
<img
src={imgSrc}
width={420}
height={590}
style={{
objectFit: "contain",
objectPosition: "center",
}}
/>
</div>
{/* Right side — content */}
<div
style={{
flex: 1,
display: "flex",
flexDirection: "column",
justifyContent: "center",
paddingTop: 50,
paddingRight: 60,
paddingBottom: 50,
paddingLeft: 60,
gap: 16,
}}
>
{/* Title */}
<div
style={{
fontSize: 36,
fontWeight: 700,
color: "#2d2418",
lineHeight: 1.4,
display: "flex",
overflow: "hidden",
maxHeight: 104,
}}
>
{displayTitle}
</div>
{/* Detail (chapter, verse, etc.) */}
{displayDetail ? (
<div
style={{
fontSize: 22,
fontWeight: 700,
color: "#5a4a3a",
lineHeight: 1.3,
display: "flex",
overflow: "hidden",
maxHeight: 30,
}}
>
{displayDetail}
</div>
) : null}
{/* Quote text */}
{displayQuote ? (
<div
style={{
display: "flex",
flexDirection: "column",
marginTop: 16,
overflow: "hidden",
maxHeight: 180,
}}
>
<div
style={{
fontSize: 24,
color: "#3d3228",
lineHeight: 1.5,
display: "flex",
overflow: "hidden",
}}
>
{displayQuote}
</div>
</div>
) : null}
{/* Read more */}
<div
style={{
display: "flex",
flexDirection: "row",
alignItems: "center",
gap: 6,
marginTop: 16,
}}
>
<span
style={{
fontSize: 20,
fontWeight: 700,
color: "#5a4a3a",
}}
>
Read more at
</span>
<span
style={{
fontSize: 20,
fontWeight: 700,
color: "#c47a5a",
}}
>
prabhupadasays.org
</span>
</div>
</div>
</div>,
{
width: 1200,
height: 630,
fonts: [
{
name: "Noto Sans",
data: fonts.regular,
style: "normal" as const,
weight: 400,
},
{
name: "Noto Sans",
data: fonts.bold,
style: "normal" as const,
weight: 700,
},
],
},
);
} catch (e) {
console.error("OG image generation failed:", e);
return new Response("Failed to generate OG image", { status: 500 });
}
}