@ingram-tech/nk-seo 0.6.2 → 0.8.0
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/README.md +160 -16
- package/dist/alternates.d.ts +31 -8
- package/dist/alternates.d.ts.map +1 -1
- package/dist/alternates.js +36 -19
- package/dist/alternates.js.map +1 -1
- package/dist/index.d.ts +2 -2
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +2 -2
- package/dist/index.js.map +1 -1
- package/dist/metadata.d.ts +55 -1
- package/dist/metadata.d.ts.map +1 -1
- package/dist/metadata.js +31 -2
- package/dist/metadata.js.map +1 -1
- package/dist/schema.d.ts +136 -12
- package/dist/schema.d.ts.map +1 -1
- package/dist/schema.js +111 -3
- package/dist/schema.js.map +1 -1
- package/dist/verify.d.ts +49 -0
- package/dist/verify.d.ts.map +1 -0
- package/dist/verify.js +136 -0
- package/dist/verify.js.map +1 -0
- package/package.json +12 -7
package/README.md
CHANGED
|
@@ -5,8 +5,9 @@ keep re-implementing:
|
|
|
5
5
|
|
|
6
6
|
1. **`<JsonLd>`** + **typed schema.org builders** — `faqPage`, `breadcrumbList`,
|
|
7
7
|
`article`, `softwareApplication`, `organization`, `website`, `person`,
|
|
8
|
-
`localBusiness`, `event`,
|
|
9
|
-
paths and injects your
|
|
8
|
+
`localBusiness`, `event`, `dataset`, `definedTerm`/`definedTermSet`, and a
|
|
9
|
+
`createSeo` factory that resolves site-relative paths and injects your
|
|
10
|
+
publisher.
|
|
10
11
|
2. **`createMetadata`** — a Next `Metadata` factory: canonical + OpenGraph +
|
|
11
12
|
Twitter card from one title/description/path.
|
|
12
13
|
3. **`createSitemap` / `createRobots`** — `app/sitemap.ts` and `app/robots.ts`
|
|
@@ -75,6 +76,53 @@ or an array (e.g. `[organization(org), website(site)]` on the homepage), and
|
|
|
75
76
|
escapes `<` on serialization so CMS-sourced strings can't break out of the
|
|
76
77
|
`<script>` tag.
|
|
77
78
|
|
|
79
|
+
### Properties the builders don't cover
|
|
80
|
+
|
|
81
|
+
schema.org is much larger than any builder surface, so every builder takes an
|
|
82
|
+
`extra` object shallow-merged over the built payload (it wins on conflict).
|
|
83
|
+
Reach for it rather than dropping the whole node back to a literal:
|
|
84
|
+
|
|
85
|
+
```ts
|
|
86
|
+
website({
|
|
87
|
+
name: "Acme",
|
|
88
|
+
url: "https://example.com",
|
|
89
|
+
extra: {
|
|
90
|
+
alternateName: "ACME",
|
|
91
|
+
inLanguage: ["en", "fr"],
|
|
92
|
+
potentialAction: {
|
|
93
|
+
"@type": "SearchAction",
|
|
94
|
+
target: "https://example.com/search?q={search_term_string}",
|
|
95
|
+
"query-input": "required name=search_term_string",
|
|
96
|
+
},
|
|
97
|
+
},
|
|
98
|
+
});
|
|
99
|
+
```
|
|
100
|
+
|
|
101
|
+
`extra` works on nested inputs too — `publisher: { name: "Acme", extra: {…} }`.
|
|
102
|
+
Values passed this way aren't reflected in the returned node type.
|
|
103
|
+
|
|
104
|
+
For a node type no builder covers, `<JsonLd data={…} />` takes **any** object or
|
|
105
|
+
array of objects. Hand-written nodes still get the `<` / U+2028 escaping, which
|
|
106
|
+
is the security-relevant half of the package — worth preferring over
|
|
107
|
+
`dangerouslySetInnerHTML` even with no builder involved.
|
|
108
|
+
|
|
109
|
+
`article` covers `Article`, `BlogPosting`, `NewsArticle`, `ScholarlyArticle`,
|
|
110
|
+
`TechArticle` (the right type for API/developer docs) and `Report` via `type`.
|
|
111
|
+
`dataset` and `definedTerm`/`definedTermSet` mark up data and controlled
|
|
112
|
+
vocabularies:
|
|
113
|
+
|
|
114
|
+
```ts
|
|
115
|
+
<JsonLd
|
|
116
|
+
data={seo.definedTerm({
|
|
117
|
+
name: "Computer programming",
|
|
118
|
+
termCode: "62.01",
|
|
119
|
+
path: "/code/62.01",
|
|
120
|
+
inDefinedTermSet: { name: "Acme classification", path: "/", version: "2025" },
|
|
121
|
+
extra: { inLanguage: "en" },
|
|
122
|
+
})}
|
|
123
|
+
/>
|
|
124
|
+
```
|
|
125
|
+
|
|
78
126
|
## Page metadata
|
|
79
127
|
|
|
80
128
|
```ts
|
|
@@ -103,10 +151,26 @@ export const metadata = pageMetadata({
|
|
|
103
151
|
|
|
104
152
|
Produces `title`, `description`, a self-referencing `alternates.canonical`,
|
|
105
153
|
`openGraph`, and a `summary_large_image` Twitter card. Pass `noIndex`, `keywords`,
|
|
106
|
-
`type: "article"`, or per-page `openGraph`/`twitter` overrides as
|
|
154
|
+
`type: "article"`, or per-page `alternates`/`openGraph`/`twitter` overrides as
|
|
155
|
+
needed — each is merged into the generated object and wins on conflict. With
|
|
107
156
|
`titleTemplate` set, `pageMetadata.root()` emits `title.template`, so plain page
|
|
108
157
|
titles render as "Services | Acme" without every page appending the suffix.
|
|
109
158
|
|
|
159
|
+
On a localized site the per-page `alternates` and `locale` are what make the
|
|
160
|
+
factory usable — `alternates.languages` is what Next renders as
|
|
161
|
+
`<link rel="alternate" hreflang>`, and `og:locale` has to vary per page:
|
|
162
|
+
|
|
163
|
+
```ts
|
|
164
|
+
import { hreflangAlternates } from "@ingram-tech/nk-seo";
|
|
165
|
+
|
|
166
|
+
const { languages } = hreflangAlternates(HREFLANG_CONFIG, `/${locale}/about`);
|
|
167
|
+
return pageMetadata({
|
|
168
|
+
title, description, path: `/${locale}/about`,
|
|
169
|
+
alternates: { languages },
|
|
170
|
+
locale: "fr_BE", // overrides the site-wide `locale`
|
|
171
|
+
});
|
|
172
|
+
```
|
|
173
|
+
|
|
110
174
|
## Sitemap & robots
|
|
111
175
|
|
|
112
176
|
```ts
|
|
@@ -171,17 +235,50 @@ Pass `logo` (absolute URL or data URI) to replace the accent-square mark with
|
|
|
171
235
|
your logo, and `fonts` + `fontFamily` to render with the brand typeface —
|
|
172
236
|
`fonts` is forwarded to `ImageResponse`, which takes raw TTF/OTF/WOFF data.
|
|
173
237
|
|
|
238
|
+
### Two ways the generated card silently doesn't reach your pages
|
|
239
|
+
|
|
240
|
+
Both fail with a green build and no warning; only fetching each page type shows
|
|
241
|
+
them.
|
|
242
|
+
|
|
243
|
+
**A page's own `openGraph` drops the inherited image.** Next attaches a
|
|
244
|
+
file-convention image to the segment that declares it, and sibling metadata
|
|
245
|
+
objects are *replaced*, not deep-merged — so any page whose `generateMetadata`
|
|
246
|
+
returns an `openGraph` object (i.e. any page setting `og:title`/`og:url`) emits
|
|
247
|
+
no `og:image` at all. The home page, sitting in the same segment as the image
|
|
248
|
+
file, looks perfectly correct throughout. Build the list once and spread it into
|
|
249
|
+
every page:
|
|
250
|
+
|
|
251
|
+
```ts
|
|
252
|
+
import { ogImageMetadata } from "@ingram-tech/nk-seo";
|
|
253
|
+
|
|
254
|
+
const images = ogImageMetadata({ baseUrl, path: "/opengraph-image", alt: "Acme" });
|
|
255
|
+
return { title, openGraph: { title, url, images } };
|
|
256
|
+
```
|
|
257
|
+
|
|
258
|
+
`createMetadata`'s `defaultImage` / per-page `image` already do this — point
|
|
259
|
+
either at the image route and every page it builds carries the card.
|
|
260
|
+
|
|
261
|
+
**Locale-negotiating middleware eats the image route.** A site that redirects
|
|
262
|
+
any path lacking a locale prefix also redirects `/opengraph-image`, so the card
|
|
263
|
+
URL never resolves. Put the file at `app/[locale]/opengraph-image.tsx` instead:
|
|
264
|
+
it dodges the redirect and gives per-locale cards from `params`. That is the
|
|
265
|
+
better default for any localized site.
|
|
266
|
+
|
|
174
267
|
The template encodes the Satori rule that trips everyone up: every node with
|
|
175
268
|
more than one child sets `display: flex`, and text nodes are never mixed with
|
|
176
269
|
sibling elements — so the headline stays a plain string and the accent rides on
|
|
177
270
|
the mark, not a coloured `<span>` inside the title.
|
|
178
271
|
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
272
|
+
Type-check won't help here: `ImageResponse` accepts all of
|
|
273
|
+
`React.CSSProperties` and Satori silently drops what it doesn't know. Two guards
|
|
274
|
+
cover that. `@ingram-tech/nk-dev`'s oxlint plugin ships `nextkit/satori-css`,
|
|
275
|
+
which flags unsupported style properties, `calc()`, and the two structural rules
|
|
276
|
+
in files that import `next/og` or are an `opengraph-image`/`twitter-image`
|
|
277
|
+
convention. And rendering remains the final validator: this package renders its
|
|
278
|
+
template through the real satori + resvg pipeline in its own tests, so a site
|
|
279
|
+
hand-rolling extra cards should add the same guard — a vitest file (node
|
|
280
|
+
environment) that renders each `opengraph-image.tsx` and asserts a valid PNG
|
|
281
|
+
comes out.
|
|
185
282
|
|
|
186
283
|
## Hreflang & canonical
|
|
187
284
|
|
|
@@ -219,6 +316,22 @@ import { HreflangLinks } from "@ingram-tech/nk-seo/components";
|
|
|
219
316
|
/>
|
|
220
317
|
```
|
|
221
318
|
|
|
319
|
+
Sites that prefix **every** locale — where `/about` redirects to `/en/about`
|
|
320
|
+
and no bare path exists — pass `prefixDefaultLocale`, which prefixes the default
|
|
321
|
+
locale too and points `x-default` at that prefixed URL. Without it the bare path
|
|
322
|
+
is emitted for both `en` and `x-default`, annotating URLs that redirect (Google
|
|
323
|
+
wants every hreflang target to answer 200 directly).
|
|
324
|
+
|
|
325
|
+
```tsx
|
|
326
|
+
<HreflangLinks
|
|
327
|
+
baseUrl="https://example.com"
|
|
328
|
+
locales={["en", "fr", "nl"]}
|
|
329
|
+
strategy="prefix"
|
|
330
|
+
defaultLocale="en"
|
|
331
|
+
prefixDefaultLocale
|
|
332
|
+
/>
|
|
333
|
+
```
|
|
334
|
+
|
|
222
335
|
Pass `pathname` explicitly if you don't use the `x-pathname` header. When
|
|
223
336
|
neither is available the component **throws** instead of guessing — a silent
|
|
224
337
|
fallback would canonicalize every page to the homepage, the kind of site-wide
|
|
@@ -228,11 +341,42 @@ Two rules of the road:
|
|
|
228
341
|
|
|
229
342
|
- **One canonical per page.** If your pages already set `alternates.canonical`
|
|
230
343
|
(e.g. via `createMetadata`), render `<HreflangLinks canonical={false} />`.
|
|
231
|
-
- **The canonical must self-reference
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
|
|
344
|
+
- **The canonical must self-reference, and it follows the ADDRESS, not the
|
|
345
|
+
rendered language.** A localized variant that canonicalizes to a different URL
|
|
346
|
+
makes Google discard the whole hreflang cluster. Under `"query"` the bare path
|
|
347
|
+
is the negotiating `x-default` and belongs to no locale, so `/pricing`
|
|
348
|
+
canonicalizes to `/pricing` even while negotiation renders it in French — pass
|
|
349
|
+
`currentLocale` only when the URL itself names a locale. The prefix strategy
|
|
350
|
+
auto-detects it from the (possibly `/fr/…`-prefixed) pathname; the query
|
|
351
|
+
strategy can't see the query string server-side, so
|
|
352
|
+
[`hreflangConfigFor(routing)`](../nk-i18n/README.md) from
|
|
353
|
+
`@ingram-tech/nk-i18n/next` is the wiring that gets this right for you.
|
|
236
354
|
- Building metadata instead of rendering links? The pure `hreflangAlternates`
|
|
237
|
-
(package root) returns the same
|
|
238
|
-
`
|
|
355
|
+
(package root) returns the same links for use in `generateMetadata`:
|
|
356
|
+
`{ canonical, links, languages }`, where `languages` is already keyed by
|
|
357
|
+
hreflang for `Metadata.alternates.languages` (or `createMetadata`'s
|
|
358
|
+
per-page `alternates`).
|
|
359
|
+
|
|
360
|
+
## Verifying the cluster (`/verify`)
|
|
361
|
+
|
|
362
|
+
hreflang is a set of promises about **other** URLs, and nothing local can tell
|
|
363
|
+
you whether they hold. A site can emit a flawless cluster while its middleware
|
|
364
|
+
redirects every URL in it away, or while each variant quietly canonicalizes to
|
|
365
|
+
the default language. Both delete the non-default languages from search, neither
|
|
366
|
+
raises an error, and Search Console reports them as ordinary redirects and
|
|
367
|
+
duplicates months later. Fetch them:
|
|
368
|
+
|
|
369
|
+
```ts
|
|
370
|
+
import { assertHreflangCluster } from "@ingram-tech/nk-seo/verify";
|
|
371
|
+
import { routing } from "@/lib/i18n/routing";
|
|
372
|
+
|
|
373
|
+
await assertHreflangCluster(routing, ["/", "/pricing", "/docs/getting-started"]);
|
|
374
|
+
```
|
|
375
|
+
|
|
376
|
+
It fails on an advertised URL that redirects or isn't 200, a variant that
|
|
377
|
+
canonicalizes elsewhere, a missing or duplicated canonical, a non-reciprocal
|
|
378
|
+
cluster, and an `<html lang>` contradicting the `hreflang` it's advertised under.
|
|
379
|
+
`verifyHreflangCluster` returns the problems instead of throwing.
|
|
380
|
+
|
|
381
|
+
Run it against a real deployment — it is the only check that sees what a crawler
|
|
382
|
+
sees. A `LocaleRouting` from `@ingram-tech/nk-i18n` is a valid config for it.
|
package/dist/alternates.d.ts
CHANGED
|
@@ -12,19 +12,36 @@ export interface HreflangConfig {
|
|
|
12
12
|
* How locale is encoded in the alternate URLs:
|
|
13
13
|
* - `"query"` (default): `${baseUrl}${path}?${param}=${locale}` for every locale.
|
|
14
14
|
* - `"prefix"`: the default locale stays at the bare path; others get
|
|
15
|
-
* `/${locale}${path}` (matches a localized-rewrite setup).
|
|
15
|
+
* `/${locale}${path}` (matches a localized-rewrite setup). Set
|
|
16
|
+
* {@link HreflangConfig.prefixDefaultLocale} for sites that prefix every
|
|
17
|
+
* locale instead.
|
|
16
18
|
*/
|
|
17
19
|
strategy?: "query" | "prefix";
|
|
18
20
|
/** Query-param name for the `"query"` strategy. Default `"hl"`. */
|
|
19
21
|
param?: string;
|
|
20
|
-
/** Default
|
|
22
|
+
/** Default locale. Required for `"prefix"`; it is what `x-default` resolves to. */
|
|
21
23
|
defaultLocale?: string;
|
|
22
24
|
/**
|
|
23
|
-
*
|
|
24
|
-
*
|
|
25
|
-
*
|
|
26
|
-
*
|
|
27
|
-
*
|
|
25
|
+
* `"prefix"` only: prefix the default locale too (`/en/about`, never
|
|
26
|
+
* `/about`), and point `x-default` at that prefixed URL. For sites whose
|
|
27
|
+
* locale negotiation redirects every bare path — emitting the bare path as
|
|
28
|
+
* an alternate would annotate a URL that 3xx-redirects, which is the bug the
|
|
29
|
+
* mandatory `defaultLocale` exists to prevent.
|
|
30
|
+
*/
|
|
31
|
+
prefixDefaultLocale?: boolean;
|
|
32
|
+
/**
|
|
33
|
+
* The locale **the URL names**, which is not always the locale that rendered.
|
|
34
|
+
* It determines the self-referencing canonical, and a canonical is a claim
|
|
35
|
+
* about an address: under `"query"`, `/pricing` canonicalizes to `/pricing`
|
|
36
|
+
* even while content negotiation renders it in French for a French visitor,
|
|
37
|
+
* because `/pricing` is the negotiating entry point and belongs to no locale.
|
|
38
|
+
* Passing the negotiated locale here instead makes the bare path claim to be
|
|
39
|
+
* the French URL, and the real French URL then looks like a duplicate.
|
|
40
|
+
*
|
|
41
|
+
* Leave it unset on a negotiating bare path. Under `"prefix"` it is detected
|
|
42
|
+
* from the pathname; under `"query"` the server cannot see the query string,
|
|
43
|
+
* so pass it — `hreflangConfigFor(routing)` from
|
|
44
|
+
* "@ingram-tech/nk-i18n/next" fills it in correctly.
|
|
28
45
|
*/
|
|
29
46
|
currentLocale?: string;
|
|
30
47
|
/** Optional locale → hreflang tag map, e.g. `{ en: "en-BE", fr: "fr-BE" }`. */
|
|
@@ -38,8 +55,14 @@ export interface HreflangLink {
|
|
|
38
55
|
export interface HreflangAlternates {
|
|
39
56
|
/** Self-referencing canonical URL of `pathname`. */
|
|
40
57
|
canonical: string;
|
|
41
|
-
/** One link per locale, plus the trailing `x-default
|
|
58
|
+
/** One link per locale, plus the trailing `x-default`. */
|
|
42
59
|
links: HreflangLink[];
|
|
60
|
+
/**
|
|
61
|
+
* The same links keyed by `hrefLang` (`x-default` included) — the shape Next
|
|
62
|
+
* wants for `Metadata.alternates.languages` / `createMetadata`'s
|
|
63
|
+
* `alternates`.
|
|
64
|
+
*/
|
|
65
|
+
languages: Record<string, string>;
|
|
43
66
|
}
|
|
44
67
|
/**
|
|
45
68
|
* Computes the self-referencing canonical plus per-locale hreflang alternate
|
package/dist/alternates.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"alternates.d.ts","sourceRoot":"","sources":["../src/alternates.ts"],"names":[],"mappings":"AAEA;;;;GAIG;AACH,MAAM,WAAW,cAAc;IAC9B,yDAAyD;IACzD,OAAO,EAAE,MAAM,CAAC;IAChB,+DAA+D;IAC/D,OAAO,EAAE,SAAS,MAAM,EAAE,CAAC;IAC3B
|
|
1
|
+
{"version":3,"file":"alternates.d.ts","sourceRoot":"","sources":["../src/alternates.ts"],"names":[],"mappings":"AAEA;;;;GAIG;AACH,MAAM,WAAW,cAAc;IAC9B,yDAAyD;IACzD,OAAO,EAAE,MAAM,CAAC;IAChB,+DAA+D;IAC/D,OAAO,EAAE,SAAS,MAAM,EAAE,CAAC;IAC3B;;;;;;;OAOG;IACH,QAAQ,CAAC,EAAE,OAAO,GAAG,QAAQ,CAAC;IAC9B,mEAAmE;IACnE,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,mFAAmF;IACnF,aAAa,CAAC,EAAE,MAAM,CAAC;IACvB;;;;;;OAMG;IACH,mBAAmB,CAAC,EAAE,OAAO,CAAC;IAC9B;;;;;;;;;;;;;OAaG;IACH,aAAa,CAAC,EAAE,MAAM,CAAC;IACvB,+EAA+E;IAC/E,YAAY,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC;CACtC;AAED,MAAM,WAAW,YAAY;IAC5B,gEAAgE;IAChE,QAAQ,EAAE,MAAM,CAAC;IACjB,IAAI,EAAE,MAAM,CAAC;CACb;AAED,MAAM,WAAW,kBAAkB;IAClC,oDAAoD;IACpD,SAAS,EAAE,MAAM,CAAC;IAClB,0DAA0D;IAC1D,KAAK,EAAE,YAAY,EAAE,CAAC;IACtB;;;;OAIG;IACH,SAAS,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC;CAClC;AAED;;;GAGG;AACH,wBAAgB,kBAAkB,CACjC,MAAM,EAAE,cAAc,EACtB,QAAQ,EAAE,MAAM,GACd,kBAAkB,CAuFpB"}
|
package/dist/alternates.js
CHANGED
|
@@ -4,10 +4,12 @@ import { absoluteUrl } from "./url.js";
|
|
|
4
4
|
* URLs (and an `x-default`) for `pathname`.
|
|
5
5
|
*/
|
|
6
6
|
export function hreflangAlternates(config, pathname) {
|
|
7
|
-
const { strategy = "query", param = "hl", defaultLocale, hrefLangTags } = config;
|
|
7
|
+
const { strategy = "query", param = "hl", defaultLocale, prefixDefaultLocale, hrefLangTags, } = config;
|
|
8
8
|
if (strategy === "prefix" && !defaultLocale) {
|
|
9
|
-
// Without it
|
|
10
|
-
//
|
|
9
|
+
// Without it canonical/x-default point at a bare path that is no
|
|
10
|
+
// locale's URL — a silent SEO bug. (With `prefixDefaultLocale` no bare
|
|
11
|
+
// path is emitted at all, but x-default still has to resolve to *some*
|
|
12
|
+
// locale, so the requirement stands.)
|
|
11
13
|
throw new Error("hreflangAlternates: `defaultLocale` is required for the prefix strategy.");
|
|
12
14
|
}
|
|
13
15
|
// Prefix strategy: accept both the bare and the locale-prefixed form of the
|
|
@@ -26,34 +28,49 @@ export function hreflangAlternates(config, pathname) {
|
|
|
26
28
|
}
|
|
27
29
|
currentLocale ??= defaultLocale;
|
|
28
30
|
}
|
|
29
|
-
/** The
|
|
30
|
-
const
|
|
31
|
+
/** The bare (locale-free) URL of the path. */
|
|
32
|
+
const bareUrl = absoluteUrl(basePath, config.baseUrl);
|
|
33
|
+
const prefixedUrl = (locale) => absoluteUrl(`/${locale}${basePath === "/" ? "" : basePath}`, config.baseUrl);
|
|
31
34
|
const hrefFor = (locale) => {
|
|
32
35
|
if (strategy === "prefix") {
|
|
33
|
-
if (locale === defaultLocale)
|
|
34
|
-
return
|
|
35
|
-
|
|
36
|
-
return absoluteUrl(`/${locale}${clean}`, config.baseUrl);
|
|
36
|
+
if (locale === defaultLocale && !prefixDefaultLocale)
|
|
37
|
+
return bareUrl;
|
|
38
|
+
return prefixedUrl(locale);
|
|
37
39
|
}
|
|
38
|
-
return `${
|
|
40
|
+
return `${bareUrl}${bareUrl.includes("?") ? "&" : "?"}${param}=${locale}`;
|
|
39
41
|
};
|
|
42
|
+
/** x-default: the default locale's own URL, prefixed or not. */
|
|
43
|
+
const defaultUrl = strategy === "prefix" && prefixDefaultLocale && defaultLocale
|
|
44
|
+
? prefixedUrl(defaultLocale)
|
|
45
|
+
: bareUrl;
|
|
40
46
|
// Self-referencing canonical: the current variant's own URL. Canonicalizing
|
|
41
47
|
// a localized variant to the bare path makes Google treat the variants as
|
|
42
48
|
// duplicates and ignore the hreflang annotations entirely.
|
|
49
|
+
//
|
|
50
|
+
// Whether the default locale has a URL of its own is strategy-dependent, and
|
|
51
|
+
// conflating the two is a silent way to delete the default locale from the
|
|
52
|
+
// cluster. Under `"prefix"` it shares the bare path (unless every locale is
|
|
53
|
+
// prefixed), so its canonical IS the bare path. Under `"query"` every locale
|
|
54
|
+
// gets its own `?param=` address and the bare path belongs to none of them:
|
|
55
|
+
// it is the negotiating entry point that `x-default` names. So `?hl=en` must
|
|
56
|
+
// canonicalize to `?hl=en`, never to the bare path.
|
|
57
|
+
const defaultLocaleOwnsBarePath = strategy === "prefix" && !prefixDefaultLocale;
|
|
43
58
|
const canonical = currentLocale &&
|
|
44
|
-
currentLocale
|
|
45
|
-
|
|
59
|
+
config.locales.includes(currentLocale) &&
|
|
60
|
+
!(defaultLocaleOwnsBarePath && currentLocale === defaultLocale)
|
|
46
61
|
? hrefFor(currentLocale)
|
|
47
62
|
: defaultUrl;
|
|
63
|
+
const links = [
|
|
64
|
+
...config.locales.map((locale) => ({
|
|
65
|
+
hrefLang: hrefLangTags?.[locale] ?? locale,
|
|
66
|
+
href: hrefFor(locale),
|
|
67
|
+
})),
|
|
68
|
+
{ hrefLang: "x-default", href: defaultUrl },
|
|
69
|
+
];
|
|
48
70
|
return {
|
|
49
71
|
canonical,
|
|
50
|
-
links
|
|
51
|
-
|
|
52
|
-
hrefLang: hrefLangTags?.[locale] ?? locale,
|
|
53
|
-
href: hrefFor(locale),
|
|
54
|
-
})),
|
|
55
|
-
{ hrefLang: "x-default", href: defaultUrl },
|
|
56
|
-
],
|
|
72
|
+
links,
|
|
73
|
+
languages: Object.fromEntries(links.map((link) => [link.hrefLang, link.href])),
|
|
57
74
|
};
|
|
58
75
|
}
|
|
59
76
|
//# sourceMappingURL=alternates.js.map
|
package/dist/alternates.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"alternates.js","sourceRoot":"","sources":["../src/alternates.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,WAAW,EAAE,MAAM,UAAU,CAAC;
|
|
1
|
+
{"version":3,"file":"alternates.js","sourceRoot":"","sources":["../src/alternates.ts"],"names":[],"mappings":"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"}
|
package/dist/index.d.ts
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
|
-
export { type AggregateOfferNode, type AggregateRatingInput, type AggregateRatingNode, article, type ArticleAuthor, type ArticleAuthorNode, type ArticleInput, type ArticleNode, breadcrumbList, type BreadcrumbItem, type BreadcrumbListNode, type BreadcrumbPath, createSeo, event, type EventInput, type EventLocationInput, type EventNode, faqPage, type FaqItem, type FaqPageNode, type GeoCoordinatesNode, type GeoInput, type ImageObjectNode, type JsonLdNode, type ListItemNode, localBusiness, type LocalBusinessInput, type LocalBusinessNode, type OfferInput, type OfferNode, organization, type OrganizationInput, type OrganizationNode, person, type PersonInput, type PersonNode, type PlaceNode, type PostalAddressInput, type PostalAddressNode, type QuestionNode, type Seo, type SeoConfig, softwareApplication, type SoftwareApplicationInput, type SoftwareApplicationNode, type VirtualLocationNode, website, type WebsiteInput, type WebsiteNode, type WithContext, } from "./schema.js";
|
|
2
|
-
export { createMetadata, type MetadataSiteConfig, type PageMetadata, type PageMetadataInput, type RootMetadataInput, } from "./metadata.js";
|
|
1
|
+
export { type AggregateOfferNode, type AggregateRatingInput, type AggregateRatingNode, article, type ArticleAuthor, type ArticleAuthorNode, type ArticleInput, type ArticleNode, type ArticleType, breadcrumbList, type BreadcrumbItem, type BreadcrumbListNode, type BreadcrumbPath, createSeo, type DataDownloadInput, type DataDownloadNode, dataset, type DatasetInput, type DatasetNode, definedTerm, type DefinedTermInput, type DefinedTermNode, definedTermSet, type DefinedTermSetInput, type DefinedTermSetNode, event, type EventInput, type EventLocationInput, type EventNode, faqPage, type FaqItem, type FaqPageNode, type GeoCoordinatesNode, type GeoInput, type ImageObjectNode, type JsonLdNode, type ListItemNode, localBusiness, type LocalBusinessInput, type LocalBusinessNode, type OfferInput, type OfferNode, organization, type OrganizationInput, type OrganizationNode, person, type PersonInput, type PersonNode, type PlaceNode, type PostalAddressInput, type PostalAddressNode, type QuestionNode, type Seo, type SeoConfig, softwareApplication, type SoftwareApplicationInput, type SoftwareApplicationNode, type VirtualLocationNode, website, type WebsiteInput, type WebsiteNode, type WithContext, type WithExtra, } from "./schema.js";
|
|
2
|
+
export { createMetadata, type MetadataSiteConfig, ogImageMetadata, type OgImageMetadataInput, type PageMetadata, type PageMetadataInput, type RootMetadataInput, } from "./metadata.js";
|
|
3
3
|
export { createRobots, createSitemap, type RobotsConfig, type SitemapConfig, type SitemapLanguages, type SitemapRoute, } from "./routes.js";
|
|
4
4
|
export { hreflangAlternates, type HreflangAlternates, type HreflangConfig, type HreflangLink, } from "./alternates.js";
|
|
5
5
|
//# sourceMappingURL=index.d.ts.map
|
package/dist/index.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAIA,OAAO,EACN,KAAK,kBAAkB,EACvB,KAAK,oBAAoB,EACzB,KAAK,mBAAmB,EACxB,OAAO,EACP,KAAK,aAAa,EAClB,KAAK,iBAAiB,EACtB,KAAK,YAAY,EACjB,KAAK,WAAW,EAChB,cAAc,EACd,KAAK,cAAc,EACnB,KAAK,kBAAkB,EACvB,KAAK,cAAc,EACnB,SAAS,EACT,KAAK,EACL,KAAK,UAAU,EACf,KAAK,kBAAkB,EACvB,KAAK,SAAS,EACd,OAAO,EACP,KAAK,OAAO,EACZ,KAAK,WAAW,EAChB,KAAK,kBAAkB,EACvB,KAAK,QAAQ,EACb,KAAK,eAAe,EACpB,KAAK,UAAU,EACf,KAAK,YAAY,EACjB,aAAa,EACb,KAAK,kBAAkB,EACvB,KAAK,iBAAiB,EACtB,KAAK,UAAU,EACf,KAAK,SAAS,EACd,YAAY,EACZ,KAAK,iBAAiB,EACtB,KAAK,gBAAgB,EACrB,MAAM,EACN,KAAK,WAAW,EAChB,KAAK,UAAU,EACf,KAAK,SAAS,EACd,KAAK,kBAAkB,EACvB,KAAK,iBAAiB,EACtB,KAAK,YAAY,EACjB,KAAK,GAAG,EACR,KAAK,SAAS,EACd,mBAAmB,EACnB,KAAK,wBAAwB,EAC7B,KAAK,uBAAuB,EAC5B,KAAK,mBAAmB,EACxB,OAAO,EACP,KAAK,YAAY,EACjB,KAAK,WAAW,EAChB,KAAK,WAAW,
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAIA,OAAO,EACN,KAAK,kBAAkB,EACvB,KAAK,oBAAoB,EACzB,KAAK,mBAAmB,EACxB,OAAO,EACP,KAAK,aAAa,EAClB,KAAK,iBAAiB,EACtB,KAAK,YAAY,EACjB,KAAK,WAAW,EAChB,KAAK,WAAW,EAChB,cAAc,EACd,KAAK,cAAc,EACnB,KAAK,kBAAkB,EACvB,KAAK,cAAc,EACnB,SAAS,EACT,KAAK,iBAAiB,EACtB,KAAK,gBAAgB,EACrB,OAAO,EACP,KAAK,YAAY,EACjB,KAAK,WAAW,EAChB,WAAW,EACX,KAAK,gBAAgB,EACrB,KAAK,eAAe,EACpB,cAAc,EACd,KAAK,mBAAmB,EACxB,KAAK,kBAAkB,EACvB,KAAK,EACL,KAAK,UAAU,EACf,KAAK,kBAAkB,EACvB,KAAK,SAAS,EACd,OAAO,EACP,KAAK,OAAO,EACZ,KAAK,WAAW,EAChB,KAAK,kBAAkB,EACvB,KAAK,QAAQ,EACb,KAAK,eAAe,EACpB,KAAK,UAAU,EACf,KAAK,YAAY,EACjB,aAAa,EACb,KAAK,kBAAkB,EACvB,KAAK,iBAAiB,EACtB,KAAK,UAAU,EACf,KAAK,SAAS,EACd,YAAY,EACZ,KAAK,iBAAiB,EACtB,KAAK,gBAAgB,EACrB,MAAM,EACN,KAAK,WAAW,EAChB,KAAK,UAAU,EACf,KAAK,SAAS,EACd,KAAK,kBAAkB,EACvB,KAAK,iBAAiB,EACtB,KAAK,YAAY,EACjB,KAAK,GAAG,EACR,KAAK,SAAS,EACd,mBAAmB,EACnB,KAAK,wBAAwB,EAC7B,KAAK,uBAAuB,EAC5B,KAAK,mBAAmB,EACxB,OAAO,EACP,KAAK,YAAY,EACjB,KAAK,WAAW,EAChB,KAAK,WAAW,EAChB,KAAK,SAAS,GACd,MAAM,aAAa,CAAC;AACrB,OAAO,EACN,cAAc,EACd,KAAK,kBAAkB,EACvB,eAAe,EACf,KAAK,oBAAoB,EACzB,KAAK,YAAY,EACjB,KAAK,iBAAiB,EACtB,KAAK,iBAAiB,GACtB,MAAM,eAAe,CAAC;AACvB,OAAO,EACN,YAAY,EACZ,aAAa,EACb,KAAK,YAAY,EACjB,KAAK,aAAa,EAClB,KAAK,gBAAgB,EACrB,KAAK,YAAY,GACjB,MAAM,aAAa,CAAC;AACrB,OAAO,EACN,kBAAkB,EAClB,KAAK,kBAAkB,EACvB,KAAK,cAAc,EACnB,KAAK,YAAY,GACjB,MAAM,iBAAiB,CAAC"}
|
package/dist/index.js
CHANGED
|
@@ -2,8 +2,8 @@
|
|
|
2
2
|
// route handlers / sitemap.ts can import these without pulling in the renderer.
|
|
3
3
|
// The <JsonLd> and <HreflangLinks> components live at
|
|
4
4
|
// "@ingram-tech/nk-seo/components".
|
|
5
|
-
export { article, breadcrumbList, createSeo, event, faqPage, localBusiness, organization, person, softwareApplication, website, } from "./schema.js";
|
|
6
|
-
export { createMetadata, } from "./metadata.js";
|
|
5
|
+
export { article, breadcrumbList, createSeo, dataset, definedTerm, definedTermSet, event, faqPage, localBusiness, organization, person, softwareApplication, website, } from "./schema.js";
|
|
6
|
+
export { createMetadata, ogImageMetadata, } from "./metadata.js";
|
|
7
7
|
export { createRobots, createSitemap, } from "./routes.js";
|
|
8
8
|
export { hreflangAlternates, } from "./alternates.js";
|
|
9
9
|
//# sourceMappingURL=index.js.map
|
package/dist/index.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,8EAA8E;AAC9E,gFAAgF;AAChF,sDAAsD;AACtD,oCAAoC;AACpC,OAAO,EAIN,OAAO,
|
|
1
|
+
{"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,8EAA8E;AAC9E,gFAAgF;AAChF,sDAAsD;AACtD,oCAAoC;AACpC,OAAO,EAIN,OAAO,EAMP,cAAc,EAId,SAAS,EAGT,OAAO,EAGP,WAAW,EAGX,cAAc,EAGd,KAAK,EAIL,OAAO,EAQP,aAAa,EAKb,YAAY,EAGZ,MAAM,EASN,mBAAmB,EAInB,OAAO,GAKP,MAAM,aAAa,CAAC;AACrB,OAAO,EACN,cAAc,EAEd,eAAe,GAKf,MAAM,eAAe,CAAC;AACvB,OAAO,EACN,YAAY,EACZ,aAAa,GAKb,MAAM,aAAa,CAAC;AACrB,OAAO,EACN,kBAAkB,GAIlB,MAAM,iBAAiB,CAAC"}
|
package/dist/metadata.d.ts
CHANGED
|
@@ -16,7 +16,10 @@ export interface MetadataSiteConfig {
|
|
|
16
16
|
defaultImage?: string;
|
|
17
17
|
defaultImageWidth?: number;
|
|
18
18
|
defaultImageHeight?: number;
|
|
19
|
-
/**
|
|
19
|
+
/**
|
|
20
|
+
* Default OpenGraph locale, e.g. "en_US". On a multilingual site set the
|
|
21
|
+
* per-page `locale` instead (or as well, as the fallback).
|
|
22
|
+
*/
|
|
20
23
|
locale?: string;
|
|
21
24
|
/** Twitter `@handle` for `site`. */
|
|
22
25
|
twitterSite?: string;
|
|
@@ -34,6 +37,18 @@ export interface PageMetadataInput {
|
|
|
34
37
|
type?: "website" | "article";
|
|
35
38
|
keywords?: Metadata["keywords"];
|
|
36
39
|
noIndex?: boolean;
|
|
40
|
+
/**
|
|
41
|
+
* OpenGraph locale for this page, e.g. "fr_BE". Overrides `site.locale` —
|
|
42
|
+
* on a multilingual site `og:locale` has to vary per page.
|
|
43
|
+
*/
|
|
44
|
+
locale?: string;
|
|
45
|
+
/**
|
|
46
|
+
* Merged into the generated `alternates` (wins on conflict). The reason it
|
|
47
|
+
* exists: `alternates.languages` is per-page on a localized site, and
|
|
48
|
+
* spreading the result to bolt it on drops the rest of the object.
|
|
49
|
+
* `hreflangAlternates()` returns a ready-made `languages` map.
|
|
50
|
+
*/
|
|
51
|
+
alternates?: Partial<NonNullable<Metadata["alternates"]>>;
|
|
37
52
|
/** Merged into the generated `openGraph` (wins on conflict). */
|
|
38
53
|
openGraph?: Partial<NonNullable<Metadata["openGraph"]>>;
|
|
39
54
|
/** Merged into the generated `twitter` (wins on conflict). */
|
|
@@ -74,4 +89,43 @@ export declare function createMetadata(site: MetadataSiteConfig): {
|
|
|
74
89
|
root: (input?: RootMetadataInput) => Metadata;
|
|
75
90
|
};
|
|
76
91
|
export type PageMetadata = ReturnType<typeof createMetadata>;
|
|
92
|
+
/** Inputs for {@link ogImageMetadata}. */
|
|
93
|
+
export interface OgImageMetadataInput {
|
|
94
|
+
/** Absolute site origin, e.g. "https://acme.example". */
|
|
95
|
+
baseUrl: string;
|
|
96
|
+
/**
|
|
97
|
+
* Path of the image route — the route the `opengraph-image.tsx` file
|
|
98
|
+
* convention serves, e.g. "/opengraph-image" or "/en/opengraph-image" when
|
|
99
|
+
* the file sits under `app/[locale]/`.
|
|
100
|
+
*/
|
|
101
|
+
path: string;
|
|
102
|
+
alt: string;
|
|
103
|
+
/** Defaults to the 1200×630 the `/og` template renders. */
|
|
104
|
+
width?: number;
|
|
105
|
+
height?: number;
|
|
106
|
+
}
|
|
107
|
+
/**
|
|
108
|
+
* Builds the `openGraph.images` entry for a card served by Next's
|
|
109
|
+
* `opengraph-image` file convention.
|
|
110
|
+
*
|
|
111
|
+
* Next attaches a file-convention image only to the segment that declares it,
|
|
112
|
+
* and a page's own `openGraph` object *replaces* the inherited one rather than
|
|
113
|
+
* merging into it — so any page returning `openGraph` from `generateMetadata`
|
|
114
|
+
* silently loses the card. Build the list once and spread it into every page.
|
|
115
|
+
*
|
|
116
|
+
* `createMetadata`'s `defaultImage` / per-page `image` already do this; reach
|
|
117
|
+
* for the standalone helper when you assemble `Metadata` by hand.
|
|
118
|
+
*
|
|
119
|
+
* @example
|
|
120
|
+
* const images = ogImageMetadata({
|
|
121
|
+
* baseUrl, path: `/${locale}/opengraph-image`, alt: "Acme",
|
|
122
|
+
* });
|
|
123
|
+
* return { title, openGraph: { title, url, images } };
|
|
124
|
+
*/
|
|
125
|
+
export declare function ogImageMetadata(input: OgImageMetadataInput): {
|
|
126
|
+
url: string;
|
|
127
|
+
width: number;
|
|
128
|
+
height: number;
|
|
129
|
+
alt: string;
|
|
130
|
+
}[];
|
|
77
131
|
//# sourceMappingURL=metadata.d.ts.map
|
package/dist/metadata.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"metadata.d.ts","sourceRoot":"","sources":["../src/metadata.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,QAAQ,EAAE,MAAM,MAAM,CAAC;AAGrC;;GAEG;AACH,MAAM,WAAW,kBAAkB;IAClC,wDAAwD;IACxD,OAAO,EAAE,MAAM,CAAC;IAChB,4BAA4B;IAC5B,QAAQ,EAAE,MAAM,CAAC;IACjB;;;OAGG;IACH,aAAa,CAAC,EAAE,MAAM,CAAC;IACvB,0DAA0D;IAC1D,YAAY,CAAC,EAAE,MAAM,CAAC;IACtB,iBAAiB,CAAC,EAAE,MAAM,CAAC;IAC3B,kBAAkB,CAAC,EAAE,MAAM,CAAC;IAC5B
|
|
1
|
+
{"version":3,"file":"metadata.d.ts","sourceRoot":"","sources":["../src/metadata.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,QAAQ,EAAE,MAAM,MAAM,CAAC;AAGrC;;GAEG;AACH,MAAM,WAAW,kBAAkB;IAClC,wDAAwD;IACxD,OAAO,EAAE,MAAM,CAAC;IAChB,4BAA4B;IAC5B,QAAQ,EAAE,MAAM,CAAC;IACjB;;;OAGG;IACH,aAAa,CAAC,EAAE,MAAM,CAAC;IACvB,0DAA0D;IAC1D,YAAY,CAAC,EAAE,MAAM,CAAC;IACtB,iBAAiB,CAAC,EAAE,MAAM,CAAC;IAC3B,kBAAkB,CAAC,EAAE,MAAM,CAAC;IAC5B;;;OAGG;IACH,MAAM,CAAC,EAAE,MAAM,CAAC;IAChB,oCAAoC;IACpC,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB,uCAAuC;IACvC,cAAc,CAAC,EAAE,MAAM,CAAC;CACxB;AAED,6DAA6D;AAC7D,MAAM,WAAW,iBAAiB;IACjC,KAAK,EAAE,MAAM,CAAC;IACd,WAAW,EAAE,MAAM,CAAC;IACpB,sFAAsF;IACtF,IAAI,EAAE,MAAM,CAAC;IACb,oEAAoE;IACpE,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,IAAI,CAAC,EAAE,SAAS,GAAG,SAAS,CAAC;IAC7B,QAAQ,CAAC,EAAE,QAAQ,CAAC,UAAU,CAAC,CAAC;IAChC,OAAO,CAAC,EAAE,OAAO,CAAC;IAClB;;;OAGG;IACH,MAAM,CAAC,EAAE,MAAM,CAAC;IAChB;;;;;OAKG;IACH,UAAU,CAAC,EAAE,OAAO,CAAC,WAAW,CAAC,QAAQ,CAAC,YAAY,CAAC,CAAC,CAAC,CAAC;IAC1D,gEAAgE;IAChE,SAAS,CAAC,EAAE,OAAO,CAAC,WAAW,CAAC,QAAQ,CAAC,WAAW,CAAC,CAAC,CAAC,CAAC;IACxD,8DAA8D;IAC9D,OAAO,CAAC,EAAE,OAAO,CAAC,WAAW,CAAC,QAAQ,CAAC,SAAS,CAAC,CAAC,CAAC,CAAC;CACpD;AAED,mEAAmE;AACnE,MAAM,WAAW,iBAAiB;IACjC,2EAA2E;IAC3E,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB,4EAA4E;IAC5E,SAAS,CAAC,EAAE,QAAQ,CAAC;CACrB;AAED;;;;;;;;;;;;;;;;;;;;;GAqBG;AACH,wBAAgB,cAAc,CAAC,IAAI,EAAE,kBAAkB;YAGzB,iBAAiB,GAAG,QAAQ;mBAgDR,iBAAiB,KAAQ,QAAQ;EAalF;AAED,MAAM,MAAM,YAAY,GAAG,UAAU,CAAC,OAAO,cAAc,CAAC,CAAC;AAE7D,0CAA0C;AAC1C,MAAM,WAAW,oBAAoB;IACpC,yDAAyD;IACzD,OAAO,EAAE,MAAM,CAAC;IAChB;;;;OAIG;IACH,IAAI,EAAE,MAAM,CAAC;IACb,GAAG,EAAE,MAAM,CAAC;IACZ,2DAA2D;IAC3D,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,MAAM,CAAC,EAAE,MAAM,CAAC;CAChB;AAED;;;;;;;;;;;;;;;;;GAiBG;AACH,wBAAgB,eAAe,CAAC,KAAK,EAAE,oBAAoB;;;;;IAS1D"}
|
package/dist/metadata.js
CHANGED
|
@@ -27,12 +27,13 @@ export function createMetadata(site) {
|
|
|
27
27
|
const imagePath = input.image ?? site.defaultImage;
|
|
28
28
|
const imageUrl = imagePath ? absolute(imagePath) : undefined;
|
|
29
29
|
const url = absolute(input.path);
|
|
30
|
+
const locale = input.locale ?? site.locale;
|
|
30
31
|
return {
|
|
31
32
|
metadataBase: new URL(site.baseUrl),
|
|
32
33
|
title: input.title,
|
|
33
34
|
description: input.description,
|
|
34
35
|
...(input.keywords ? { keywords: input.keywords } : {}),
|
|
35
|
-
alternates: { canonical: url },
|
|
36
|
+
alternates: { canonical: url, ...input.alternates },
|
|
36
37
|
// noindex but follow: keep link equity flowing through the page. Use a
|
|
37
38
|
// full robots override in the page's own metadata for nofollow too.
|
|
38
39
|
...(input.noIndex ? { robots: { index: false, follow: true } } : {}),
|
|
@@ -42,7 +43,7 @@ export function createMetadata(site) {
|
|
|
42
43
|
type: input.type ?? "website",
|
|
43
44
|
url,
|
|
44
45
|
siteName: site.siteName,
|
|
45
|
-
...(
|
|
46
|
+
...(locale ? { locale } : {}),
|
|
46
47
|
...(imageUrl
|
|
47
48
|
? {
|
|
48
49
|
images: [
|
|
@@ -81,4 +82,32 @@ export function createMetadata(site) {
|
|
|
81
82
|
};
|
|
82
83
|
return pageMetadata;
|
|
83
84
|
}
|
|
85
|
+
/**
|
|
86
|
+
* Builds the `openGraph.images` entry for a card served by Next's
|
|
87
|
+
* `opengraph-image` file convention.
|
|
88
|
+
*
|
|
89
|
+
* Next attaches a file-convention image only to the segment that declares it,
|
|
90
|
+
* and a page's own `openGraph` object *replaces* the inherited one rather than
|
|
91
|
+
* merging into it — so any page returning `openGraph` from `generateMetadata`
|
|
92
|
+
* silently loses the card. Build the list once and spread it into every page.
|
|
93
|
+
*
|
|
94
|
+
* `createMetadata`'s `defaultImage` / per-page `image` already do this; reach
|
|
95
|
+
* for the standalone helper when you assemble `Metadata` by hand.
|
|
96
|
+
*
|
|
97
|
+
* @example
|
|
98
|
+
* const images = ogImageMetadata({
|
|
99
|
+
* baseUrl, path: `/${locale}/opengraph-image`, alt: "Acme",
|
|
100
|
+
* });
|
|
101
|
+
* return { title, openGraph: { title, url, images } };
|
|
102
|
+
*/
|
|
103
|
+
export function ogImageMetadata(input) {
|
|
104
|
+
return [
|
|
105
|
+
{
|
|
106
|
+
url: absoluteUrl(input.path, input.baseUrl),
|
|
107
|
+
width: input.width ?? 1200,
|
|
108
|
+
height: input.height ?? 630,
|
|
109
|
+
alt: input.alt,
|
|
110
|
+
},
|
|
111
|
+
];
|
|
112
|
+
}
|
|
84
113
|
//# sourceMappingURL=metadata.js.map
|
package/dist/metadata.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"metadata.js","sourceRoot":"","sources":["../src/metadata.ts"],"names":[],"mappings":"AACA,OAAO,EAAE,WAAW,EAAE,MAAM,UAAU,CAAC;
|
|
1
|
+
{"version":3,"file":"metadata.js","sourceRoot":"","sources":["../src/metadata.ts"],"names":[],"mappings":"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"}
|