@djangocfg/nextjs 2.1.438 → 2.1.440

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.
@@ -2,6 +2,23 @@ import { Metadata } from 'next';
2
2
 
3
3
  type OGPreset = 'DARK' | 'DARK_BLUE' | 'DARK_PURPLE' | 'DARK_GREEN' | 'DARK_ROSE' | 'LIGHT' | 'LIGHT_GRAY' | 'LIGHT_WARM' | 'LIGHT_GREEN';
4
4
  type OGLayout = 'DEFAULT' | 'HERO' | 'ARTICLE' | 'MINIMAL';
5
+ /**
6
+ * Per-page OG image intent — the single axis that decides what the OG image is.
7
+ * A discriminated union so every case is explicit and type-checked:
8
+ *
9
+ * 'static' brand image from site config (the default)
10
+ * 'dynamic' render the page title onto the OG card
11
+ * { render: {...} } render with explicit preset/layout/colors
12
+ * { image, alt? } an exact image URL (e.g. a content screenshot)
13
+ */
14
+ type OgSpec = 'static' | 'dynamic' | {
15
+ render: Omit<OGImageParams, 'title'> & {
16
+ title?: string;
17
+ };
18
+ } | {
19
+ image: string;
20
+ alt?: string;
21
+ };
5
22
  /** Mirrors Django OGImageParams exactly */
6
23
  interface OGImageParams {
7
24
  title: string;
@@ -20,51 +37,6 @@ interface OGImageParams {
20
37
  page_url?: string;
21
38
  }
22
39
 
23
- /**
24
- * Builds an absolute (or relative) OG image URL pointing to Django's
25
- * django_ogimage endpoint: /cfg/og/<base64params>/
26
- *
27
- * The URL is suitable for use in og:image and twitter:image meta tags.
28
- */
29
- declare function buildOgUrl(params: OGImageParams): string;
30
-
31
- /**
32
- * Creates Next.js Metadata fragment with og:image and twitter:image
33
- * pointing to Django's django_ogimage renderer.
34
- *
35
- * Usage in generateMetadata():
36
- * return createOgMetadata({ title: 'Page', preset: 'DARK_BLUE' })
37
- */
38
- declare function createOgMetadata(params: OGImageParams): Metadata;
39
- /**
40
- * Options for `withOgImage`. Extends the dynamic-renderer params with an
41
- * `image` opt-out so a page can keep its own (static) OG image instead of the
42
- * dynamically-rendered one.
43
- */
44
- type WithOgImageOptions = Omit<OGImageParams, 'title'> & {
45
- title?: string;
46
- /**
47
- * Controls the OG image source:
48
- * - omitted / `true` → dynamic render via Django's /cfg/og/ (default)
49
- * - `false` → don't touch images; the page/layout's own og:image
50
- * is preserved (use for a ready-made static brand image)
51
- * - `string` → use this exact URL as a static og/twitter image
52
- */
53
- image?: boolean | string;
54
- };
55
- /**
56
- * Merges og:image metadata into an existing Metadata object.
57
- *
58
- * Usage:
59
- * // dynamic (default)
60
- * return withOgImage({ title: 'Page', description: '...' }, { preset: 'DARK_BLUE' })
61
- * // keep the page's own static image
62
- * return withOgImage(meta, { preset: 'DARK_BLUE', image: false })
63
- * // explicit static image
64
- * return withOgImage(meta, { image: '/static/ogimage.png' })
65
- */
66
- declare function withOgImage(metadata: Metadata, params?: WithOgImageOptions): Metadata;
67
-
68
40
  /**
69
41
  * Site-wide identity the metadata factory needs. Config-agnostic on purpose —
70
42
  * the package doesn't know about any app's `settings` object, so the consumer
@@ -79,43 +51,55 @@ interface SiteMetaConfig {
79
51
  siteUrl: string;
80
52
  /** Ready-made static brand OG image URL (absolute or root-relative). */
81
53
  ogImage: string;
54
+ /**
55
+ * Django origin that serves the dynamic OG renderer (`/cfg/og/`), e.g.
56
+ * "https://api.example.com". Required for `og: 'dynamic'` / `{ render }`.
57
+ *
58
+ * - set → dynamic OG images render against this origin
59
+ * - `''` → no backend: dynamic specs fall back to the static brand image
60
+ * (use this for a frontend with no Django, so no broken URLs)
61
+ * - omit → fall back to env vars (MEDIA_URL → API_URL → SITE_URL)
62
+ */
63
+ ogBackend?: string;
82
64
  /** Optional favicon / apple-touch icons forwarded to Metadata.icons. */
83
65
  icons?: Metadata['icons'];
84
- /** Locales for hreflang alternates (first is treated as x-default if no enDefault). */
66
+ /** Locales for hreflang alternates. Omit for single-locale sites. */
85
67
  locales?: readonly string[];
86
68
  /** Locale used for x-default hreflang (defaults to "en" or first locale). */
87
69
  defaultLocale?: string;
88
70
  }
89
- /** Per-page inputs. Everything is optional except what makes the page unique. */
71
+ /**
72
+ * Per-page inputs. Only `title` is really worth setting per page; everything
73
+ * else falls back to the site config.
74
+ */
90
75
  interface PageMeta {
91
- /** Page title (without the brand suffix the template adds it). */
76
+ /** Page title (without the brand suffix). Falls back to the site name. */
92
77
  title?: string;
93
78
  /** Page description; falls back to the site description. */
94
79
  description?: string;
95
80
  keywords?: string[];
96
81
  /** Current locale, used for canonical + openGraph.locale + alternates. */
97
82
  locale?: string;
98
- /** Path without locale prefix, e.g. "/skills". '' for home. Drives alternates. */
83
+ /** Path without locale prefix, e.g. "/skills". '' for home. */
99
84
  path?: string;
100
85
  /**
101
- * OG image control, forwarded to `withOgImage`:
102
- * - omitted → static brand image (`config.ogImage`) — the safe default
103
- * - `true` → dynamic render from the page title
104
- * - OGImageParams → dynamic render with explicit preset/layout/colors
105
- * - `false` no image (inherit whatever the layout set)
106
- * - string → explicit image URL
86
+ * What the OG image should be. Defaults to `'static'` (the brand image).
87
+ * - `'static'` brand image from config (default)
88
+ * - `'dynamic'` render the page title onto the card
89
+ * - `{ render: {...} }` render with explicit preset/layout/colors
90
+ * - `{ image, alt? }` an exact image URL (e.g. a content screenshot)
107
91
  */
108
- og?: boolean | string | Omit<OGImageParams, 'title'>;
92
+ og?: OgSpec;
93
+ /** Escape hatch: extra Metadata fields merged last (robots, authors, etc.). */
94
+ extra?: Metadata;
109
95
  }
110
96
  /**
111
- * Builds a complete Next.js `Metadata` object from a one-time site config plus
112
- * per-page inputs — title-template, description, openGraph, twitter, canonical
113
- * + hreflang alternates, and icons — so pages only declare what's unique.
97
+ * Builds a complete Next.js `Metadata` from a one-time site config plus per-page
98
+ * inputs — title-template, description, openGraph, twitter, canonical + hreflang,
99
+ * icons, and the OG image — so pages only declare what's unique.
114
100
  *
115
- * OG image defaults to the static brand image (`config.ogImage`); opt into the
116
- * dynamic renderer per page with `og: true` / `og: { preset: 'DARK_BLUE' }`.
117
- *
118
- * Bind the config once in your app, then call the bound fn from each page:
101
+ * The OG image is chosen by the single `og` field (defaults to the static brand
102
+ * image). There is exactly one way to do each thing.
119
103
  *
120
104
  * // app/_core/metadata.ts
121
105
  * export const createMetadata = makeMetadataFactory({
@@ -127,14 +111,62 @@ interface PageMeta {
127
111
  * locales: LOCALES,
128
112
  * })
129
113
  *
130
- * // any page.tsx
131
- * export const metadata = createMetadata({ title: 'Skills', path: '/skills' })
114
+ * // landing page — static brand image (default)
115
+ * export const metadata = createMetadata({ title: 'Pricing', path: '/pricing' })
116
+ * // content page — title rendered onto the card
117
+ * export const metadata = createMetadata({ title: post.title, path, og: 'dynamic' })
118
+ * // content page with its own image
119
+ * export const metadata = createMetadata({ title: p.name, og: { image: p.image } })
132
120
  */
133
121
  declare function createMetadata(config: SiteMetaConfig, page?: PageMeta): Metadata;
134
122
  /**
135
123
  * Curries `createMetadata` with a fixed site config so pages call a zero-config
136
- * factory. See the docstring on `createMetadata` for the usage pattern.
124
+ * factory. This is the single recommended entry point for app metadata.
137
125
  */
138
126
  declare function makeMetadataFactory(config: SiteMetaConfig): (page?: PageMeta) => Metadata;
139
127
 
140
- export { type OGImageParams, type OGLayout, type OGPreset, type PageMeta, type SiteMetaConfig, type WithOgImageOptions, buildOgUrl, createMetadata, createOgMetadata, makeMetadataFactory, withOgImage };
128
+ /**
129
+ * Builds an absolute (or relative) OG image URL pointing to Django's
130
+ * django_ogimage endpoint: /cfg/og/<base64params>/
131
+ *
132
+ * The URL is suitable for use in og:image and twitter:image meta tags.
133
+ *
134
+ * @param base Optional explicit backend origin (e.g. "https://api.example.com").
135
+ * When omitted, it's resolved from env (MEDIA_URL → API_URL → SITE_URL → '').
136
+ * Pass the `ogBackend` from your site config to avoid relying on env.
137
+ */
138
+ declare function buildOgUrl(params: OGImageParams, base?: string): string;
139
+
140
+ /**
141
+ * Metadata fragment whose OG image is rendered by Django's django_ogimage
142
+ * endpoint (/cfg/og/). Low-level — most callers use `createMetadata`'s `og`
143
+ * field instead.
144
+ */
145
+ declare function createOgMetadata(params: OGImageParams): Metadata;
146
+ /** Extract a plain title string from Next.js's title shapes. */
147
+ declare function extractTitle(metadata: Metadata): string;
148
+ /**
149
+ * Resolves an `OgSpec` against a base Metadata into a concrete og/twitter image
150
+ * fragment. `staticUrl` is the site's brand image, used for the `'static'` case.
151
+ *
152
+ * `backend` is the Django origin that serves the dynamic renderer. The dynamic
153
+ * cases (`'dynamic'`, `{ render }`) require it: when it's `undefined`/empty —
154
+ * e.g. a frontend with no Django backend — they **fall back to the static brand
155
+ * image** instead of emitting a broken `/cfg/og/` URL. (`undefined` still lets
156
+ * `buildOgUrl` fall back to env vars; pass `''` to force the static fallback.)
157
+ *
158
+ * This is the single decision point for "what is the OG image" — no other code
159
+ * branches on the spec.
160
+ */
161
+ declare function resolveOgImage(spec: OgSpec, staticUrl: string, fallbackTitle: string, backend?: string): Pick<Metadata, 'openGraph' | 'twitter'>;
162
+ /**
163
+ * Merges OG image metadata (dynamic render) into existing Metadata. Kept for
164
+ * low-level/manual use; `createMetadata` is the recommended entry point.
165
+ *
166
+ * return withOgImage({ title: 'Page' }, { preset: 'DARK_BLUE' })
167
+ */
168
+ declare function withOgImage(metadata: Metadata, params?: Omit<OGImageParams, 'title'> & {
169
+ title?: string;
170
+ }): Metadata;
171
+
172
+ export { type OGImageParams, type OGLayout, type OGPreset, type OgSpec, type PageMeta, type SiteMetaConfig, buildOgUrl, createMetadata, createOgMetadata, extractTitle, makeMetadataFactory, resolveOgImage, withOgImage };
@@ -16,62 +16,27 @@ function cleanParams(params) {
16
16
  )
17
17
  );
18
18
  }
19
- function buildOgUrl(params) {
19
+ function buildOgUrl(params, base) {
20
20
  const b64 = encodeBase64(JSON.stringify(cleanParams(params)));
21
- const base = resolveOgPublicBase();
22
- return `${base}/cfg/og/${b64}/`;
21
+ const origin = (base ?? resolveOgPublicBase()).replace(/\/$/, "");
22
+ return `${origin}/cfg/og/${b64}/`;
23
23
  }
24
24
 
25
25
  // src/og-image/metadata.ts
26
- function createOgMetadata(params) {
27
- const url = buildOgUrl(params);
28
- const image = { url, width: 1200, height: 630, alt: params.title };
29
- return {
30
- openGraph: {
31
- images: [image]
32
- },
33
- twitter: {
34
- card: "summary_large_image",
35
- images: [url]
36
- }
37
- };
26
+ var OG_WIDTH = 1200;
27
+ var OG_HEIGHT = 630;
28
+ function imageEntry(url, alt) {
29
+ return { url, width: OG_WIDTH, height: OG_HEIGHT, alt };
38
30
  }
39
- function withOgImage(metadata, params = {}) {
40
- const { image, ...ogParams } = params;
41
- if (image === false) {
42
- return metadata;
43
- }
44
- if (typeof image === "string") {
45
- return {
46
- ...metadata,
47
- openGraph: {
48
- ...metadata.openGraph,
49
- images: [{ url: image, width: 1200, height: 630, alt: extractTitle(metadata) }]
50
- },
51
- twitter: {
52
- card: "summary_large_image",
53
- ...metadata.twitter,
54
- images: [image]
55
- }
56
- };
57
- }
58
- const resolvedParams = {
59
- title: extractTitle(metadata),
60
- ...ogParams
61
- };
62
- const ogMeta = createOgMetadata(resolvedParams);
31
+ function ogImageFragment(url, alt) {
63
32
  return {
64
- ...metadata,
65
- openGraph: {
66
- ...metadata.openGraph,
67
- ...ogMeta.openGraph
68
- },
69
- twitter: {
70
- ...metadata.twitter,
71
- ...ogMeta.twitter
72
- }
33
+ openGraph: { images: [imageEntry(url, alt)] },
34
+ twitter: { card: "summary_large_image", images: [url] }
73
35
  };
74
36
  }
37
+ function createOgMetadata(params) {
38
+ return ogImageFragment(buildOgUrl(params), params.title);
39
+ }
75
40
  function extractTitle(metadata) {
76
41
  const t = metadata.title;
77
42
  if (!t) return "";
@@ -80,11 +45,34 @@ function extractTitle(metadata) {
80
45
  if (typeof t === "object" && "default" in t) return t.default;
81
46
  return "";
82
47
  }
48
+ function resolveOgImage(spec, staticUrl, fallbackTitle, backend) {
49
+ const canRenderDynamic = backend === void 0 || backend !== "";
50
+ if (spec === "static") {
51
+ return ogImageFragment(staticUrl, fallbackTitle);
52
+ }
53
+ if (spec === "dynamic") {
54
+ return canRenderDynamic ? ogImageFragment(buildOgUrl({ title: fallbackTitle }, backend), fallbackTitle) : ogImageFragment(staticUrl, fallbackTitle);
55
+ }
56
+ if ("image" in spec) {
57
+ return ogImageFragment(spec.image, spec.alt ?? fallbackTitle);
58
+ }
59
+ const title = spec.render.title ?? fallbackTitle;
60
+ return canRenderDynamic ? ogImageFragment(buildOgUrl({ ...spec.render, title }, backend), title) : ogImageFragment(staticUrl, title);
61
+ }
62
+ function withOgImage(metadata, params = {}) {
63
+ const title = params.title ?? extractTitle(metadata);
64
+ const frag = createOgMetadata({ ...params, title });
65
+ return {
66
+ ...metadata,
67
+ openGraph: { ...metadata.openGraph, ...frag.openGraph },
68
+ twitter: { ...metadata.twitter, ...frag.twitter }
69
+ };
70
+ }
83
71
 
84
72
  // src/og-image/metadata-factory.ts
85
73
  function buildAlternates(config, path, locale) {
86
74
  if (!config.locales?.length) {
87
- return { canonical: `${config.siteUrl}/${locale}${path}` };
75
+ return { canonical: `${config.siteUrl}/${path}`.replace(/\/+$/, "") || config.siteUrl };
88
76
  }
89
77
  const languages = {};
90
78
  for (const loc of config.locales) {
@@ -99,7 +87,9 @@ function createMetadata(config, page = {}) {
99
87
  const description = page.description ?? config.description;
100
88
  const locale = page.locale ?? config.defaultLocale ?? "en";
101
89
  const path = page.path ?? "";
102
- const base = {
90
+ const ogUrl = config.locales?.length ? `${config.siteUrl}/${locale}${path}` : `${config.siteUrl}${path}`;
91
+ const ogImage = resolveOgImage(page.og ?? "static", config.ogImage, title, config.ogBackend);
92
+ const metadata = {
103
93
  title,
104
94
  description,
105
95
  keywords: page.keywords,
@@ -107,29 +97,26 @@ function createMetadata(config, page = {}) {
107
97
  openGraph: {
108
98
  type: "website",
109
99
  locale,
110
- url: `${config.siteUrl}/${locale}${path}`,
100
+ url: ogUrl,
111
101
  siteName: config.name,
112
102
  title,
113
- description
103
+ description,
104
+ ...ogImage.openGraph
114
105
  },
115
106
  twitter: {
116
- card: "summary_large_image",
117
107
  title,
118
- description
108
+ description,
109
+ ...ogImage.twitter
119
110
  },
120
111
  icons: config.icons
121
112
  };
122
- let ogOptions;
123
- if (page.og === void 0 || page.og === false) {
124
- ogOptions = { image: config.ogImage };
125
- } else if (page.og === true) {
126
- ogOptions = {};
127
- } else if (typeof page.og === "string") {
128
- ogOptions = { image: page.og };
129
- } else {
130
- ogOptions = page.og;
131
- }
132
- return withOgImage(base, ogOptions);
113
+ if (!page.extra) return metadata;
114
+ return {
115
+ ...metadata,
116
+ ...page.extra,
117
+ openGraph: { ...metadata.openGraph, ...page.extra.openGraph },
118
+ twitter: { ...metadata.twitter, ...page.extra.twitter }
119
+ };
133
120
  }
134
121
  function makeMetadataFactory(config) {
135
122
  return (page = {}) => createMetadata(config, page);
@@ -138,7 +125,9 @@ export {
138
125
  buildOgUrl,
139
126
  createMetadata,
140
127
  createOgMetadata,
128
+ extractTitle,
141
129
  makeMetadataFactory,
130
+ resolveOgImage,
142
131
  withOgImage
143
132
  };
144
133
  //# sourceMappingURL=index.mjs.map
@@ -1 +1 @@
1
- {"version":3,"sources":["../../src/og-image/url.ts","../../src/og-image/metadata.ts","../../src/og-image/metadata-factory.ts"],"sourcesContent":["import type { OGImageParams } from './types'\n\n/**\n * Resolves the base URL for og:image meta tags.\n * Must be publicly accessible by crawlers (Twitter, Facebook, etc).\n *\n * Priority:\n * 1. NEXT_PUBLIC_MEDIA_URL — explicit media domain (nginx proxy or CDN)\n * 2. NEXT_PUBLIC_API_URL — direct Django API domain\n * 3. NEXT_PUBLIC_SITE_URL — site domain (same-domain/static build)\n * 4. '' — relative URL (last resort)\n *\n * Use cases:\n * - Same domain (static build): API_URL='' → relative /cfg/og/...\n * - Split domain (standalone): API_URL=https://api.x.com → absolute\n * - Media via nginx proxy: MEDIA_URL=https://x.com, API_URL=https://api.x.com\n * - CDN: MEDIA_URL=https://cdn.x.com\n */\nfunction resolveOgPublicBase(): string {\n if (typeof process === 'undefined') return ''\n return (\n process.env.NEXT_PUBLIC_MEDIA_URL?.replace(/\\/$/, '') ??\n process.env.NEXT_PUBLIC_API_URL?.replace(/\\/$/, '') ??\n process.env.NEXT_PUBLIC_SITE_URL?.replace(/\\/$/, '') ??\n ''\n )\n}\n\nfunction encodeBase64(str: string): string {\n if (typeof Buffer !== 'undefined') {\n return Buffer.from(str).toString('base64url')\n }\n // Browser fallback\n return btoa(unescape(encodeURIComponent(str)))\n .replace(/\\+/g, '-')\n .replace(/\\//g, '_')\n .replace(/=+$/, '')\n}\n\nfunction cleanParams(params: OGImageParams): Record<string, unknown> {\n return Object.fromEntries(\n Object.entries(params).filter(\n ([, v]) => v !== undefined && v !== null && v !== ''\n )\n )\n}\n\n/**\n * Builds an absolute (or relative) OG image URL pointing to Django's\n * django_ogimage endpoint: /cfg/og/<base64params>/\n *\n * The URL is suitable for use in og:image and twitter:image meta tags.\n */\nexport function buildOgUrl(params: OGImageParams): string {\n const b64 = encodeBase64(JSON.stringify(cleanParams(params)))\n const base = resolveOgPublicBase()\n return `${base}/cfg/og/${b64}/`\n}\n","import type { Metadata } from 'next'\nimport type { OGImageParams } from './types'\nimport { buildOgUrl } from './url'\n\n/**\n * Creates Next.js Metadata fragment with og:image and twitter:image\n * pointing to Django's django_ogimage renderer.\n *\n * Usage in generateMetadata():\n * return createOgMetadata({ title: 'Page', preset: 'DARK_BLUE' })\n */\nexport function createOgMetadata(params: OGImageParams): Metadata {\n const url = buildOgUrl(params)\n const image = { url, width: 1200, height: 630, alt: params.title }\n return {\n openGraph: {\n images: [image],\n },\n twitter: {\n card: 'summary_large_image',\n images: [url],\n },\n }\n}\n\n/**\n * Options for `withOgImage`. Extends the dynamic-renderer params with an\n * `image` opt-out so a page can keep its own (static) OG image instead of the\n * dynamically-rendered one.\n */\nexport type WithOgImageOptions = Omit<OGImageParams, 'title'> & {\n title?: string\n /**\n * Controls the OG image source:\n * - omitted / `true` → dynamic render via Django's /cfg/og/ (default)\n * - `false` → don't touch images; the page/layout's own og:image\n * is preserved (use for a ready-made static brand image)\n * - `string` → use this exact URL as a static og/twitter image\n */\n image?: boolean | string\n}\n\n/**\n * Merges og:image metadata into an existing Metadata object.\n *\n * Usage:\n * // dynamic (default)\n * return withOgImage({ title: 'Page', description: '...' }, { preset: 'DARK_BLUE' })\n * // keep the page's own static image\n * return withOgImage(meta, { preset: 'DARK_BLUE', image: false })\n * // explicit static image\n * return withOgImage(meta, { image: '/static/ogimage.png' })\n */\nexport function withOgImage(\n metadata: Metadata,\n params: WithOgImageOptions = {}\n): Metadata {\n const { image, ...ogParams } = params\n\n // Opt-out: leave the incoming metadata's images untouched.\n if (image === false) {\n return metadata\n }\n\n // Explicit static URL: set og/twitter images without hitting the renderer.\n if (typeof image === 'string') {\n return {\n ...metadata,\n openGraph: {\n ...metadata.openGraph,\n images: [{ url: image, width: 1200, height: 630, alt: extractTitle(metadata) }],\n },\n twitter: {\n card: 'summary_large_image',\n ...metadata.twitter,\n images: [image],\n },\n }\n }\n\n // Default: dynamic render. Auto-extract title; params.title overrides.\n const resolvedParams: OGImageParams = {\n title: extractTitle(metadata),\n ...ogParams,\n }\n\n const ogMeta = createOgMetadata(resolvedParams)\n\n return {\n ...metadata,\n openGraph: {\n ...metadata.openGraph,\n ...ogMeta.openGraph,\n },\n twitter: {\n ...metadata.twitter,\n ...ogMeta.twitter,\n },\n }\n}\n\nfunction extractTitle(metadata: Metadata): string {\n const t = metadata.title\n if (!t) return ''\n if (typeof t === 'string') return t\n if (typeof t === 'object' && 'absolute' in t) return (t as { absolute: string }).absolute\n if (typeof t === 'object' && 'default' in t) return (t as { default: string }).default\n return ''\n}\n","import type { Metadata } from 'next'\nimport type { OGImageParams } from './types'\nimport { withOgImage, type WithOgImageOptions } from './metadata'\n\n/**\n * Site-wide identity the metadata factory needs. Config-agnostic on purpose —\n * the package doesn't know about any app's `settings` object, so the consumer\n * passes these once (typically derived from their own settings module).\n */\nexport interface SiteMetaConfig {\n /** Brand / app name, e.g. \"CMDOP\". Used in titles, siteName, alt text. */\n name: string\n /** Default description used when a page doesn't supply its own. */\n description: string\n /** Absolute site origin, e.g. \"https://cmdop.com\". Used for canonical/og url. */\n siteUrl: string\n /** Ready-made static brand OG image URL (absolute or root-relative). */\n ogImage: string\n /** Optional favicon / apple-touch icons forwarded to Metadata.icons. */\n icons?: Metadata['icons']\n /** Locales for hreflang alternates (first is treated as x-default if no enDefault). */\n locales?: readonly string[]\n /** Locale used for x-default hreflang (defaults to \"en\" or first locale). */\n defaultLocale?: string\n}\n\n/** Per-page inputs. Everything is optional except what makes the page unique. */\nexport interface PageMeta {\n /** Page title (without the brand suffix — the template adds it). */\n title?: string\n /** Page description; falls back to the site description. */\n description?: string\n keywords?: string[]\n /** Current locale, used for canonical + openGraph.locale + alternates. */\n locale?: string\n /** Path without locale prefix, e.g. \"/skills\". '' for home. Drives alternates. */\n path?: string\n /**\n * OG image control, forwarded to `withOgImage`:\n * - omitted → static brand image (`config.ogImage`) — the safe default\n * - `true` → dynamic render from the page title\n * - OGImageParams → dynamic render with explicit preset/layout/colors\n * - `false` → no image (inherit whatever the layout set)\n * - string → explicit image URL\n */\n og?: boolean | string | Omit<OGImageParams, 'title'>\n}\n\nfunction buildAlternates(\n config: SiteMetaConfig,\n path: string,\n locale: string\n): Metadata['alternates'] {\n if (!config.locales?.length) {\n return { canonical: `${config.siteUrl}/${locale}${path}` }\n }\n const languages: Record<string, string> = {}\n for (const loc of config.locales) {\n languages[loc] = `${config.siteUrl}/${loc}${path}`\n }\n const xDefault = config.defaultLocale ?? (config.locales.includes('en') ? 'en' : config.locales[0])\n languages['x-default'] = `${config.siteUrl}/${xDefault}${path}`\n return { canonical: `${config.siteUrl}/${locale}${path}`, languages }\n}\n\n/**\n * Builds a complete Next.js `Metadata` object from a one-time site config plus\n * per-page inputs — title-template, description, openGraph, twitter, canonical\n * + hreflang alternates, and icons — so pages only declare what's unique.\n *\n * OG image defaults to the static brand image (`config.ogImage`); opt into the\n * dynamic renderer per page with `og: true` / `og: { preset: 'DARK_BLUE' }`.\n *\n * Bind the config once in your app, then call the bound fn from each page:\n *\n * // app/_core/metadata.ts\n * export const createMetadata = makeMetadataFactory({\n * name: settings.app.name,\n * description: settings.app.description,\n * siteUrl: settings.app.siteUrl,\n * ogImage: settings.app.media.ogimage,\n * icons: { icon: settings.app.media.favicon },\n * locales: LOCALES,\n * })\n *\n * // any page.tsx\n * export const metadata = createMetadata({ title: 'Skills', path: '/skills' })\n */\nexport function createMetadata(config: SiteMetaConfig, page: PageMeta = {}): Metadata {\n const title = page.title ?? config.name\n const description = page.description ?? config.description\n const locale = page.locale ?? config.defaultLocale ?? 'en'\n const path = page.path ?? ''\n\n const base: Metadata = {\n title,\n description,\n keywords: page.keywords,\n alternates: buildAlternates(config, path, locale),\n openGraph: {\n type: 'website',\n locale,\n url: `${config.siteUrl}/${locale}${path}`,\n siteName: config.name,\n title,\n description,\n },\n twitter: {\n card: 'summary_large_image',\n title,\n description,\n },\n icons: config.icons,\n }\n\n // Resolve the `og` shorthand into withOgImage options.\n let ogOptions: WithOgImageOptions\n if (page.og === undefined || page.og === false) {\n // Default & explicit-off both map to the static brand image. (false on the\n // factory means \"don't render dynamic\" — but we still want a brand image,\n // so we point at the static one. Use og:false at the page only if you've\n // set images elsewhere; here static is the friendly default.)\n ogOptions = { image: config.ogImage }\n } else if (page.og === true) {\n ogOptions = {} // dynamic, title auto-extracted\n } else if (typeof page.og === 'string') {\n ogOptions = { image: page.og }\n } else {\n ogOptions = page.og // OGImageParams → dynamic with overrides\n }\n\n return withOgImage(base, ogOptions)\n}\n\n/**\n * Curries `createMetadata` with a fixed site config so pages call a zero-config\n * factory. See the docstring on `createMetadata` for the usage pattern.\n */\nexport function makeMetadataFactory(config: SiteMetaConfig) {\n return (page: PageMeta = {}): Metadata => createMetadata(config, page)\n}\n"],"mappings":"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","names":[]}
1
+ {"version":3,"sources":["../../src/og-image/url.ts","../../src/og-image/metadata.ts","../../src/og-image/metadata-factory.ts"],"sourcesContent":["import type { OGImageParams } from './types'\n\n/**\n * Resolves the base URL for og:image meta tags.\n * Must be publicly accessible by crawlers (Twitter, Facebook, etc).\n *\n * Priority:\n * 1. NEXT_PUBLIC_MEDIA_URL — explicit media domain (nginx proxy or CDN)\n * 2. NEXT_PUBLIC_API_URL — direct Django API domain\n * 3. NEXT_PUBLIC_SITE_URL — site domain (same-domain/static build)\n * 4. '' — relative URL (last resort)\n *\n * Use cases:\n * - Same domain (static build): API_URL='' → relative /cfg/og/...\n * - Split domain (standalone): API_URL=https://api.x.com → absolute\n * - Media via nginx proxy: MEDIA_URL=https://x.com, API_URL=https://api.x.com\n * - CDN: MEDIA_URL=https://cdn.x.com\n */\nfunction resolveOgPublicBase(): string {\n if (typeof process === 'undefined') return ''\n return (\n process.env.NEXT_PUBLIC_MEDIA_URL?.replace(/\\/$/, '') ??\n process.env.NEXT_PUBLIC_API_URL?.replace(/\\/$/, '') ??\n process.env.NEXT_PUBLIC_SITE_URL?.replace(/\\/$/, '') ??\n ''\n )\n}\n\nfunction encodeBase64(str: string): string {\n if (typeof Buffer !== 'undefined') {\n return Buffer.from(str).toString('base64url')\n }\n // Browser fallback\n return btoa(unescape(encodeURIComponent(str)))\n .replace(/\\+/g, '-')\n .replace(/\\//g, '_')\n .replace(/=+$/, '')\n}\n\nfunction cleanParams(params: OGImageParams): Record<string, unknown> {\n return Object.fromEntries(\n Object.entries(params).filter(\n ([, v]) => v !== undefined && v !== null && v !== ''\n )\n )\n}\n\n/**\n * Builds an absolute (or relative) OG image URL pointing to Django's\n * django_ogimage endpoint: /cfg/og/<base64params>/\n *\n * The URL is suitable for use in og:image and twitter:image meta tags.\n *\n * @param base Optional explicit backend origin (e.g. \"https://api.example.com\").\n * When omitted, it's resolved from env (MEDIA_URL → API_URL → SITE_URL → '').\n * Pass the `ogBackend` from your site config to avoid relying on env.\n */\nexport function buildOgUrl(params: OGImageParams, base?: string): string {\n const b64 = encodeBase64(JSON.stringify(cleanParams(params)))\n const origin = (base ?? resolveOgPublicBase()).replace(/\\/$/, '')\n return `${origin}/cfg/og/${b64}/`\n}\n","import type { Metadata } from 'next'\nimport type { OGImageParams, OgSpec } from './types'\nimport { buildOgUrl } from './url'\n\nconst OG_WIDTH = 1200\nconst OG_HEIGHT = 630\n\n/** A Next.js OG image entry. */\nfunction imageEntry(url: string, alt: string) {\n return { url, width: OG_WIDTH, height: OG_HEIGHT, alt }\n}\n\n/**\n * Builds the og/twitter image fragment for a given image URL — the one place\n * that knows the shape Next.js expects. Both static URLs and the dynamic\n * renderer funnel through here, so there's a single source of truth.\n */\nfunction ogImageFragment(url: string, alt: string): Pick<Metadata, 'openGraph' | 'twitter'> {\n return {\n openGraph: { images: [imageEntry(url, alt)] },\n twitter: { card: 'summary_large_image', images: [url] },\n }\n}\n\n/**\n * Metadata fragment whose OG image is rendered by Django's django_ogimage\n * endpoint (/cfg/og/). Low-level — most callers use `createMetadata`'s `og`\n * field instead.\n */\nexport function createOgMetadata(params: OGImageParams): Metadata {\n return ogImageFragment(buildOgUrl(params), params.title)\n}\n\n/** Extract a plain title string from Next.js's title shapes. */\nexport function extractTitle(metadata: Metadata): string {\n const t = metadata.title\n if (!t) return ''\n if (typeof t === 'string') return t\n if (typeof t === 'object' && 'absolute' in t) return (t as { absolute: string }).absolute\n if (typeof t === 'object' && 'default' in t) return (t as { default: string }).default\n return ''\n}\n\n/**\n * Resolves an `OgSpec` against a base Metadata into a concrete og/twitter image\n * fragment. `staticUrl` is the site's brand image, used for the `'static'` case.\n *\n * `backend` is the Django origin that serves the dynamic renderer. The dynamic\n * cases (`'dynamic'`, `{ render }`) require it: when it's `undefined`/empty —\n * e.g. a frontend with no Django backend — they **fall back to the static brand\n * image** instead of emitting a broken `/cfg/og/` URL. (`undefined` still lets\n * `buildOgUrl` fall back to env vars; pass `''` to force the static fallback.)\n *\n * This is the single decision point for \"what is the OG image\" — no other code\n * branches on the spec.\n */\nexport function resolveOgImage(\n spec: OgSpec,\n staticUrl: string,\n fallbackTitle: string,\n backend?: string\n): Pick<Metadata, 'openGraph' | 'twitter'> {\n const canRenderDynamic = backend === undefined || backend !== ''\n\n if (spec === 'static') {\n return ogImageFragment(staticUrl, fallbackTitle)\n }\n if (spec === 'dynamic') {\n return canRenderDynamic\n ? ogImageFragment(buildOgUrl({ title: fallbackTitle }, backend), fallbackTitle)\n : ogImageFragment(staticUrl, fallbackTitle)\n }\n if ('image' in spec) {\n return ogImageFragment(spec.image, spec.alt ?? fallbackTitle)\n }\n // { render: {...} } — dynamic with explicit params; title defaults to page title.\n const title = spec.render.title ?? fallbackTitle\n return canRenderDynamic\n ? ogImageFragment(buildOgUrl({ ...spec.render, title }, backend), title)\n : ogImageFragment(staticUrl, title)\n}\n\n/**\n * Merges OG image metadata (dynamic render) into existing Metadata. Kept for\n * low-level/manual use; `createMetadata` is the recommended entry point.\n *\n * return withOgImage({ title: 'Page' }, { preset: 'DARK_BLUE' })\n */\nexport function withOgImage(\n metadata: Metadata,\n params: Omit<OGImageParams, 'title'> & { title?: string } = {}\n): Metadata {\n const title = params.title ?? extractTitle(metadata)\n const frag = createOgMetadata({ ...params, title })\n return {\n ...metadata,\n openGraph: { ...metadata.openGraph, ...frag.openGraph },\n twitter: { ...metadata.twitter, ...frag.twitter },\n }\n}\n","import type { Metadata } from 'next'\nimport type { OgSpec } from './types'\nimport { resolveOgImage } from './metadata'\n\n/**\n * Site-wide identity the metadata factory needs. Config-agnostic on purpose —\n * the package doesn't know about any app's `settings` object, so the consumer\n * passes these once (typically derived from their own settings module).\n */\nexport interface SiteMetaConfig {\n /** Brand / app name, e.g. \"CMDOP\". Used in titles, siteName, alt text. */\n name: string\n /** Default description used when a page doesn't supply its own. */\n description: string\n /** Absolute site origin, e.g. \"https://cmdop.com\". Used for canonical/og url. */\n siteUrl: string\n /** Ready-made static brand OG image URL (absolute or root-relative). */\n ogImage: string\n /**\n * Django origin that serves the dynamic OG renderer (`/cfg/og/`), e.g.\n * \"https://api.example.com\". Required for `og: 'dynamic'` / `{ render }`.\n *\n * - set → dynamic OG images render against this origin\n * - `''` → no backend: dynamic specs fall back to the static brand image\n * (use this for a frontend with no Django, so no broken URLs)\n * - omit → fall back to env vars (MEDIA_URL → API_URL → SITE_URL)\n */\n ogBackend?: string\n /** Optional favicon / apple-touch icons forwarded to Metadata.icons. */\n icons?: Metadata['icons']\n /** Locales for hreflang alternates. Omit for single-locale sites. */\n locales?: readonly string[]\n /** Locale used for x-default hreflang (defaults to \"en\" or first locale). */\n defaultLocale?: string\n}\n\n/**\n * Per-page inputs. Only `title` is really worth setting per page; everything\n * else falls back to the site config.\n */\nexport interface PageMeta {\n /** Page title (without the brand suffix). Falls back to the site name. */\n title?: string\n /** Page description; falls back to the site description. */\n description?: string\n keywords?: string[]\n /** Current locale, used for canonical + openGraph.locale + alternates. */\n locale?: string\n /** Path without locale prefix, e.g. \"/skills\". '' for home. */\n path?: string\n /**\n * What the OG image should be. Defaults to `'static'` (the brand image).\n * - `'static'` brand image from config (default)\n * - `'dynamic'` render the page title onto the card\n * - `{ render: {...} }` render with explicit preset/layout/colors\n * - `{ image, alt? }` an exact image URL (e.g. a content screenshot)\n */\n og?: OgSpec\n /** Escape hatch: extra Metadata fields merged last (robots, authors, etc.). */\n extra?: Metadata\n}\n\nfunction buildAlternates(\n config: SiteMetaConfig,\n path: string,\n locale: string\n): Metadata['alternates'] {\n if (!config.locales?.length) {\n return { canonical: `${config.siteUrl}/${path}`.replace(/\\/+$/, '') || config.siteUrl }\n }\n const languages: Record<string, string> = {}\n for (const loc of config.locales) {\n languages[loc] = `${config.siteUrl}/${loc}${path}`\n }\n const xDefault =\n config.defaultLocale ?? (config.locales.includes('en') ? 'en' : config.locales[0])\n languages['x-default'] = `${config.siteUrl}/${xDefault}${path}`\n return { canonical: `${config.siteUrl}/${locale}${path}`, languages }\n}\n\n/**\n * Builds a complete Next.js `Metadata` from a one-time site config plus per-page\n * inputs — title-template, description, openGraph, twitter, canonical + hreflang,\n * icons, and the OG image — so pages only declare what's unique.\n *\n * The OG image is chosen by the single `og` field (defaults to the static brand\n * image). There is exactly one way to do each thing.\n *\n * // app/_core/metadata.ts\n * export const createMetadata = makeMetadataFactory({\n * name: settings.app.name,\n * description: settings.app.description,\n * siteUrl: settings.app.siteUrl,\n * ogImage: settings.app.media.ogimage,\n * icons: { icon: settings.app.media.favicon },\n * locales: LOCALES,\n * })\n *\n * // landing page — static brand image (default)\n * export const metadata = createMetadata({ title: 'Pricing', path: '/pricing' })\n * // content page — title rendered onto the card\n * export const metadata = createMetadata({ title: post.title, path, og: 'dynamic' })\n * // content page with its own image\n * export const metadata = createMetadata({ title: p.name, og: { image: p.image } })\n */\nexport function createMetadata(config: SiteMetaConfig, page: PageMeta = {}): Metadata {\n const title = page.title ?? config.name\n const description = page.description ?? config.description\n const locale = page.locale ?? config.defaultLocale ?? 'en'\n const path = page.path ?? ''\n const ogUrl = config.locales?.length\n ? `${config.siteUrl}/${locale}${path}`\n : `${config.siteUrl}${path}`\n\n const ogImage = resolveOgImage(page.og ?? 'static', config.ogImage, title, config.ogBackend)\n\n const metadata: Metadata = {\n title,\n description,\n keywords: page.keywords,\n alternates: buildAlternates(config, path, locale),\n openGraph: {\n type: 'website',\n locale,\n url: ogUrl,\n siteName: config.name,\n title,\n description,\n ...ogImage.openGraph,\n },\n twitter: {\n title,\n description,\n ...ogImage.twitter,\n },\n icons: config.icons,\n }\n\n if (!page.extra) return metadata\n // Shallow-merge the escape hatch, but deep-merge the nested og/twitter so the\n // image we just set isn't clobbered by a partial `extra.openGraph`.\n return {\n ...metadata,\n ...page.extra,\n openGraph: { ...metadata.openGraph, ...page.extra.openGraph },\n twitter: { ...metadata.twitter, ...page.extra.twitter },\n }\n}\n\n/**\n * Curries `createMetadata` with a fixed site config so pages call a zero-config\n * factory. This is the single recommended entry point for app metadata.\n */\nexport function makeMetadataFactory(config: SiteMetaConfig) {\n return (page: PageMeta = {}): Metadata => createMetadata(config, page)\n}\n"],"mappings":"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","names":[]}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@djangocfg/nextjs",
3
- "version": "2.1.438",
3
+ "version": "2.1.440",
4
4
  "description": "Next.js server utilities: sitemap, health, OG images, contact forms, navigation, config",
5
5
  "keywords": [
6
6
  "nextjs",
@@ -143,9 +143,9 @@
143
143
  "ai-docs": "tsx src/ai/cli.ts"
144
144
  },
145
145
  "peerDependencies": {
146
- "@djangocfg/i18n": "^2.1.438",
147
- "@djangocfg/monitor": "^2.1.438",
148
- "@djangocfg/ui-core": "^2.1.438",
146
+ "@djangocfg/i18n": "^2.1.440",
147
+ "@djangocfg/monitor": "^2.1.440",
148
+ "@djangocfg/ui-core": "^2.1.440",
149
149
  "next": "^16.2.2"
150
150
  },
151
151
  "peerDependenciesMeta": {
@@ -167,11 +167,11 @@
167
167
  "serwist": "^9.2.3"
168
168
  },
169
169
  "devDependencies": {
170
- "@djangocfg/i18n": "^2.1.438",
171
- "@djangocfg/monitor": "^2.1.438",
172
- "@djangocfg/ui-core": "^2.1.438",
173
- "@djangocfg/layouts": "^2.1.438",
174
- "@djangocfg/typescript-config": "^2.1.438",
170
+ "@djangocfg/i18n": "^2.1.440",
171
+ "@djangocfg/monitor": "^2.1.440",
172
+ "@djangocfg/ui-core": "^2.1.440",
173
+ "@djangocfg/layouts": "^2.1.440",
174
+ "@djangocfg/typescript-config": "^2.1.440",
175
175
  "@types/node": "^25.2.3",
176
176
  "@types/react": "^19.2.15",
177
177
  "@types/react-dom": "^19.2.3",