@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 +21 -0
- package/README.md +142 -0
- package/package.json +64 -0
- package/src/card.ts +173 -0
- package/src/file.ts +22 -0
- package/src/fonts/PlusJakartaSans-ExtraBold.woff +0 -0
- package/src/fonts/PlusJakartaSans-Medium.woff +0 -0
- package/src/fonts/PlusJakartaSans-SemiBold.woff +0 -0
- package/src/fonts/index.ts +70 -0
- package/src/index.ts +16 -0
- package/src/render.tsx +261 -0
- package/src/url.ts +67 -0
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
|
+
}
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
@@ -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
|
+
}
|