@jdevalk/astro-seo-graph 0.1.0 → 0.2.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 CHANGED
@@ -11,14 +11,15 @@ content-collection aggregator, and Zod helpers for content schemas.
11
11
 
12
12
  ## What's in v0.1
13
13
 
14
- | API | Purpose |
15
- | ------------------------------ | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
16
- | **`<Seo>`** (`./Seo.astro`) | Single head component covering `<title>`, meta description, canonical, Open Graph, Twitter card, and optional JSON-LD `@graph`. Wraps [`astro-seo`](https://github.com/jonasmerlin/astro-seo) for the meta tags. |
17
- | **`createSchemaEndpoint`** | Factory returning an Astro `APIRoute` handler that serves a corpus-wide JSON-LD `@graph` for a content collection. |
18
- | **`createSchemaMap`** | Factory returning an `APIRoute` handler that emits a sitemap-style XML listing of your site's schema endpoints — the discovery point for agent crawlers. |
19
- | **`aggregate`** | Shared engine behind the endpoint factories. Walks a list of entries, runs a caller-supplied mapper, deduplicates by `@id`. |
20
- | **`seoSchema`, `imageSchema`** | Zod schemas for the `seo` and `image` fields on content collections. Import them into `src/content.config.ts`. |
21
- | **`buildAstroSeoProps`** | Pure-TS logic that powers `<Seo>` — exported for users who want to feed a different head component. |
14
+ | API | Purpose |
15
+ | ------------------------------ | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
16
+ | **`<Seo>`** (`./Seo.astro`) | Single head component covering `<title>`, meta description, canonical, Open Graph, Twitter card, hreflang alternates, and optional JSON-LD `@graph`. Wraps [`astro-seo`](https://github.com/jonasmerlin/astro-seo) for the meta tags. |
17
+ | **`createSchemaEndpoint`** | Factory returning an Astro `APIRoute` handler that serves a corpus-wide JSON-LD `@graph` for a content collection. |
18
+ | **`createSchemaMap`** | Factory returning an `APIRoute` handler that emits a sitemap-style XML listing of your site's schema endpoints — the discovery point for agent crawlers. |
19
+ | **`aggregate`** | Shared engine behind the endpoint factories. Walks a list of entries, runs a caller-supplied mapper, deduplicates by `@id`. |
20
+ | **`seoSchema`, `imageSchema`** | Zod schemas for the `seo` and `image` fields on content collections. Import them into `src/content.config.ts`. |
21
+ | **`buildAstroSeoProps`** | Pure-TS logic that powers `<Seo>` — exported for users who want to feed a different head component. |
22
+ | **`buildAlternateLinks`** | Pure helper that turns a `{ hreflang, href }` entry list into normalized `<link rel="alternate">` tags plus an `x-default`. Used internally by `<Seo>`'s `alternates` prop, and exported for non-Astro callers (e.g. CMS plugins feeding their own metadata pipelines). |
22
23
 
23
24
  ## Not in `0.1.x` (coming in `0.2.x`)
24
25
 
@@ -77,6 +78,73 @@ const graph = buildSchemaGraph({
77
78
  </html>
78
79
  ```
79
80
 
81
+ ## hreflang alternates
82
+
83
+ For multilingual sites, pass an `alternates` prop with one entry per locale.
84
+ `<Seo>` emits a `<link rel="alternate">` for every entry plus an
85
+ `x-default`, normalizes BCP 47 tags on the way out, and drops entries with
86
+ relative or non-http(s) URLs.
87
+
88
+ ```astro
89
+ ---
90
+ import Seo from '@jdevalk/astro-seo-graph/Seo.astro';
91
+ ---
92
+
93
+ <Seo
94
+ title="Hello"
95
+ alternates={{
96
+ defaultLocale: 'en',
97
+ entries: [
98
+ { hreflang: 'en', href: 'https://example.com/hello/' },
99
+ { hreflang: 'fr-CA', href: 'https://example.com/fr-ca/bonjour/' },
100
+ { hreflang: 'nl', href: 'https://example.com/nl/hallo/' },
101
+ ],
102
+ }}
103
+ />
104
+ ```
105
+
106
+ Renders roughly:
107
+
108
+ ```html
109
+ <link rel="alternate" hreflang="en" href="https://example.com/hello/" />
110
+ <link rel="alternate" hreflang="fr-CA" href="https://example.com/fr-ca/bonjour/" />
111
+ <link rel="alternate" hreflang="nl" href="https://example.com/nl/hallo/" />
112
+ <link rel="alternate" hreflang="x-default" href="https://example.com/hello/" />
113
+ ```
114
+
115
+ ### Rules
116
+
117
+ - **Absolute URLs only.** Relative (`/hello/`), protocol-relative (`//…`), and non-http schemes (`mailto:`) are dropped silently.
118
+ - **Include the current page.** Google treats self-referential hreflang as required, not optional.
119
+ - **BCP 47 normalization.** `fr-ca` becomes `fr-CA`, `zh-hant-hk` becomes `zh-Hant-HK`. Language subtag lowercase, script subtag title-case, region subtag uppercase.
120
+ - **First entry wins.** Duplicate normalized tags are collapsed to the first one.
121
+ - **Automatic `x-default`.** Points at `defaultLocale` if it matches an entry; otherwise falls back to the first entry.
122
+ - **< 2 entries → nothing emitted.** A single-locale page has no meaningful alternates.
123
+ - **`"x-default"` is reserved.** Passing it as an input `hreflang` gets dropped; it's only ever added automatically.
124
+
125
+ ### Feeding `buildAlternateLinks` from other renderers
126
+
127
+ If you're not using `<Seo>` directly (e.g. you're writing a CMS plugin that
128
+ contributes to its own metadata pipeline), import `buildAlternateLinks` from
129
+ the main package entry:
130
+
131
+ ```ts
132
+ import { buildAlternateLinks } from '@jdevalk/astro-seo-graph';
133
+
134
+ const links = buildAlternateLinks({
135
+ defaultLocale: 'en',
136
+ entries: [
137
+ { hreflang: 'en', href: siteEn },
138
+ { hreflang: 'fr', href: siteFr },
139
+ ],
140
+ });
141
+ // → [{ rel: 'alternate', hreflang: 'en', href: ... }, ..., { hreflang: 'x-default', ... }]
142
+ ```
143
+
144
+ The main package entry is pure TypeScript — importing `buildAlternateLinks`
145
+ does not pull in any Astro runtime, so it's safe to use from non-Astro
146
+ contexts.
147
+
80
148
  ## Schema endpoints
81
149
 
82
150
  ```ts
@@ -0,0 +1,63 @@
1
+ /**
2
+ * hreflang alternate-language link builder.
3
+ *
4
+ * Pure function: no DOM, no fetch, no Astro runtime. Safe to import from
5
+ * non-Astro TypeScript (in particular, from `@jdevalk/emdash-plugin-seo`'s
6
+ * EmDash `page:metadata` hook).
7
+ *
8
+ * Callers provide a set of `{ hreflang, href }` entries; this module
9
+ * normalizes BCP 47 tags, validates URLs, dedupes, and appends an
10
+ * `x-default` entry. It does NOT source translation data, construct URLs
11
+ * from slugs, or know about any particular CMS.
12
+ */
13
+ /** Output shape: a single rendered `<link rel="alternate" hreflang="…" href="…">` tag. */
14
+ export interface AlternateLink {
15
+ /** Always `"alternate"`. */
16
+ rel: 'alternate';
17
+ /** Normalized BCP 47 tag (e.g. `"fr-CA"`) or the literal `"x-default"`. */
18
+ hreflang: string;
19
+ /** Absolute `http(s)://` URL string. */
20
+ href: string;
21
+ }
22
+ /** Input shape accepted by `buildAlternateLinks`. */
23
+ export interface BuildAlternateLinksInput {
24
+ /**
25
+ * BCP 47 locale tag of the default sibling, used for the `x-default`
26
+ * entry. Matched case-insensitively against the normalized
27
+ * `entries[].hreflang` values. If omitted (or no match), the first
28
+ * surviving entry is used as the default.
29
+ */
30
+ defaultLocale?: string;
31
+ /**
32
+ * Per-locale URL entries. Order is preserved in the output. The
33
+ * caller MUST include the current page itself — Google treats
34
+ * self-referential hreflang as required, not optional.
35
+ */
36
+ entries: ReadonlyArray<{
37
+ /** BCP 47 tag, e.g. `"en"`, `"fr"`, `"fr-CA"`. */
38
+ hreflang: string;
39
+ /** Absolute URL. Relative or non-http(s) values are dropped. */
40
+ href: string | URL;
41
+ }>;
42
+ }
43
+ /**
44
+ * Build a deduplicated, normalized set of hreflang alternate links,
45
+ * including an `x-default` entry.
46
+ *
47
+ * Behaviour:
48
+ * - Returns `[]` when fewer than 2 entries survive validation (a
49
+ * single-locale page has no meaningful alternates).
50
+ * - Drops entries with malformed / non-http(s) URLs.
51
+ * - Drops entries with empty hreflang.
52
+ * - Drops entries whose input hreflang is the literal `"x-default"` —
53
+ * that's reserved and added automatically.
54
+ * - On duplicate normalized hreflang, the first entry wins.
55
+ * - Preserves input order for per-locale entries.
56
+ * - Appends `x-default` as the final entry, pointing at the
57
+ * `defaultLocale` match (or the first entry as fallback).
58
+ *
59
+ * @param input Entries plus optional default locale hint.
60
+ * @returns Deduped, normalized link tags. Never throws.
61
+ */
62
+ export declare function buildAlternateLinks(input: BuildAlternateLinksInput): AlternateLink[];
63
+ //# sourceMappingURL=alternates.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"alternates.d.ts","sourceRoot":"","sources":["../src/alternates.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;GAWG;AAEH,0FAA0F;AAC1F,MAAM,WAAW,aAAa;IAC1B,4BAA4B;IAC5B,GAAG,EAAE,WAAW,CAAC;IACjB,2EAA2E;IAC3E,QAAQ,EAAE,MAAM,CAAC;IACjB,wCAAwC;IACxC,IAAI,EAAE,MAAM,CAAC;CAChB;AAED,qDAAqD;AACrD,MAAM,WAAW,wBAAwB;IACrC;;;;;OAKG;IACH,aAAa,CAAC,EAAE,MAAM,CAAC;IAEvB;;;;OAIG;IACH,OAAO,EAAE,aAAa,CAAC;QACnB,kDAAkD;QAClD,QAAQ,EAAE,MAAM,CAAC;QACjB,gEAAgE;QAChE,IAAI,EAAE,MAAM,GAAG,GAAG,CAAC;KACtB,CAAC,CAAC;CACN;AAkDD;;;;;;;;;;;;;;;;;;GAkBG;AACH,wBAAgB,mBAAmB,CAAC,KAAK,EAAE,wBAAwB,GAAG,aAAa,EAAE,CAkCpF"}
@@ -0,0 +1,113 @@
1
+ /**
2
+ * hreflang alternate-language link builder.
3
+ *
4
+ * Pure function: no DOM, no fetch, no Astro runtime. Safe to import from
5
+ * non-Astro TypeScript (in particular, from `@jdevalk/emdash-plugin-seo`'s
6
+ * EmDash `page:metadata` hook).
7
+ *
8
+ * Callers provide a set of `{ hreflang, href }` entries; this module
9
+ * normalizes BCP 47 tags, validates URLs, dedupes, and appends an
10
+ * `x-default` entry. It does NOT source translation data, construct URLs
11
+ * from slugs, or know about any particular CMS.
12
+ */
13
+ /** BCP 47 subtag structure we handle: language-[script]-[region]. */
14
+ const BCP47_RE = /^([a-z]{2,3})(?:-([a-z]{4}))?(?:-([a-z]{2,3}))?$/i;
15
+ /**
16
+ * Normalize a BCP 47 locale tag to conventional casing:
17
+ * language subtag lowercase, script subtag title-case, region subtag
18
+ * uppercase. Returns `null` for empty / whitespace-only input.
19
+ *
20
+ * For tags that don't match the language-script-region pattern
21
+ * (variants, extended tags, private-use, grandfathered), this falls
22
+ * back to lowercasing the entire tag. hreflang rarely uses those.
23
+ */
24
+ function normalizeLocaleTag(raw) {
25
+ const trimmed = raw.trim();
26
+ if (trimmed.length === 0)
27
+ return null;
28
+ const match = BCP47_RE.exec(trimmed);
29
+ if (match === null)
30
+ return trimmed.toLowerCase();
31
+ // eslint-disable-next-line @typescript-eslint/no-unused-vars
32
+ const [, lang, script, region] = match;
33
+ let out = lang.toLowerCase();
34
+ if (script !== undefined) {
35
+ out += '-' + script.charAt(0).toUpperCase() + script.slice(1).toLowerCase();
36
+ }
37
+ if (region !== undefined) {
38
+ out += '-' + region.toUpperCase();
39
+ }
40
+ return out;
41
+ }
42
+ /**
43
+ * Validate that a URL-ish value is absolute http(s). Returns the
44
+ * stringified URL on success, `null` on failure.
45
+ */
46
+ function validateAbsoluteUrl(href) {
47
+ try {
48
+ const str = typeof href === 'string' ? href : href.toString();
49
+ const parsed = new URL(str);
50
+ if (parsed.protocol !== 'http:' && parsed.protocol !== 'https:') {
51
+ return null;
52
+ }
53
+ return parsed.toString();
54
+ }
55
+ catch {
56
+ return null;
57
+ }
58
+ }
59
+ /**
60
+ * Build a deduplicated, normalized set of hreflang alternate links,
61
+ * including an `x-default` entry.
62
+ *
63
+ * Behaviour:
64
+ * - Returns `[]` when fewer than 2 entries survive validation (a
65
+ * single-locale page has no meaningful alternates).
66
+ * - Drops entries with malformed / non-http(s) URLs.
67
+ * - Drops entries with empty hreflang.
68
+ * - Drops entries whose input hreflang is the literal `"x-default"` —
69
+ * that's reserved and added automatically.
70
+ * - On duplicate normalized hreflang, the first entry wins.
71
+ * - Preserves input order for per-locale entries.
72
+ * - Appends `x-default` as the final entry, pointing at the
73
+ * `defaultLocale` match (or the first entry as fallback).
74
+ *
75
+ * @param input Entries plus optional default locale hint.
76
+ * @returns Deduped, normalized link tags. Never throws.
77
+ */
78
+ export function buildAlternateLinks(input) {
79
+ const seen = new Set();
80
+ const validated = [];
81
+ for (const entry of input.entries) {
82
+ const normalized = normalizeLocaleTag(entry.hreflang);
83
+ if (normalized === null)
84
+ continue;
85
+ if (normalized === 'x-default')
86
+ continue; // reserved
87
+ if (seen.has(normalized))
88
+ continue;
89
+ const href = validateAbsoluteUrl(entry.href);
90
+ if (href === null)
91
+ continue;
92
+ seen.add(normalized);
93
+ validated.push({ rel: 'alternate', hreflang: normalized, href });
94
+ }
95
+ // Short-circuit: one (or zero) surviving entries is not a meaningful
96
+ // hreflang set. Emit nothing rather than a self-referential single
97
+ // tag. Callers who want that behaviour can skip the helper.
98
+ if (validated.length < 2)
99
+ return [];
100
+ // Pick the x-default target. defaultLocale is matched
101
+ // case-insensitively against the normalized tags.
102
+ let defaultEntry = validated[0];
103
+ if (input.defaultLocale !== undefined) {
104
+ const normalizedDefault = normalizeLocaleTag(input.defaultLocale);
105
+ if (normalizedDefault !== null) {
106
+ const match = validated.find((v) => v.hreflang === normalizedDefault);
107
+ if (match !== undefined)
108
+ defaultEntry = match;
109
+ }
110
+ }
111
+ return [...validated, { rel: 'alternate', hreflang: 'x-default', href: defaultEntry.href }];
112
+ }
113
+ //# sourceMappingURL=alternates.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"alternates.js","sourceRoot":"","sources":["../src/alternates.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;GAWG;AAmCH,qEAAqE;AACrE,MAAM,QAAQ,GAAG,mDAAmD,CAAC;AAErE;;;;;;;;GAQG;AACH,SAAS,kBAAkB,CAAC,GAAW;IACnC,MAAM,OAAO,GAAG,GAAG,CAAC,IAAI,EAAE,CAAC;IAC3B,IAAI,OAAO,CAAC,MAAM,KAAK,CAAC;QAAE,OAAO,IAAI,CAAC;IAEtC,MAAM,KAAK,GAAG,QAAQ,CAAC,IAAI,CAAC,OAAO,CAAC,CAAC;IACrC,IAAI,KAAK,KAAK,IAAI;QAAE,OAAO,OAAO,CAAC,WAAW,EAAE,CAAC;IAEjD,6DAA6D;IAC7D,MAAM,CAAC,EAAE,IAAI,EAAE,MAAM,EAAE,MAAM,CAAC,GAAG,KAAK,CAAC;IACvC,IAAI,GAAG,GAAG,IAAK,CAAC,WAAW,EAAE,CAAC;IAC9B,IAAI,MAAM,KAAK,SAAS,EAAE,CAAC;QACvB,GAAG,IAAI,GAAG,GAAG,MAAM,CAAC,MAAM,CAAC,CAAC,CAAC,CAAC,WAAW,EAAE,GAAG,MAAM,CAAC,KAAK,CAAC,CAAC,CAAC,CAAC,WAAW,EAAE,CAAC;IAChF,CAAC;IACD,IAAI,MAAM,KAAK,SAAS,EAAE,CAAC;QACvB,GAAG,IAAI,GAAG,GAAG,MAAM,CAAC,WAAW,EAAE,CAAC;IACtC,CAAC;IACD,OAAO,GAAG,CAAC;AACf,CAAC;AAED;;;GAGG;AACH,SAAS,mBAAmB,CAAC,IAAkB;IAC3C,IAAI,CAAC;QACD,MAAM,GAAG,GAAG,OAAO,IAAI,KAAK,QAAQ,CAAC,CAAC,CAAC,IAAI,CAAC,CAAC,CAAC,IAAI,CAAC,QAAQ,EAAE,CAAC;QAC9D,MAAM,MAAM,GAAG,IAAI,GAAG,CAAC,GAAG,CAAC,CAAC;QAC5B,IAAI,MAAM,CAAC,QAAQ,KAAK,OAAO,IAAI,MAAM,CAAC,QAAQ,KAAK,QAAQ,EAAE,CAAC;YAC9D,OAAO,IAAI,CAAC;QAChB,CAAC;QACD,OAAO,MAAM,CAAC,QAAQ,EAAE,CAAC;IAC7B,CAAC;IAAC,MAAM,CAAC;QACL,OAAO,IAAI,CAAC;IAChB,CAAC;AACL,CAAC;AAED;;;;;;;;;;;;;;;;;;GAkBG;AACH,MAAM,UAAU,mBAAmB,CAAC,KAA+B;IAC/D,MAAM,IAAI,GAAG,IAAI,GAAG,EAAU,CAAC;IAC/B,MAAM,SAAS,GAAoB,EAAE,CAAC;IAEtC,KAAK,MAAM,KAAK,IAAI,KAAK,CAAC,OAAO,EAAE,CAAC;QAChC,MAAM,UAAU,GAAG,kBAAkB,CAAC,KAAK,CAAC,QAAQ,CAAC,CAAC;QACtD,IAAI,UAAU,KAAK,IAAI;YAAE,SAAS;QAClC,IAAI,UAAU,KAAK,WAAW;YAAE,SAAS,CAAC,WAAW;QACrD,IAAI,IAAI,CAAC,GAAG,CAAC,UAAU,CAAC;YAAE,SAAS;QAEnC,MAAM,IAAI,GAAG,mBAAmB,CAAC,KAAK,CAAC,IAAI,CAAC,CAAC;QAC7C,IAAI,IAAI,KAAK,IAAI;YAAE,SAAS;QAE5B,IAAI,CAAC,GAAG,CAAC,UAAU,CAAC,CAAC;QACrB,SAAS,CAAC,IAAI,CAAC,EAAE,GAAG,EAAE,WAAW,EAAE,QAAQ,EAAE,UAAU,EAAE,IAAI,EAAE,CAAC,CAAC;IACrE,CAAC;IAED,qEAAqE;IACrE,mEAAmE;IACnE,4DAA4D;IAC5D,IAAI,SAAS,CAAC,MAAM,GAAG,CAAC;QAAE,OAAO,EAAE,CAAC;IAEpC,sDAAsD;IACtD,kDAAkD;IAClD,IAAI,YAAY,GAAG,SAAS,CAAC,CAAC,CAAE,CAAC;IACjC,IAAI,KAAK,CAAC,aAAa,KAAK,SAAS,EAAE,CAAC;QACpC,MAAM,iBAAiB,GAAG,kBAAkB,CAAC,KAAK,CAAC,aAAa,CAAC,CAAC;QAClE,IAAI,iBAAiB,KAAK,IAAI,EAAE,CAAC;YAC7B,MAAM,KAAK,GAAG,SAAS,CAAC,IAAI,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,QAAQ,KAAK,iBAAiB,CAAC,CAAC;YACtE,IAAI,KAAK,KAAK,SAAS;gBAAE,YAAY,GAAG,KAAK,CAAC;QAClD,CAAC;IACL,CAAC;IAED,OAAO,CAAC,GAAG,SAAS,EAAE,EAAE,GAAG,EAAE,WAAW,EAAE,QAAQ,EAAE,WAAW,EAAE,IAAI,EAAE,YAAY,CAAC,IAAI,EAAE,CAAC,CAAC;AAChG,CAAC"}
@@ -5,6 +5,7 @@
5
5
  * here makes it unit-testable with vitest — Astro's renderer isn't
6
6
  * easily accessible from vitest.
7
7
  */
8
+ import { type BuildAlternateLinksInput } from '../alternates.js';
8
9
  /** Public props surface of the <Seo> component. */
9
10
  export interface SeoProps {
10
11
  /** Page title. Required. */
@@ -68,6 +69,28 @@ export interface SeoProps {
68
69
  extraLinks?: ReadonlyArray<Record<string, string>>;
69
70
  /** Extra `<meta>` tags (author, custom fields). */
70
71
  extraMeta?: ReadonlyArray<Record<string, string>>;
72
+ /**
73
+ * hreflang alternate-language annotations.
74
+ *
75
+ * Emits one `<link rel="alternate" hreflang="…" href="…">` per
76
+ * entry, plus an `x-default` entry pointing at the default-locale
77
+ * sibling (or the first entry, if no default match is found).
78
+ *
79
+ * All `href` values MUST be absolute `http(s)://` URLs — relative
80
+ * or other-scheme values are dropped silently. BCP 47 tags are
81
+ * normalized on output (`fr-ca` → `fr-CA`, `zh-hant-hk` →
82
+ * `zh-Hant-HK`). On duplicate normalized tags, the first entry
83
+ * wins.
84
+ *
85
+ * **The caller must include the current page itself** in the
86
+ * entries list — Google treats self-referential hreflang as
87
+ * required, not optional.
88
+ *
89
+ * When fewer than 2 entries survive validation, nothing is emitted
90
+ * (a single-locale page has no meaningful alternates). If you
91
+ * prefer strict input checking, validate before passing in.
92
+ */
93
+ alternates?: BuildAlternateLinksInput;
71
94
  }
72
95
  /**
73
96
  * Shape consumed by `astro-seo`'s `<SEO>` component. We expose it as a
@@ -1 +1 @@
1
- {"version":3,"file":"seo-props.d.ts","sourceRoot":"","sources":["../../src/components/seo-props.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"seo-props.d.ts","sourceRoot":"","sources":["../../src/components/seo-props.ts"],"names":[],"mappings":"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"}
@@ -5,6 +5,7 @@
5
5
  * here makes it unit-testable with vitest — Astro's renderer isn't
6
6
  * easily accessible from vitest.
7
7
  */
8
+ import { buildAlternateLinks } from '../alternates.js';
8
9
  function toIsoString(value) {
9
10
  if (value === undefined)
10
11
  return undefined;
@@ -99,13 +100,31 @@ export function buildAstroSeoProps(props, pageUrl) {
99
100
  twitter.imageAlt = props.ogImageAlt;
100
101
  astroSeo.twitter = twitter;
101
102
  }
102
- if ((props.extraLinks !== undefined && props.extraLinks.length > 0) ||
103
- (props.extraMeta !== undefined && props.extraMeta.length > 0)) {
103
+ // Resolve hreflang alternates (if any). Returns [] when fewer than
104
+ // 2 entries survive validation, so this is cheap to call.
105
+ const alternateLinks = props.alternates !== undefined ? buildAlternateLinks(props.alternates) : [];
106
+ const hasExtraLinks = props.extraLinks !== undefined && props.extraLinks.length > 0;
107
+ const hasExtraMeta = props.extraMeta !== undefined && props.extraMeta.length > 0;
108
+ const hasAlternates = alternateLinks.length > 0;
109
+ if (hasExtraLinks || hasExtraMeta || hasAlternates) {
104
110
  const extend = {};
105
- if (props.extraLinks !== undefined && props.extraLinks.length > 0) {
106
- extend.link = props.extraLinks.map((link) => ({ ...link }));
111
+ const link = [];
112
+ if (hasExtraLinks) {
113
+ for (const entry of props.extraLinks)
114
+ link.push({ ...entry });
107
115
  }
108
- if (props.extraMeta !== undefined && props.extraMeta.length > 0) {
116
+ if (hasAlternates) {
117
+ for (const entry of alternateLinks) {
118
+ link.push({
119
+ rel: entry.rel,
120
+ href: entry.href,
121
+ hreflang: entry.hreflang,
122
+ });
123
+ }
124
+ }
125
+ if (link.length > 0)
126
+ extend.link = link;
127
+ if (hasExtraMeta) {
109
128
  extend.meta = props.extraMeta.map((meta) => ({ ...meta }));
110
129
  }
111
130
  astroSeo.extend = extend;
@@ -1 +1 @@
1
- {"version":3,"file":"seo-props.js","sourceRoot":"","sources":["../../src/components/seo-props.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"seo-props.js","sourceRoot":"","sources":["../../src/components/seo-props.ts"],"names":[],"mappings":"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"}
package/dist/index.d.ts CHANGED
@@ -5,4 +5,6 @@ export type { SchemaEndpointOptions, SchemaMapEntry, SchemaMapOptions } from './
5
5
  export { seoSchema, imageSchema } from './content-helpers.js';
6
6
  export { buildAstroSeoProps } from './components/seo-props.js';
7
7
  export type { SeoProps, AstroSeoProps } from './components/seo-props.js';
8
+ export { buildAlternateLinks } from './alternates.js';
9
+ export type { AlternateLink, BuildAlternateLinksInput } from './alternates.js';
8
10
  //# sourceMappingURL=index.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAMA,OAAO,EAAE,SAAS,EAAE,MAAM,iBAAiB,CAAC;AAC5C,YAAY,EAAE,iBAAiB,EAAE,eAAe,EAAE,MAAM,iBAAiB,CAAC;AAE1E,OAAO,EAAE,oBAAoB,EAAE,eAAe,EAAE,MAAM,aAAa,CAAC;AACpE,YAAY,EAAE,qBAAqB,EAAE,cAAc,EAAE,gBAAgB,EAAE,MAAM,aAAa,CAAC;AAE3F,OAAO,EAAE,SAAS,EAAE,WAAW,EAAE,MAAM,sBAAsB,CAAC;AAE9D,OAAO,EAAE,kBAAkB,EAAE,MAAM,2BAA2B,CAAC;AAC/D,YAAY,EAAE,QAAQ,EAAE,aAAa,EAAE,MAAM,2BAA2B,CAAC"}
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAMA,OAAO,EAAE,SAAS,EAAE,MAAM,iBAAiB,CAAC;AAC5C,YAAY,EAAE,iBAAiB,EAAE,eAAe,EAAE,MAAM,iBAAiB,CAAC;AAE1E,OAAO,EAAE,oBAAoB,EAAE,eAAe,EAAE,MAAM,aAAa,CAAC;AACpE,YAAY,EAAE,qBAAqB,EAAE,cAAc,EAAE,gBAAgB,EAAE,MAAM,aAAa,CAAC;AAE3F,OAAO,EAAE,SAAS,EAAE,WAAW,EAAE,MAAM,sBAAsB,CAAC;AAE9D,OAAO,EAAE,kBAAkB,EAAE,MAAM,2BAA2B,CAAC;AAC/D,YAAY,EAAE,QAAQ,EAAE,aAAa,EAAE,MAAM,2BAA2B,CAAC;AAEzE,OAAO,EAAE,mBAAmB,EAAE,MAAM,iBAAiB,CAAC;AACtD,YAAY,EAAE,aAAa,EAAE,wBAAwB,EAAE,MAAM,iBAAiB,CAAC"}
package/dist/index.js CHANGED
@@ -7,4 +7,5 @@ export { aggregate } from './aggregator.js';
7
7
  export { createSchemaEndpoint, createSchemaMap } from './routes.js';
8
8
  export { seoSchema, imageSchema } from './content-helpers.js';
9
9
  export { buildAstroSeoProps } from './components/seo-props.js';
10
+ export { buildAlternateLinks } from './alternates.js';
10
11
  //# 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,+EAA+E;AAC/E,EAAE;AACF,wEAAwE;AACxE,8EAA8E;AAC9E,sDAAsD;AAEtD,OAAO,EAAE,SAAS,EAAE,MAAM,iBAAiB,CAAC;AAG5C,OAAO,EAAE,oBAAoB,EAAE,eAAe,EAAE,MAAM,aAAa,CAAC;AAGpE,OAAO,EAAE,SAAS,EAAE,WAAW,EAAE,MAAM,sBAAsB,CAAC;AAE9D,OAAO,EAAE,kBAAkB,EAAE,MAAM,2BAA2B,CAAC"}
1
+ {"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,+EAA+E;AAC/E,EAAE;AACF,wEAAwE;AACxE,8EAA8E;AAC9E,sDAAsD;AAEtD,OAAO,EAAE,SAAS,EAAE,MAAM,iBAAiB,CAAC;AAG5C,OAAO,EAAE,oBAAoB,EAAE,eAAe,EAAE,MAAM,aAAa,CAAC;AAGpE,OAAO,EAAE,SAAS,EAAE,WAAW,EAAE,MAAM,sBAAsB,CAAC;AAE9D,OAAO,EAAE,kBAAkB,EAAE,MAAM,2BAA2B,CAAC;AAG/D,OAAO,EAAE,mBAAmB,EAAE,MAAM,iBAAiB,CAAC"}
package/package.json CHANGED
@@ -1,62 +1,62 @@
1
1
  {
2
- "name": "@jdevalk/astro-seo-graph",
3
- "version": "0.1.0",
4
- "description": "Astro integration for @jdevalk/seo-graph-core. Seo component, route factories, content-collection aggregator, Zod content helpers.",
5
- "keywords": [
6
- "astro",
7
- "astro-integration",
8
- "astro-component",
9
- "schema.org",
10
- "json-ld",
11
- "seo",
12
- "agent-ready",
13
- "structured-data"
14
- ],
15
- "author": "Joost de Valk <joost@joost.blog>",
16
- "license": "MIT",
17
- "type": "module",
18
- "main": "./dist/index.js",
19
- "types": "./dist/index.d.ts",
20
- "exports": {
21
- ".": {
22
- "types": "./dist/index.d.ts",
23
- "import": "./dist/index.js"
2
+ "name": "@jdevalk/astro-seo-graph",
3
+ "version": "0.2.0",
4
+ "description": "Astro integration for @jdevalk/seo-graph-core. Seo component, route factories, content-collection aggregator, Zod content helpers.",
5
+ "keywords": [
6
+ "astro",
7
+ "astro-integration",
8
+ "astro-component",
9
+ "schema.org",
10
+ "json-ld",
11
+ "seo",
12
+ "agent-ready",
13
+ "structured-data"
14
+ ],
15
+ "author": "Joost de Valk <joost@joost.blog>",
16
+ "license": "MIT",
17
+ "type": "module",
18
+ "main": "./dist/index.js",
19
+ "types": "./dist/index.d.ts",
20
+ "exports": {
21
+ ".": {
22
+ "types": "./dist/index.d.ts",
23
+ "import": "./dist/index.js"
24
+ },
25
+ "./Seo.astro": "./dist/components/Seo.astro"
24
26
  },
25
- "./Seo.astro": "./dist/components/Seo.astro"
26
- },
27
- "files": [
28
- "dist",
29
- "README.md",
30
- "LICENSE"
31
- ],
32
- "repository": {
33
- "type": "git",
34
- "url": "git+https://github.com/jdevalk/seo-graph.git",
35
- "directory": "packages/astro-seo-graph"
36
- },
37
- "bugs": "https://github.com/jdevalk/seo-graph/issues",
38
- "homepage": "https://github.com/jdevalk/seo-graph/tree/main/packages/astro-seo-graph#readme",
39
- "publishConfig": {
40
- "access": "public"
41
- },
42
- "peerDependencies": {
43
- "astro": "^5.0.0 || ^6.0.0"
44
- },
45
- "dependencies": {
46
- "astro-seo": "^1.1.0",
47
- "schema-dts": "^2.0.0",
48
- "zod": "^3.24.0",
49
- "@jdevalk/seo-graph-core": "0.1.0"
50
- },
51
- "devDependencies": {
52
- "@types/node": "^22.0.0",
53
- "astro": "^6.0.5",
54
- "typescript": "^5.6.0",
55
- "vitest": "^2.0.0"
56
- },
57
- "scripts": {
58
- "build": "tsc -p tsconfig.build.json && mkdir -p dist/components && cp src/components/*.astro dist/components/",
59
- "typecheck": "tsc -p tsconfig.json",
60
- "test": "vitest run"
61
- }
62
- }
27
+ "files": [
28
+ "dist",
29
+ "README.md",
30
+ "LICENSE"
31
+ ],
32
+ "repository": {
33
+ "type": "git",
34
+ "url": "git+https://github.com/jdevalk/seo-graph.git",
35
+ "directory": "packages/astro-seo-graph"
36
+ },
37
+ "bugs": "https://github.com/jdevalk/seo-graph/issues",
38
+ "homepage": "https://github.com/jdevalk/seo-graph/tree/main/packages/astro-seo-graph#readme",
39
+ "publishConfig": {
40
+ "access": "public"
41
+ },
42
+ "scripts": {
43
+ "build": "tsc -p tsconfig.build.json && mkdir -p dist/components && cp src/components/*.astro dist/components/",
44
+ "typecheck": "tsc -p tsconfig.json",
45
+ "test": "vitest run"
46
+ },
47
+ "peerDependencies": {
48
+ "astro": "^5.0.0 || ^6.0.0"
49
+ },
50
+ "dependencies": {
51
+ "@jdevalk/seo-graph-core": "workspace:*",
52
+ "astro-seo": "^1.1.0",
53
+ "schema-dts": "^2.0.0",
54
+ "zod": "^3.24.0"
55
+ },
56
+ "devDependencies": {
57
+ "@types/node": "^22.0.0",
58
+ "astro": "^6.0.5",
59
+ "typescript": "^5.6.0",
60
+ "vitest": "^2.0.0"
61
+ }
62
+ }
package/LICENSE DELETED
@@ -1,21 +0,0 @@
1
- MIT License
2
-
3
- Copyright (c) 2026 Joost de Valk
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.