@djangocfg/widget-ogimage 0.1.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2025 Reforms.ai
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
package/README.md ADDED
@@ -0,0 +1,142 @@
1
+ # @djangocfg/widget-ogimage
2
+
3
+ Open Graph cards for Next.js apps. What a link **becomes** when someone pastes
4
+ it into WhatsApp, Telegram, Slack, iMessage or Facebook — often the only part
5
+ of a site a person sees before deciding whether to open the page.
6
+
7
+ One satori renderer, the vendored Plus Jakarta Sans, and the two card
8
+ "fabrics" every `opengraph-image.tsx` calls. Brand identity is bound once per
9
+ app, so two apps on this package draw from the same layout and still read as
10
+ two products.
11
+
12
+ ## Quick start
13
+
14
+ ```tsx
15
+ // app/_lib/og.tsx — bind the identity once
16
+ import { createOgCards, domainFromEnv } from '@djangocfg/widget-ogimage';
17
+ import { settings } from '@core/settings';
18
+
19
+ export const { size, contentType, brandCard, contentCard } = createOgCards({
20
+ name: settings.app.name,
21
+ description: settings.app.description,
22
+ tagline: 'Run on one machine. Coordinate many.',
23
+ domain: domainFromEnv({ fallback: settings.app.siteUrl }),
24
+ siteUrl: settings.app.siteUrl,
25
+ accentColor: '#61B8FF',
26
+ mark: <svg …/>, // inline SVG only — see below
27
+ });
28
+ ```
29
+
30
+ ```tsx
31
+ // app/opengraph-image.tsx — the brand card: home, and the fallback everywhere
32
+ import { brandCard, contentType, size } from '@lib/og';
33
+
34
+ export const runtime = 'nodejs';
35
+ export const alt = 'CMDOP — run commands across your machines';
36
+ export { size, contentType };
37
+
38
+ export default async function OpengraphImage() {
39
+ return brandCard('Skills & MCP servers', 'Browse, search, and install.');
40
+ }
41
+ ```
42
+
43
+ ```tsx
44
+ // app/skills/[slug]/opengraph-image.tsx — one entity
45
+ import { contentCard, contentType, size } from '@lib/og';
46
+ import { getSkill } from '@lib/api';
47
+
48
+ export const runtime = 'nodejs';
49
+ export { size, contentType };
50
+
51
+ export default async function Image({ params }: { params: Promise<{ slug: string }> }) {
52
+ const { slug } = await params;
53
+ const skill = await getSkill(slug).catch(() => null); // never throw — see rules
54
+
55
+ return contentCard({
56
+ title: skill?.seoTitle || skill?.name, // empty → brand card
57
+ eyebrow: 'Skill',
58
+ description: skill?.seoDescription,
59
+ footer: 'Marketplace · Skills',
60
+ image: skill?.cover, // absolute URL, root-relative path, or data URI
61
+ });
62
+ }
63
+ ```
64
+
65
+ Check locally by appending the segment to any page path:
66
+ `/opengraph-image`, `/skills/<slug>/opengraph-image`. Both return a 1200×630 PNG.
67
+
68
+ ## Three rules
69
+
70
+ **`runtime = 'nodejs'` on every route.** The font decoder uses `Buffer`, which
71
+ the edge runtime does not provide.
72
+
73
+ **Never throw.** A card renders while a crawler waits. A route that 500s leaves
74
+ a bare grey rectangle in the chat thread — strictly worse than a generic brand
75
+ card. Every fetch is `.catch(() => null)`, and `contentCard` falls back to
76
+ `brandCard` when the title is empty.
77
+
78
+ **No `openGraph.images` above a card.** Next's file convention only applies
79
+ where no explicit `images` is set in metadata, and an explicit entry *wins*.
80
+ An `images` array in a layout does not complement the cards below it, it
81
+ silently disables them — the root layout can suppress every card in the app at
82
+ once. `withoutOgImages(metadata)` strips them from generated metadata.
83
+
84
+ ## API
85
+
86
+ | Export | Role |
87
+ |---|---|
88
+ | `createOgCards(config)` | Binds identity → `{ size, contentType, brandCard, contentCard }`. The recommended entry point. |
89
+ | `fitDescription(title, text)` | Description budget as a function of title length; trims at a word boundary. |
90
+ | `ogFooter(...parts)` | Joins facts with ` · `, dropping absent ones. |
91
+ | `ogImageUrl(path, siteUrl)` | Absolute URL or `undefined` — satori fetches images itself. |
92
+ | `domainFromEnv` · `siteUrlFromEnv` · `metadataBaseFromEnv` · `resolvePublicUrl` | Site origin from `NEXT_PUBLIC_SITE_URL` with an app-owned fallback. |
93
+ | `withoutOgImages(metadata)` | Strips `openGraph.images` / `twitter.images` so a segment's card applies. |
94
+ | `renderOgImage(options, fonts)` · `OG_SIZE` · `OG_CONTENT_TYPE` · `DEFAULT_OG_THEME` | Draw a card directly. |
95
+ | `loadOgFonts()` | The vendored typeface, decoded once per process. |
96
+ | `loadImageDataUri(path, mime)` | A local file as a data URI (a portrait that ships with the app). |
97
+
98
+ ### `OgCardsConfig`
99
+
100
+ | Field | Notes |
101
+ |---|---|
102
+ | `name` | brand card title; wordmark on content cards |
103
+ | `description?` · `tagline?` | brand card description and footer |
104
+ | `domain?` | printed bottom-right |
105
+ | `siteUrl?` | resolves root-relative `image` paths |
106
+ | `accentColor?` · `theme?` | palette; `theme.footer` defaults to the accent |
107
+ | `mark?` | inline SVG. Satori cannot load React icon components with CSS, and an `<img>` to a remote logo makes every card depend on a fetch that can fail while a crawler waits. |
108
+ | `loadFonts?` | swap the typeface |
109
+
110
+ ## Layout
111
+
112
+ The frame is 1200×630 and satori does not clip: a title long enough to wrap
113
+ pushes into the footer instead of truncating. `titleSizeFor` steps the title
114
+ through 84 → 66 → 52px at 34 and 58 characters, and `fitDescription` budgets
115
+ the description against those same steps (150 → 92 → 0 characters), so the two
116
+ move together. Change one, change the other.
117
+
118
+ Over a photograph a two-pass scrim darkens the left half so the copy survives
119
+ whatever the image happens to be, and the copy column narrows to stay on the
120
+ scrimmed ground. The numbers were arrived at by rendering; if you change them,
121
+ render one and look at it.
122
+
123
+ ## Fonts
124
+
125
+ `src/fonts/index.ts` is **generated** — the three `.woff` files beside it are
126
+ the source of truth. Satori cannot use `next/font` (woff2, returns class
127
+ names) and cannot read a `.woff` beside a route in a production build (nothing
128
+ traces it into the output), so the bytes are inlined as base64 into the module
129
+ graph. ~64KB of source, paid at build time, never sent to a browser.
130
+
131
+ ```bash
132
+ pnpm fonts # node scripts/generate-fonts.mjs
133
+ ```
134
+
135
+ Plus Jakarta Sans, SIL Open Font License 1.1.
136
+
137
+ ## Metadata
138
+
139
+ Page `Metadata` (title template, canonical, hreflang, static brand image) is
140
+ `@djangocfg/nextjs/og-image`'s `makeMetadataFactory`. It has no "render a
141
+ card" case on purpose: the file convention above overrides metadata by design,
142
+ so supporting both would be two mechanisms competing to set the same tag.
package/package.json ADDED
@@ -0,0 +1,64 @@
1
+ {
2
+ "name": "@djangocfg/widget-ogimage",
3
+ "version": "0.1.1",
4
+ "description": "Open Graph cards for Next.js apps: one satori renderer, vendored Plus Jakarta Sans, and the brand/content card fabrics every opengraph-image route calls",
5
+ "keywords": [
6
+ "nextjs",
7
+ "og-image",
8
+ "opengraph",
9
+ "satori",
10
+ "react",
11
+ "typescript"
12
+ ],
13
+ "author": {
14
+ "name": "DjangoCFG",
15
+ "url": "https://djangocfg.com"
16
+ },
17
+ "homepage": "https://djangocfg.com",
18
+ "repository": {
19
+ "type": "git",
20
+ "url": "https://github.com/markolofsen/django-cfg.git",
21
+ "directory": "widgets/ogimage"
22
+ },
23
+ "license": "MIT",
24
+ "type": "module",
25
+ "main": "./src/index.ts",
26
+ "types": "./src/index.ts",
27
+ "exports": {
28
+ ".": {
29
+ "types": "./src/index.ts",
30
+ "default": "./src/index.ts"
31
+ }
32
+ },
33
+ "files": [
34
+ "src",
35
+ "!src/**/*.test.ts",
36
+ "!src/**/*.test.tsx",
37
+ "README.md",
38
+ "LICENSE"
39
+ ],
40
+ "scripts": {
41
+ "check": "tsc --noEmit",
42
+ "check-types": "tsc --noEmit",
43
+ "test": "vitest run",
44
+ "test:watch": "vitest",
45
+ "fonts": "node scripts/generate-fonts.mjs"
46
+ },
47
+ "peerDependencies": {
48
+ "next": ">=15.0.0",
49
+ "react": "^19.0.0"
50
+ },
51
+ "devDependencies": {
52
+ "@djangocfg/typescript-config": "^2.1.539",
53
+ "@types/node": "^25.9.5",
54
+ "@types/react": "19.2.15",
55
+ "next": "16.2.10",
56
+ "react": "19.2.7",
57
+ "react-dom": "19.2.7",
58
+ "typescript": "^5.9.3",
59
+ "vitest": "^3.2.7"
60
+ },
61
+ "publishConfig": {
62
+ "access": "public"
63
+ }
64
+ }
package/src/card.ts ADDED
@@ -0,0 +1,173 @@
1
+ import type { ReactNode } from 'react';
2
+
3
+ import { loadOgFonts, type OgFonts } from './fonts';
4
+ import { OG_CONTENT_TYPE, OG_SIZE, renderOgImage, type OgTheme } from './render';
5
+
6
+ /**
7
+ * The card fabric — what an Open Graph card SAYS, as opposed to how
8
+ * `render.tsx` draws it.
9
+ *
10
+ * An app binds its identity once with `createOgCards` and every
11
+ * `opengraph-image.tsx` calls one of the two fabrics, so a product link, a
12
+ * category link and an article link preview as three recognisably different
13
+ * cards while staying obviously one product.
14
+ *
15
+ * Two rules hold everywhere in this file:
16
+ *
17
+ * Never throw. A card is rendered while a crawler waits, and a route that
18
+ * 500s leaves a bare grey rectangle in the chat thread — strictly worse than
19
+ * a generic brand card. Callers pass what they managed to load, including
20
+ * nothing, and `brandCard` is the floor.
21
+ *
22
+ * Never invent. A missing field is absent, not a placeholder — a placeholder
23
+ * is a claim about the entity that the page itself does not make. This file
24
+ * trims; it never writes.
25
+ */
26
+
27
+ /**
28
+ * A title long enough to wrap eats the description's space, and Satori does
29
+ * not clip — it overflows into the footer. So the description budget is a
30
+ * function of the title's length, derived from `titleSizeFor`'s three steps
31
+ * and the 800px copy column.
32
+ *
33
+ * Trimming happens at a word boundary with an ellipsis, so the reader can see
34
+ * the text is a preview. Nothing is rewritten; only cut.
35
+ */
36
+ export function fitDescription(title: string, description: string | null | undefined): string | undefined {
37
+ const text = description?.trim();
38
+ if (!text) return undefined;
39
+
40
+ // Past ~44 characters the title is already two or three lines and owns the
41
+ // space between the mark and the footer, so it gets the card to itself.
42
+ const budget = title.length > 44 ? 0 : title.length > 34 ? 92 : 150;
43
+ if (budget === 0) return undefined;
44
+ if (text.length <= budget) return text;
45
+
46
+ const cut = text.slice(0, budget);
47
+ const lastSpace = cut.lastIndexOf(' ');
48
+ return `${(lastSpace > 24 ? cut.slice(0, lastSpace) : cut).replace(/[\s.,;:—-]+$/u, '')}…`;
49
+ }
50
+
51
+ /**
52
+ * Joins the footer's facts, dropping the ones that are absent rather than
53
+ * leaving a stray separator: "· Bahamas" reads as a rendering fault.
54
+ */
55
+ export function ogFooter(...parts: Array<string | number | null | undefined>): string | undefined {
56
+ const kept = parts
57
+ .map((part) => (typeof part === 'number' ? String(part) : part?.trim()))
58
+ .filter((part): part is string => Boolean(part));
59
+ return kept.length ? kept.join(' · ') : undefined;
60
+ }
61
+
62
+ /**
63
+ * An absolute URL for a photograph Satori is to draw.
64
+ *
65
+ * Satori fetches this itself, so a relative path resolves against nothing and
66
+ * the card renders without its image — hence `siteUrl`, which a root-relative
67
+ * path is resolved against. Anything that is not a usable absolute URL comes
68
+ * back `undefined`, which degrades to the text-only card: the one failure mode
69
+ * here that a reader would never notice.
70
+ */
71
+ export function ogImageUrl(path: string | null | undefined, siteUrl: string): string | undefined {
72
+ const raw = path?.trim();
73
+ if (!raw) return undefined;
74
+ if (raw.startsWith('data:')) return raw;
75
+ try {
76
+ const url = new URL(raw, siteUrl);
77
+ return url.protocol === 'http:' || url.protocol === 'https:' ? url.toString() : undefined;
78
+ } catch {
79
+ return undefined;
80
+ }
81
+ }
82
+
83
+ /** The identity every card from one app shares. Bound once with `createOgCards`. */
84
+ export interface OgCardsConfig {
85
+ /** Brand name — the brand card's title, and the wordmark on content cards. */
86
+ name: string;
87
+ /** Brand card description, unless the route passes its own. */
88
+ description?: string;
89
+ /** Brand card footer — the tagline. */
90
+ tagline?: string;
91
+ /** Host printed bottom-right, e.g. `djangocfg.com`. See `domainFromEnv`. */
92
+ domain?: string;
93
+ /** Site origin, so `contentCard({ image: '/media/x.jpg' })` resolves. */
94
+ siteUrl?: string;
95
+ accentColor?: string;
96
+ /** Brand mark, inline SVG. See `OgImageOptions.mark`. */
97
+ mark?: ReactNode;
98
+ theme?: Partial<OgTheme>;
99
+ /** Swap the typeface. Defaults to the vendored Plus Jakarta Sans. */
100
+ loadFonts?: () => Promise<OgFonts>;
101
+ }
102
+
103
+ export interface ContentCardOptions {
104
+ /**
105
+ * Nullable on purpose: a caller whose fetch failed passes nothing and gets
106
+ * the brand card, which is the honest fallback rather than a card headed
107
+ * "undefined".
108
+ */
109
+ title: string | null | undefined;
110
+ /** Small uppercase kicker: what KIND of thing this is ("Skill", "For sale"). */
111
+ eyebrow?: string;
112
+ description?: string | null;
113
+ /** Scannable facts. Defaults to the brand name. See `ogFooter`. */
114
+ footer?: string;
115
+ /** Absolute URL, root-relative path (needs `siteUrl`), or data URI. */
116
+ image?: string | null;
117
+ accentColor?: string;
118
+ }
119
+
120
+ export interface OgCards {
121
+ /** Re-export from every `opengraph-image.tsx`: `export { size, contentType }`. */
122
+ size: typeof OG_SIZE;
123
+ contentType: typeof OG_CONTENT_TYPE;
124
+ /** The site-wide fallback: home, section landings, and any detail route whose record failed to load. */
125
+ brandCard(eyebrow?: string, description?: string): Promise<Response>;
126
+ /** One catalogue entity. Falls back to `brandCard` when `title` is empty. */
127
+ contentCard(options: ContentCardOptions): Promise<Response>;
128
+ }
129
+
130
+ /** Binds an app's identity to the two fabrics. Call once, in an `_lib/og.ts`. */
131
+ export function createOgCards(config: OgCardsConfig): OgCards {
132
+ const loadFonts = config.loadFonts ?? loadOgFonts;
133
+ const shared = {
134
+ domain: config.domain,
135
+ accentColor: config.accentColor,
136
+ mark: config.mark,
137
+ theme: config.theme,
138
+ };
139
+
140
+ async function brandCard(eyebrow?: string, description?: string): Promise<Response> {
141
+ return renderOgImage(
142
+ {
143
+ ...shared,
144
+ title: config.name,
145
+ eyebrow,
146
+ description: fitDescription(config.name, description ?? config.description),
147
+ footer: config.tagline,
148
+ },
149
+ await loadFonts(),
150
+ );
151
+ }
152
+
153
+ async function contentCard(options: ContentCardOptions): Promise<Response> {
154
+ const title = options.title?.trim();
155
+ if (!title) return brandCard();
156
+
157
+ return renderOgImage(
158
+ {
159
+ ...shared,
160
+ accentColor: options.accentColor ?? config.accentColor,
161
+ brand: config.name,
162
+ title,
163
+ eyebrow: options.eyebrow,
164
+ description: fitDescription(title, options.description),
165
+ footer: options.footer ?? config.name,
166
+ image: ogImageUrl(options.image, config.siteUrl ?? 'http://invalid.invalid'),
167
+ },
168
+ await loadFonts(),
169
+ );
170
+ }
171
+
172
+ return { size: OG_SIZE, contentType: OG_CONTENT_TYPE, brandCard, contentCard };
173
+ }
package/src/file.ts ADDED
@@ -0,0 +1,22 @@
1
+ import { readFile } from 'node:fs/promises';
2
+
3
+ const dataUriCache = new Map<string, string>();
4
+
5
+ /**
6
+ * Reads a local image into a Satori-compatible data URI and caches the result.
7
+ *
8
+ * For a portrait or logo that ships with the app. Pass an absolute path built
9
+ * from `process.cwd()` — a `.jpg` beside a route file is not traced into a
10
+ * standalone build any more than a `.woff` is, so keep such assets somewhere
11
+ * `outputFileTracingIncludes` covers, or prefer an absolute URL via
12
+ * `ogImageUrl` when the image is already served.
13
+ */
14
+ export async function loadImageDataUri(path: string, mimeType: string): Promise<string> {
15
+ const cached = dataUriCache.get(path);
16
+ if (cached) return cached;
17
+
18
+ const data = await readFile(path);
19
+ const uri = `data:${mimeType};base64,${data.toString('base64')}`;
20
+ dataUriCache.set(path, uri);
21
+ return uri;
22
+ }
@@ -0,0 +1,70 @@
1
+ import type { ImageResponse } from 'next/og';
2
+
3
+ /**
4
+ * The typeface for generated Open Graph cards.
5
+ *
6
+ * Satori has no access to the browser's font stack, so every face a card draws
7
+ * with has to be handed to it as raw bytes. That rules out `next/font`, which
8
+ * is what the rest of an app uses: it emits `woff2` and returns CSS class
9
+ * names, and Satori reads neither.
10
+ *
11
+ * ── Why the bytes are inlined as base64 ───────────────────────────────────
12
+ *
13
+ * The obvious alternative is `readFile` at request time. It works in `dev` and
14
+ * then fails in production, because a `.woff` beside a route file is not a
15
+ * module import — nothing traces it, so the standalone build does not copy it
16
+ * into the output and the route throws ENOENT on the first shared link.
17
+ * Inlining makes the font part of the module graph, which is the one thing
18
+ * that guarantees it ships wherever the route ships.
19
+ *
20
+ * The cost is roughly 64KB of source across the three weights. It is paid once
21
+ * at build time and never reaches a visitor's browser — OG cards are rendered
22
+ * on the server for crawlers.
23
+ *
24
+ * GENERATED — do not hand-edit the base64 below. The source of truth is the
25
+ * three `PlusJakartaSans-*.woff` files in this directory; regenerate with
26
+ * `node scripts/generate-fonts.mjs` if a weight ever changes.
27
+ *
28
+ * Plus Jakarta Sans is the display face the djangocfg apps set their pages in,
29
+ * so a shared card and the page it links to are set in the same type. It is
30
+ * licensed under the SIL Open Font License 1.1.
31
+ */
32
+
33
+ export type OgFonts = NonNullable<ConstructorParameters<typeof ImageResponse>[1]>['fonts'];
34
+
35
+ /** Referenced by `render.tsx` as `fontFamily`; the two must not drift. */
36
+ export const OG_FONT_FAMILY = 'Jakarta';
37
+
38
+ const MEDIUM_B64 =
39
+ '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';
40
+
41
+ const SEMIBOLD_B64 =
42
+ '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';
43
+
44
+ const EXTRABOLD_B64 =
45
+ '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';
46
+
47
+ /**
48
+ * `Buffer` is available here (OG routes declare `runtime = 'nodejs'`) and is
49
+ * markedly faster than the `atob` + charCodeAt loop for buffers this size.
50
+ */
51
+ function decode(b64: string): ArrayBuffer {
52
+ const bytes = Buffer.from(b64, 'base64');
53
+ return bytes.buffer.slice(bytes.byteOffset, bytes.byteOffset + bytes.byteLength) as ArrayBuffer;
54
+ }
55
+
56
+ let cached: OgFonts | undefined;
57
+
58
+ /**
59
+ * The three weights `render.tsx` draws with. Decoded once per server process —
60
+ * a card is regenerated on cache misses, and re-decoding 64KB on each of them
61
+ * is pure waste.
62
+ */
63
+ export function loadOgFonts(): Promise<OgFonts> {
64
+ cached ??= [
65
+ { name: OG_FONT_FAMILY, data: decode(MEDIUM_B64), weight: 500, style: 'normal' as const },
66
+ { name: OG_FONT_FAMILY, data: decode(SEMIBOLD_B64), weight: 600, style: 'normal' as const },
67
+ { name: OG_FONT_FAMILY, data: decode(EXTRABOLD_B64), weight: 800, style: 'normal' as const },
68
+ ];
69
+ return Promise.resolve(cached);
70
+ }
package/src/index.ts ADDED
@@ -0,0 +1,16 @@
1
+ // ── Recommended API ──────────────────────────────────────────────────────────
2
+ // Bind once per app (`app/_lib/og.ts`), then every `opengraph-image.tsx`
3
+ // re-exports `size`/`contentType` and calls `brandCard` or `contentCard`.
4
+ export { createOgCards, fitDescription, ogFooter, ogImageUrl } from './card';
5
+ export type { ContentCardOptions, OgCards, OgCardsConfig } from './card';
6
+
7
+ // ── Site origin + the metadata trap ──────────────────────────────────────────
8
+ export { domainFromEnv, metadataBaseFromEnv, resolvePublicUrl, siteUrlFromEnv, trimTrailingSlash, withoutOgImages } from './url';
9
+ export type { SiteUrlOptions } from './url';
10
+
11
+ // ── Low-level: draw a card directly ──────────────────────────────────────────
12
+ export { DEFAULT_OG_THEME, OG_CONTENT_TYPE, OG_SIZE, renderOgImage, titleSizeFor } from './render';
13
+ export type { OgImageOptions, OgTheme } from './render';
14
+ export { loadOgFonts, OG_FONT_FAMILY } from './fonts';
15
+ export type { OgFonts } from './fonts';
16
+ export { loadImageDataUri } from './file';
package/src/render.tsx ADDED
@@ -0,0 +1,261 @@
1
+ import { ImageResponse } from 'next/og';
2
+ import type { ReactNode } from 'react';
3
+
4
+ import { OG_FONT_FAMILY, type OgFonts } from './fonts';
5
+
6
+ /**
7
+ * One Open Graph card renderer, every djangocfg app.
8
+ *
9
+ * A card is what a link BECOMES in WhatsApp, Telegram, Slack, iMessage and
10
+ * Facebook — often the only part of a site a person sees before deciding
11
+ * whether to open the page. This module draws it; `card.ts` decides what it
12
+ * says. Brand identity (name, mark, accent, palette) is passed in by the app,
13
+ * so a CMDOP card and an MLS card come from the same layout and still read as
14
+ * two products.
15
+ *
16
+ * ── Constraints this layout is built around ───────────────────────────────
17
+ *
18
+ * Satori is not a browser. It implements a subset of flexbox and needs
19
+ * `display: flex` stated on anything with children, computes no cascade, and
20
+ * parses no `oklch()` — so colours arrive here as hex/rgba literals.
21
+ *
22
+ * The frame is fixed at 1200x630 and NOTHING CLIPS: a title long enough to
23
+ * wrap past its allotted lines pushes into the footer instead of truncating.
24
+ * Callers hand over text that fits — `fitDescription` in `card.ts` owns that
25
+ * budget, and `titleSizeFor` below is the scale it reasons against.
26
+ */
27
+
28
+ export const OG_SIZE = { width: 1200, height: 630 } as const;
29
+ export const OG_CONTENT_TYPE = 'image/png';
30
+
31
+ /** Colours a card is drawn in. Every field has a default; override the few that carry the brand. */
32
+ export interface OgTheme {
33
+ /** Flat ground behind a text-only card, and the base a photograph sits on. */
34
+ background: string;
35
+ /** Darker corner of the background gradient. */
36
+ backgroundDeep: string;
37
+ /** Title and wordmark. */
38
+ foreground: string;
39
+ /** Description and domain. */
40
+ muted: string;
41
+ /** The inset hairline frame. */
42
+ border: string;
43
+ /** Eyebrow, mark, and the background wash. `accentColor` on the options overrides it per card. */
44
+ accent: string;
45
+ /** Footer line — the scannable facts. Defaults to `accent`. */
46
+ footer?: string;
47
+ }
48
+
49
+ /** Dark, neutral, blue-accented — the palette every consumer started from. */
50
+ export const DEFAULT_OG_THEME: OgTheme = {
51
+ background: '#07111D',
52
+ backgroundDeep: '#02070C',
53
+ foreground: '#F2F7FB',
54
+ muted: '#A9B7C5',
55
+ border: 'rgba(194, 216, 235, 0.22)',
56
+ accent: '#61B8FF',
57
+ };
58
+
59
+ export interface OgImageOptions {
60
+ /** The one line that must survive. Never empty — callers fall back first. */
61
+ title: string;
62
+ /** Small uppercase kicker: what KIND of thing this is. */
63
+ eyebrow?: string;
64
+ description?: string;
65
+ /**
66
+ * Bottom-left supporting line — price, category, author. Kept distinct from
67
+ * `description` because it is scanned, not read.
68
+ */
69
+ footer?: string;
70
+ /** Bottom-right origin, so a forwarded card still says where it came from. */
71
+ domain?: string;
72
+ /**
73
+ * A photograph to render behind the copy, as an absolute URL or data URI.
74
+ * When present a scrim darkens the left half so the copy survives whatever
75
+ * the photograph happens to be.
76
+ */
77
+ image?: string;
78
+ /** Per-card accent; falls back to `theme.accent`. */
79
+ accentColor?: string;
80
+ /**
81
+ * Brand wordmark drawn top-left, next to `mark`. Omit for a card whose
82
+ * `title` IS the brand (the site-wide brand card).
83
+ */
84
+ brand?: string;
85
+ /**
86
+ * Brand mark, top-left. Inline SVG only: Satori cannot load React icon
87
+ * components with CSS, and an `<img>` to a remote logo makes every card
88
+ * depend on a network fetch that can fail while a crawler waits.
89
+ */
90
+ mark?: ReactNode;
91
+ theme?: Partial<OgTheme>;
92
+ }
93
+
94
+ /**
95
+ * Type scale by length. These three steps are what `fitDescription` budgets
96
+ * against — changing them changes how much description fits, so the two move
97
+ * together.
98
+ */
99
+ export function titleSizeFor(title: string): number {
100
+ if (title.length > 58) return 52;
101
+ if (title.length > 34) return 66;
102
+ return 84;
103
+ }
104
+
105
+ /** Renders one Open Graph card as a PNG response. */
106
+ export function renderOgImage(options: OgImageOptions, fonts: OgFonts): Response {
107
+ const theme: OgTheme = { ...DEFAULT_OG_THEME, ...options.theme };
108
+ const accent = options.accentColor ?? theme.accent;
109
+ const footerColor = theme.footer ?? accent;
110
+ const withImage = Boolean(options.image);
111
+
112
+ // Two passes, tuned by rendering them. The photograph is the POINT of an
113
+ // image card, so the scrim protects the copy column and then gets out of the
114
+ // way: near-opaque under the copy, clearing past it so the right half stays
115
+ // a photograph; a light touch top and bottom for the mark and footer.
116
+ const imageScrim = withImage
117
+ ? `linear-gradient(90deg, rgba(2,8,14,0.92) 0%, rgba(2,8,14,0.86) 34%, rgba(2,8,14,0.48) 60%, rgba(2,8,14,0.12) 82%, rgba(2,8,14,0.04) 100%), linear-gradient(180deg, rgba(2,8,14,0.42) 0%, rgba(2,8,14,0.06) 34%, rgba(2,8,14,0.10) 62%, rgba(2,8,14,0.52) 100%)`
118
+ : undefined;
119
+
120
+ // Satori stringifies every style value, so an unwanted shadow must be ABSENT
121
+ // rather than `none`.
122
+ const shadow = (value: string) => (withImage ? { textShadow: value } : {});
123
+
124
+ return new ImageResponse(
125
+ (
126
+ <div
127
+ style={{
128
+ width: '100%',
129
+ height: '100%',
130
+ display: 'flex',
131
+ flexDirection: 'column',
132
+ justifyContent: 'space-between',
133
+ position: 'relative',
134
+ overflow: 'hidden',
135
+ padding: '70px 86px',
136
+ color: theme.foreground,
137
+ backgroundColor: theme.background,
138
+ // Without a photograph the card carries an accent wash off the top
139
+ // left, so a text-only card still reads as a brand rather than as a
140
+ // dark rectangle.
141
+ ...(withImage
142
+ ? {}
143
+ : {
144
+ backgroundImage: `radial-gradient(880px 520px at 2% -10%, ${accent}30, transparent 60%), linear-gradient(150deg, ${theme.backgroundDeep} 0%, ${theme.background} 76%)`,
145
+ }),
146
+ fontFamily: OG_FONT_FAMILY,
147
+ }}
148
+ >
149
+ {options.image ? (
150
+ <img
151
+ src={options.image}
152
+ width={OG_SIZE.width}
153
+ height={OG_SIZE.height}
154
+ style={{ position: 'absolute', top: 0, left: 0, width: '100%', height: '100%', objectFit: 'cover' }}
155
+ />
156
+ ) : null}
157
+ {/* `width`/`height`, NOT `inset: 0`. An absolutely positioned element
158
+ with no children and no explicit size collapses to a zero box in
159
+ Satori, and `inset` does not give it one — an overlay written that
160
+ way draws nothing at all. Same trap applies to the frame below. */}
161
+ {imageScrim ? (
162
+ <div style={{ position: 'absolute', top: 0, left: 0, width: '100%', height: '100%', backgroundImage: imageScrim }} />
163
+ ) : null}
164
+
165
+ {/* An inset hairline frame: a mount around a photograph, and a visible
166
+ edge on the white background of a chat thread. */}
167
+ <div
168
+ style={{
169
+ position: 'absolute',
170
+ top: 22,
171
+ left: 22,
172
+ width: OG_SIZE.width - 44,
173
+ height: OG_SIZE.height - 44,
174
+ border: `1px solid ${theme.border}`,
175
+ borderRadius: 26,
176
+ }}
177
+ />
178
+
179
+ <div style={{ display: 'flex', position: 'relative', alignItems: 'center', gap: 16, minHeight: 52 }}>
180
+ {options.mark ? <div style={{ display: 'flex', color: accent }}>{options.mark}</div> : null}
181
+ {options.brand ? (
182
+ <div style={{ display: 'flex', fontSize: 30, fontWeight: 800, letterSpacing: -0.6, ...shadow('0 1px 12px rgba(0,0,0,0.8)') }}>
183
+ {options.brand}
184
+ </div>
185
+ ) : null}
186
+ </div>
187
+
188
+ {/* Narrower over a photograph: the scrim's dark half is the left half,
189
+ and 720px keeps every wrapped line on ground the scrim covers. */}
190
+ <div style={{ display: 'flex', flexDirection: 'column', position: 'relative', maxWidth: withImage ? 720 : 800 }}>
191
+ {options.eyebrow ? (
192
+ <div
193
+ style={{
194
+ display: 'flex',
195
+ marginBottom: 16,
196
+ // The accent is tuned for the flat dark ground. Over a
197
+ // photograph even a scrimmed sky can rise under it, so the
198
+ // eyebrow goes to foreground there.
199
+ color: withImage ? theme.foreground : accent,
200
+ fontSize: 22,
201
+ fontWeight: 600,
202
+ letterSpacing: 2.2,
203
+ textTransform: 'uppercase',
204
+ ...shadow('0 1px 12px rgba(0,0,0,0.8)'),
205
+ }}
206
+ >
207
+ {options.eyebrow}
208
+ </div>
209
+ ) : null}
210
+ <div
211
+ style={{
212
+ display: 'flex',
213
+ fontSize: titleSizeFor(options.title),
214
+ fontWeight: 800,
215
+ lineHeight: 1.05,
216
+ letterSpacing: -1.5,
217
+ ...shadow('0 2px 22px rgba(0,0,0,0.55)'),
218
+ }}
219
+ >
220
+ {options.title}
221
+ </div>
222
+ {options.description ? (
223
+ <div
224
+ style={{
225
+ display: 'flex',
226
+ marginTop: 20,
227
+ // Muted grey is a deliberate step down on the flat card; over
228
+ // a photograph it lands too close to the scrimmed image, so
229
+ // the description keeps foreground and separates by size.
230
+ color: withImage ? theme.foreground : theme.muted,
231
+ fontSize: 24,
232
+ fontWeight: 500,
233
+ lineHeight: 1.32,
234
+ maxWidth: withImage ? 660 : 730,
235
+ ...shadow('0 1px 12px rgba(0,0,0,0.75)'),
236
+ }}
237
+ >
238
+ {options.description}
239
+ </div>
240
+ ) : null}
241
+ </div>
242
+
243
+ <div style={{ display: 'flex', position: 'relative', alignItems: 'flex-end', justifyContent: 'space-between' }}>
244
+ {options.footer ? (
245
+ <div style={{ display: 'flex', color: footerColor, fontSize: 23, fontWeight: 600, letterSpacing: 0.2, ...shadow('0 1px 12px rgba(0,0,0,0.85)') }}>
246
+ {options.footer}
247
+ </div>
248
+ ) : (
249
+ <div style={{ display: 'flex' }} />
250
+ )}
251
+ {options.domain ? (
252
+ <div style={{ display: 'flex', color: theme.muted, fontSize: 21, fontWeight: 500, ...shadow('0 1px 12px rgba(0,0,0,0.85)') }}>
253
+ {options.domain}
254
+ </div>
255
+ ) : null}
256
+ </div>
257
+ </div>
258
+ ),
259
+ { ...OG_SIZE, fonts },
260
+ );
261
+ }
package/src/url.ts ADDED
@@ -0,0 +1,67 @@
1
+ import type { Metadata } from 'next';
2
+
3
+ /**
4
+ * Site-origin helpers for OG routes — where a card says it came from, and the
5
+ * one metadata trap that silently disables every card.
6
+ */
7
+
8
+ export interface SiteUrlOptions {
9
+ /** App-owned canonical URL, used when the env var is not set (build-time page-data collection). */
10
+ fallback?: string;
11
+ /** Defaults to `process.env`; injectable for tests. */
12
+ env?: Record<string, string | undefined>;
13
+ }
14
+
15
+ export function trimTrailingSlash(value: string): string {
16
+ return value.replace(/\/+$/, '');
17
+ }
18
+
19
+ /**
20
+ * Resolve any public URL (`NEXT_PUBLIC_*`) against an app-owned fallback.
21
+ * Throws only when neither is given — a misconfiguration worth failing on.
22
+ */
23
+ export function resolvePublicUrl(value: string | undefined, fallback?: string): string {
24
+ const resolved = value || fallback;
25
+ if (!resolved) {
26
+ throw new Error('A public URL (NEXT_PUBLIC_*) or a fallback is required.');
27
+ }
28
+ return trimTrailingSlash(new URL(resolved).toString());
29
+ }
30
+
31
+ /** The canonical site origin, from `NEXT_PUBLIC_SITE_URL` or the fallback. */
32
+ export function siteUrlFromEnv(options: SiteUrlOptions = {}): string {
33
+ const env = options.env ?? process.env;
34
+ return resolvePublicUrl(env.NEXT_PUBLIC_SITE_URL, options.fallback);
35
+ }
36
+
37
+ export function metadataBaseFromEnv(options: SiteUrlOptions = {}): URL {
38
+ return new URL(siteUrlFromEnv(options));
39
+ }
40
+
41
+ /** The host to print bottom-right on a card, e.g. `djangocfg.com`. */
42
+ export function domainFromEnv(options: SiteUrlOptions = {}): string {
43
+ return metadataBaseFromEnv(options).host;
44
+ }
45
+
46
+ /**
47
+ * Lets a segment's `opengraph-image.tsx` control both OG and Twitter images.
48
+ *
49
+ * Next's file convention only applies where no explicit `images` is set in
50
+ * metadata — and an explicit entry WINS. So an `openGraph.images` array in a
51
+ * layout does not complement the cards below it, it silently disables them.
52
+ * Run generated metadata through this before returning it from a segment that
53
+ * owns a card.
54
+ */
55
+ export function withoutOgImages(metadata: Metadata): Metadata {
56
+ const openGraph = metadata.openGraph ? { ...metadata.openGraph } : undefined;
57
+ if (openGraph && 'images' in openGraph) {
58
+ delete (openGraph as { images?: unknown }).images;
59
+ }
60
+
61
+ const twitter = metadata.twitter ? { ...metadata.twitter } : undefined;
62
+ if (twitter && 'images' in twitter) {
63
+ delete (twitter as { images?: unknown }).images;
64
+ }
65
+
66
+ return { ...metadata, openGraph, twitter };
67
+ }