返回 CodeWhale
opengraph-image.tsx
根目录 / web / app / opengraph-image.tsx
1 import { readFile } from "node:fs/promises";
2 import { join } from "node:path";
3 import { ImageResponse } from "next/og";
4 import { IDENTITY_PHRASE, OG_ALT } from "@/lib/page-meta";
5
6 export const alt = OG_ALT;
7 export const size = { width: 1200, height: 630 };
8 export const contentType = "image/png";
9
10 // Read the brand SVGs when the image is generated, never at import time. This
11 // route is prerendered at build on Node, but its module is still evaluated
12 // inside the Cloudflare Worker whenever a page regenerates, and the Workers
13 // Node shim throws on fs.readFile. A top-level read here made every ISR
14 // regeneration on the site fail with a 500 and serve its build snapshot forever.
15 async function brandSvgs(): Promise<[string, string]> {
16 const [mark, wordmark] = await Promise.all([
17 readFile(join(process.cwd(), "public/brand/mark-reversed.svg")),
18 readFile(join(process.cwd(), "public/brand/wordmark-inverted.svg")),
19 ]);
20 return [mark.toString().replace("currentColor", "#ffffff"), wordmark.toString()];
21 }
22
23 export default async function OpengraphImage() {
24 const [markSvg, wordmarkSvg] = await brandSvgs();
25 const markDataUrl = `data:image/svg+xml;base64,${Buffer.from(markSvg).toString("base64")}`;
26 const wordmarkDataUrl = `data:image/svg+xml;base64,${Buffer.from(wordmarkSvg).toString("base64")}`;
27
28 // Brand navy ground, the white mark and inverted wordmark, and the identity
29 // phrase once — the wordmark is the name, so no second "Codewhale" heading.
30 return new ImageResponse(
31 (
32 <div
33 style={{
34 width: "100%",
35 height: "100%",
36 display: "flex",
37 flexDirection: "column",
38 alignItems: "center",
39 justifyContent: "center",
40 gap: 34,
41 background: "#142352",
42 fontFamily: "sans-serif",
43 }}
44 >
45 <img src={markDataUrl} width={200} height={200} alt="" />
46 {/* The family wordmark is 1024x160 (6.4:1). */}
47 <img src={wordmarkDataUrl} width={532} height={83} alt="Codewhale" />
48 <div style={{ display: "flex", fontSize: 30, color: "#F6F2E8", marginTop: 14 }}>
49 {IDENTITY_PHRASE}
50 </div>
51 </div>
52 ),
53 { ...size },
54 );
55 }
56
56 lines Plain Text