@jdevalk/astro-seo-graph 0.5.2 → 0.6.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
@@ -292,18 +292,49 @@ export const GET = createSchemaMap({
292
292
  ```ts
293
293
  // src/content.config.ts
294
294
  import { defineCollection, z } from 'astro:content';
295
- import { seoSchema } from '@jdevalk/astro-seo-graph';
295
+ import { seoSchema, imageSchema } from '@jdevalk/astro-seo-graph';
296
296
 
297
297
  const blog = defineCollection({
298
298
  schema: ({ image }) =>
299
299
  z.object({
300
300
  title: z.string(),
301
301
  publishDate: z.coerce.date(),
302
+ featureImage: imageSchema(image).optional(),
302
303
  seo: seoSchema(image).optional(),
303
304
  }),
304
305
  });
305
306
  ```
306
307
 
308
+ `imageSchema` **requires** `alt` — missing alt text is an accessibility
309
+ failure and an SEO failure. Decorative images should use `alt: ''`
310
+ explicitly. If you want the whole image to be optional, wrap the schema:
311
+ `imageSchema(image).optional()`.
312
+
313
+ ## Astro integration
314
+
315
+ An Astro integration that runs build-time SEO checks. Currently:
316
+
317
+ - Warns about built pages with zero or more than one `<h1>` element (a
318
+ common SEO and accessibility issue).
319
+
320
+ ```js
321
+ // astro.config.mjs
322
+ import { defineConfig } from 'astro/config';
323
+ import seoGraph from '@jdevalk/astro-seo-graph/integration';
324
+
325
+ export default defineConfig({
326
+ integrations: [seoGraph()],
327
+ });
328
+ ```
329
+
330
+ Options:
331
+
332
+ | Prop | Default | Description |
333
+ | ------------ | ------- | ------------------------------------------- |
334
+ | `validateH1` | `true` | Warn about pages without exactly one `<h1>` |
335
+
336
+ Only static pages are checked (SSR pages aren't on disk at build time).
337
+
307
338
  ## License
308
339
 
309
340
  MIT © Joost de Valk
@@ -19,8 +19,20 @@ export interface SeoProps {
19
19
  titleTemplate?: string;
20
20
  /** Meta description. */
21
21
  description?: string;
22
- /** Canonical URL. Defaults to the current page URL (`Astro.url.href`). */
22
+ /**
23
+ * Canonical URL. Defaults to the current page URL with the query
24
+ * string stripped. Set `preserveQueryParams` to keep the query
25
+ * string, or pass an explicit `canonical` to override entirely.
26
+ *
27
+ * Omitted from the output when `noindex` is true.
28
+ */
23
29
  canonical?: string | URL;
30
+ /**
31
+ * Keep the query string in the default canonical URL. Defaults to
32
+ * `false` (query params are stripped — the SEO-correct behavior for
33
+ * most cases). Has no effect when `canonical` is explicitly set.
34
+ */
35
+ preserveQueryParams?: boolean;
24
36
  /** Open Graph type. Defaults to `'website'`. */
25
37
  ogType?: 'website' | 'article' | 'profile' | 'book';
26
38
  /** Absolute URL of the share image. */
@@ -35,7 +47,14 @@ export interface SeoProps {
35
47
  siteName?: string;
36
48
  /** OG locale. Defaults to `'en_US'`. */
37
49
  locale?: string;
38
- /** Twitter card metadata. */
50
+ /**
51
+ * Twitter card metadata. Only Twitter-specific tags (card, site,
52
+ * creator) are emitted by default — twitter:title, :description,
53
+ * :image, and :image:alt fall back to their `og:` counterparts
54
+ * automatically. Set `title`, `description`, `image`, or `imageAlt`
55
+ * here only when you want Twitter-specific content that differs
56
+ * from the OG values.
57
+ */
39
58
  twitter?: {
40
59
  /** Twitter handle of the site owner, e.g. `'@jdevalk'`. */
41
60
  site?: string;
@@ -43,6 +62,14 @@ export interface SeoProps {
43
62
  creator?: string;
44
63
  /** Card type. Defaults to `'summary_large_image'`. */
45
64
  card?: 'summary' | 'summary_large_image' | 'app' | 'player';
65
+ /** Override twitter:title. Omit to fall back to og:title. */
66
+ title?: string;
67
+ /** Override twitter:description. Omit to fall back to og:description. */
68
+ description?: string;
69
+ /** Override twitter:image. Omit to fall back to og:image. */
70
+ image?: string;
71
+ /** Override twitter:image:alt. Omit to fall back to og:image:alt. */
72
+ imageAlt?: string;
46
73
  };
47
74
  /**
48
75
  * Article-specific OG metadata. Only emitted when `ogType` is
@@ -56,8 +83,20 @@ export interface SeoProps {
56
83
  tags?: readonly string[];
57
84
  section?: string;
58
85
  };
59
- /** Emit `<meta name="robots" content="noindex, follow">` when `true`. */
86
+ /** Emit `<meta name="robots" content="noindex, follow, max-*">`. */
60
87
  noindex?: boolean;
88
+ /** Emit `nofollow` in the robots meta. */
89
+ nofollow?: boolean;
90
+ /**
91
+ * Facebook page URL of the publisher. Emitted as `article:publisher`
92
+ * when `ogType` is `'article'`.
93
+ */
94
+ articlePublisher?: string;
95
+ /**
96
+ * Author name for the `<meta name="author">` tag. If omitted but
97
+ * `article.authors` is set, the first author is used.
98
+ */
99
+ author?: string;
61
100
  /**
62
101
  * JSON-LD `@graph` envelope to inject as
63
102
  * `<script type="application/ld+json">`. Typically the output of
@@ -74,21 +113,8 @@ export interface SeoProps {
74
113
  *
75
114
  * Emits one `<link rel="alternate" hreflang="…" href="…">` per
76
115
  * 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.
116
+ * sibling (or the first entry, if no default match is found). Also
117
+ * emits matching `og:locale:alternate` tags.
92
118
  */
93
119
  alternates?: BuildAlternateLinksInput;
94
120
  }
@@ -100,8 +126,7 @@ export interface SeoProps {
100
126
  export interface AstroSeoProps {
101
127
  title: string;
102
128
  description?: string;
103
- canonical: string;
104
- noindex?: boolean;
129
+ canonical?: string;
105
130
  openGraph: {
106
131
  basic: {
107
132
  title: string;
@@ -113,6 +138,7 @@ export interface AstroSeoProps {
113
138
  description?: string;
114
139
  siteName?: string;
115
140
  locale?: string;
141
+ localeAlternate?: string[];
116
142
  };
117
143
  image?: {
118
144
  alt?: string;
@@ -126,13 +152,14 @@ export interface AstroSeoProps {
126
152
  authors?: string[];
127
153
  tags?: string[];
128
154
  section?: string;
155
+ publisher?: string;
129
156
  };
130
157
  };
131
158
  twitter?: {
132
159
  card: 'summary' | 'summary_large_image' | 'app' | 'player';
133
160
  site?: string;
134
161
  creator?: string;
135
- title: string;
162
+ title?: string;
136
163
  description?: string;
137
164
  image?: string;
138
165
  imageAlt?: string;
@@ -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":"AAAA;;;;;;GAMG;AAEH,OAAO,EAAuB,KAAK,wBAAwB,EAAE,MAAM,kBAAkB,CAAC;AAEtF,mDAAmD;AACnD,MAAM,WAAW,QAAQ;IACrB,4BAA4B;IAC5B,KAAK,EAAE,MAAM,CAAC;IACd;;;;;OAKG;IACH,aAAa,CAAC,EAAE,MAAM,CAAC;IACvB,wBAAwB;IACxB,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB;;;;;;OAMG;IACH,SAAS,CAAC,EAAE,MAAM,GAAG,GAAG,CAAC;IACzB;;;;OAIG;IACH,mBAAmB,CAAC,EAAE,OAAO,CAAC;IAC9B,gDAAgD;IAChD,MAAM,CAAC,EAAE,SAAS,GAAG,SAAS,GAAG,SAAS,GAAG,MAAM,CAAC;IACpD,uCAAuC;IACvC,OAAO,CAAC,EAAE,MAAM,CAAC;IACjB,oCAAoC;IACpC,UAAU,CAAC,EAAE,MAAM,CAAC;IACpB,0CAA0C;IAC1C,YAAY,CAAC,EAAE,MAAM,CAAC;IACtB,2CAA2C;IAC3C,aAAa,CAAC,EAAE,MAAM,CAAC;IACvB,uDAAuD;IACvD,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB,wCAAwC;IACxC,MAAM,CAAC,EAAE,MAAM,CAAC;IAChB;;;;;;;OAOG;IACH,OAAO,CAAC,EAAE;QACN,2DAA2D;QAC3D,IAAI,CAAC,EAAE,MAAM,CAAC;QACd,oCAAoC;QACpC,OAAO,CAAC,EAAE,MAAM,CAAC;QACjB,sDAAsD;QACtD,IAAI,CAAC,EAAE,SAAS,GAAG,qBAAqB,GAAG,KAAK,GAAG,QAAQ,CAAC;QAC5D,6DAA6D;QAC7D,KAAK,CAAC,EAAE,MAAM,CAAC;QACf,yEAAyE;QACzE,WAAW,CAAC,EAAE,MAAM,CAAC;QACrB,6DAA6D;QAC7D,KAAK,CAAC,EAAE,MAAM,CAAC;QACf,qEAAqE;QACrE,QAAQ,CAAC,EAAE,MAAM,CAAC;KACrB,CAAC;IACF;;;OAGG;IACH,OAAO,CAAC,EAAE;QACN,aAAa,CAAC,EAAE,IAAI,GAAG,MAAM,CAAC;QAC9B,YAAY,CAAC,EAAE,IAAI,GAAG,MAAM,CAAC;QAC7B,cAAc,CAAC,EAAE,IAAI,GAAG,MAAM,CAAC;QAC/B,OAAO,CAAC,EAAE,SAAS,MAAM,EAAE,CAAC;QAC5B,IAAI,CAAC,EAAE,SAAS,MAAM,EAAE,CAAC;QACzB,OAAO,CAAC,EAAE,MAAM,CAAC;KACpB,CAAC;IACF,oEAAoE;IACpE,OAAO,CAAC,EAAE,OAAO,CAAC;IAClB,0CAA0C;IAC1C,QAAQ,CAAC,EAAE,OAAO,CAAC;IACnB;;;OAGG;IACH,gBAAgB,CAAC,EAAE,MAAM,CAAC;IAC1B;;;OAGG;IACH,MAAM,CAAC,EAAE,MAAM,CAAC;IAChB;;;;;OAKG;IACH,KAAK,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,GAAG,IAAI,CAAC;IACvC,iEAAiE;IACjE,UAAU,CAAC,EAAE,aAAa,CAAC,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC,CAAC;IACnD,mDAAmD;IACnD,SAAS,CAAC,EAAE,aAAa,CAAC,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC,CAAC;IAClD;;;;;;;OAOG;IACH,UAAU,CAAC,EAAE,wBAAwB,CAAC;CACzC;AAED;;;;GAIG;AACH,MAAM,WAAW,aAAa;IAC1B,KAAK,EAAE,MAAM,CAAC;IACd,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,SAAS,EAAE;QACP,KAAK,EAAE;YACH,KAAK,EAAE,MAAM,CAAC;YACd,IAAI,EAAE,MAAM,CAAC;YACb,KAAK,EAAE,MAAM,CAAC;YACd,GAAG,EAAE,MAAM,CAAC;SACf,CAAC;QACF,QAAQ,CAAC,EAAE;YACP,WAAW,CAAC,EAAE,MAAM,CAAC;YACrB,QAAQ,CAAC,EAAE,MAAM,CAAC;YAClB,MAAM,CAAC,EAAE,MAAM,CAAC;YAChB,eAAe,CAAC,EAAE,MAAM,EAAE,CAAC;SAC9B,CAAC;QACF,KAAK,CAAC,EAAE;YACJ,GAAG,CAAC,EAAE,MAAM,CAAC;YACb,KAAK,CAAC,EAAE,MAAM,CAAC;YACf,MAAM,CAAC,EAAE,MAAM,CAAC;SACnB,CAAC;QACF,OAAO,CAAC,EAAE;YACN,aAAa,CAAC,EAAE,MAAM,CAAC;YACvB,YAAY,CAAC,EAAE,MAAM,CAAC;YACtB,cAAc,CAAC,EAAE,MAAM,CAAC;YACxB,OAAO,CAAC,EAAE,MAAM,EAAE,CAAC;YACnB,IAAI,CAAC,EAAE,MAAM,EAAE,CAAC;YAChB,OAAO,CAAC,EAAE,MAAM,CAAC;YACjB,SAAS,CAAC,EAAE,MAAM,CAAC;SACtB,CAAC;KACL,CAAC;IACF,OAAO,CAAC,EAAE;QACN,IAAI,EAAE,SAAS,GAAG,qBAAqB,GAAG,KAAK,GAAG,QAAQ,CAAC;QAC3D,IAAI,CAAC,EAAE,MAAM,CAAC;QACd,OAAO,CAAC,EAAE,MAAM,CAAC;QACjB,KAAK,CAAC,EAAE,MAAM,CAAC;QACf,WAAW,CAAC,EAAE,MAAM,CAAC;QACrB,KAAK,CAAC,EAAE,MAAM,CAAC;QACf,QAAQ,CAAC,EAAE,MAAM,CAAC;KACrB,CAAC;IACF,MAAM,CAAC,EAAE;QACL,IAAI,CAAC,EAAE,KAAK,CAAC,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC,CAAC;QACrC,IAAI,CAAC,EAAE,KAAK,CAAC,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC,CAAC;KACxC,CAAC;CACL;AAwCD;;;;;;;;GAQG;AACH,wBAAgB,kBAAkB,CAAC,KAAK,EAAE,QAAQ,EAAE,OAAO,EAAE,MAAM,GAAG,aAAa,CAgLlF"}
@@ -11,6 +11,40 @@ function toIsoString(value) {
11
11
  return undefined;
12
12
  return value instanceof Date ? value.toISOString() : value;
13
13
  }
14
+ /**
15
+ * Build the `content` value of the `<meta name="robots">` tag. Always
16
+ * includes `max-snippet:-1, max-image-preview:large, max-video-preview:-1`
17
+ * (opt into maximum snippet/preview sizes in search results). Prepends
18
+ * `noindex`, `nofollow`, or `noindex, follow` as applicable.
19
+ */
20
+ function buildRobotsContent(noindex, nofollow) {
21
+ const directives = [];
22
+ if (noindex && nofollow) {
23
+ directives.push('noindex', 'nofollow');
24
+ }
25
+ else if (noindex) {
26
+ directives.push('noindex', 'follow');
27
+ }
28
+ else if (nofollow) {
29
+ directives.push('nofollow');
30
+ }
31
+ directives.push('max-snippet:-1', 'max-image-preview:large', 'max-video-preview:-1');
32
+ return directives.join(', ');
33
+ }
34
+ /**
35
+ * Strip the query string from a URL. Fragments are preserved. Falls
36
+ * back to returning the input unchanged if URL parsing fails.
37
+ */
38
+ function stripQueryParams(url) {
39
+ try {
40
+ const parsed = new URL(url);
41
+ parsed.search = '';
42
+ return parsed.toString();
43
+ }
44
+ catch {
45
+ return url;
46
+ }
47
+ }
14
48
  /**
15
49
  * Project the public `SeoProps` onto the shape `astro-seo`'s `<SEO>`
16
50
  * component expects. Pure function; no Astro runtime access.
@@ -24,7 +58,26 @@ export function buildAstroSeoProps(props, pageUrl) {
24
58
  const fullTitle = props.titleTemplate
25
59
  ? props.titleTemplate.replace('%s', props.title)
26
60
  : props.title;
27
- const canonical = props.canonical !== undefined ? props.canonical.toString() : pageUrl;
61
+ // Default canonical strips query params (SEO-correct for most
62
+ // cases). `preserveQueryParams` opts out. Explicit `canonical`
63
+ // overrides entirely.
64
+ let canonical;
65
+ if (props.canonical !== undefined) {
66
+ canonical = props.canonical.toString();
67
+ }
68
+ else if (props.preserveQueryParams) {
69
+ canonical = pageUrl;
70
+ }
71
+ else {
72
+ canonical = stripQueryParams(pageUrl);
73
+ }
74
+ // Omit canonical when the page is noindex (Google recommendation).
75
+ if (props.noindex) {
76
+ canonical = undefined;
77
+ }
78
+ // og:url uses the canonical when available, otherwise the (possibly
79
+ // query-stripped) page URL.
80
+ const ogUrl = canonical ?? stripQueryParams(pageUrl);
28
81
  const ogType = props.ogType ?? 'website';
29
82
  const ogImage = props.ogImage ?? '';
30
83
  const openGraph = {
@@ -32,7 +85,7 @@ export function buildAstroSeoProps(props, pageUrl) {
32
85
  title: fullTitle,
33
86
  type: ogType,
34
87
  image: ogImage,
35
- url: canonical,
88
+ url: ogUrl,
36
89
  },
37
90
  };
38
91
  const optional = {};
@@ -41,6 +94,30 @@ export function buildAstroSeoProps(props, pageUrl) {
41
94
  if (props.siteName !== undefined)
42
95
  optional.siteName = props.siteName;
43
96
  optional.locale = props.locale ?? 'en_US';
97
+ // og:locale:alternate from hreflang entries. Convert hyphenated
98
+ // BCP 47 (fr-CA) to OG-style underscores (fr_CA), and skip any
99
+ // entry that matches the primary og:locale (including language-only
100
+ // matches like hreflang='en' when og:locale='en_US').
101
+ if (props.alternates?.entries && props.alternates.entries.length > 1) {
102
+ const primaryLang = optional.locale?.split('_')[0]?.toLowerCase();
103
+ const localeAlternate = [];
104
+ const seen = new Set();
105
+ for (const entry of props.alternates.entries) {
106
+ const ogLocale = entry.hreflang.replace('-', '_');
107
+ const entryLang = entry.hreflang.split('-')[0]?.toLowerCase();
108
+ if (seen.has(ogLocale))
109
+ continue;
110
+ if (ogLocale === optional.locale)
111
+ continue;
112
+ // Skip language-only hreflang when it matches the primary locale's language.
113
+ if (!entry.hreflang.includes('-') && entryLang === primaryLang)
114
+ continue;
115
+ seen.add(ogLocale);
116
+ localeAlternate.push(ogLocale);
117
+ }
118
+ if (localeAlternate.length > 0)
119
+ optional.localeAlternate = localeAlternate;
120
+ }
44
121
  openGraph.optional = optional;
45
122
  const hasImageMeta = props.ogImageAlt !== undefined ||
46
123
  props.ogImageWidth !== undefined ||
@@ -55,78 +132,103 @@ export function buildAstroSeoProps(props, pageUrl) {
55
132
  image.height = props.ogImageHeight;
56
133
  openGraph.image = image;
57
134
  }
58
- if (ogType === 'article' && props.article) {
135
+ if (ogType === 'article' && (props.article || props.articlePublisher)) {
59
136
  const article = {};
60
- const published = toIsoString(props.article.publishedTime);
61
- if (published !== undefined)
62
- article.publishedTime = published;
63
- const modified = toIsoString(props.article.modifiedTime);
64
- if (modified !== undefined)
65
- article.modifiedTime = modified;
66
- const expiration = toIsoString(props.article.expirationTime);
67
- if (expiration !== undefined)
68
- article.expirationTime = expiration;
69
- if (props.article.authors !== undefined)
70
- article.authors = [...props.article.authors];
71
- if (props.article.tags !== undefined)
72
- article.tags = [...props.article.tags];
73
- if (props.article.section !== undefined)
74
- article.section = props.article.section;
137
+ if (props.article) {
138
+ const published = toIsoString(props.article.publishedTime);
139
+ if (published !== undefined)
140
+ article.publishedTime = published;
141
+ const modified = toIsoString(props.article.modifiedTime);
142
+ if (modified !== undefined)
143
+ article.modifiedTime = modified;
144
+ const expiration = toIsoString(props.article.expirationTime);
145
+ if (expiration !== undefined)
146
+ article.expirationTime = expiration;
147
+ if (props.article.authors !== undefined)
148
+ article.authors = [...props.article.authors];
149
+ if (props.article.tags !== undefined)
150
+ article.tags = [...props.article.tags];
151
+ if (props.article.section !== undefined)
152
+ article.section = props.article.section;
153
+ }
154
+ if (props.articlePublisher !== undefined)
155
+ article.publisher = props.articlePublisher;
75
156
  openGraph.article = article;
76
157
  }
77
158
  const astroSeo = {
78
159
  title: fullTitle,
79
- canonical,
80
160
  openGraph,
81
161
  };
162
+ if (canonical !== undefined)
163
+ astroSeo.canonical = canonical;
82
164
  if (props.description !== undefined)
83
165
  astroSeo.description = props.description;
84
- if (props.noindex)
85
- astroSeo.noindex = true;
86
166
  if (props.twitter !== undefined) {
87
167
  const twitter = {
88
168
  card: props.twitter.card ?? 'summary_large_image',
89
- title: fullTitle,
90
169
  };
91
170
  if (props.twitter.site !== undefined)
92
171
  twitter.site = props.twitter.site;
93
172
  if (props.twitter.creator !== undefined)
94
173
  twitter.creator = props.twitter.creator;
95
- if (props.description !== undefined)
96
- twitter.description = props.description;
97
- if (ogImage)
98
- twitter.image = ogImage;
99
- if (props.ogImageAlt !== undefined)
100
- twitter.imageAlt = props.ogImageAlt;
174
+ // Only emit twitter:title/description/image/imageAlt when the
175
+ // caller explicitly provided Twitter-specific overrides.
176
+ // Otherwise Twitter falls back to the og: counterparts
177
+ // automatically — emitting duplicates is noisy.
178
+ if (props.twitter.title !== undefined && props.twitter.title !== fullTitle) {
179
+ twitter.title = props.twitter.title;
180
+ }
181
+ if (props.twitter.description !== undefined &&
182
+ props.twitter.description !== props.description) {
183
+ twitter.description = props.twitter.description;
184
+ }
185
+ if (props.twitter.image !== undefined && props.twitter.image !== ogImage) {
186
+ twitter.image = props.twitter.image;
187
+ }
188
+ if (props.twitter.imageAlt !== undefined && props.twitter.imageAlt !== props.ogImageAlt) {
189
+ twitter.imageAlt = props.twitter.imageAlt;
190
+ }
101
191
  astroSeo.twitter = twitter;
102
192
  }
103
193
  // Resolve hreflang alternates (if any). Returns [] when fewer than
104
194
  // 2 entries survive validation, so this is cheap to call.
105
195
  const alternateLinks = props.alternates !== undefined ? buildAlternateLinks(props.alternates) : [];
196
+ // Build our own robots meta (astro-seo's doesn't support max-*).
197
+ const robotsContent = buildRobotsContent(props.noindex, props.nofollow);
198
+ // Resolve author name: explicit prop takes precedence, falls back
199
+ // to first entry in article.authors.
200
+ const authorName = props.author ?? props.article?.authors?.[0];
201
+ const meta = [];
202
+ meta.push({ name: 'robots', content: robotsContent });
203
+ if (authorName !== undefined) {
204
+ meta.push({ name: 'author', content: authorName });
205
+ }
206
+ if (props.extraMeta !== undefined) {
207
+ for (const entry of props.extraMeta)
208
+ meta.push({ ...entry });
209
+ }
106
210
  const hasExtraLinks = props.extraLinks !== undefined && props.extraLinks.length > 0;
107
- const hasExtraMeta = props.extraMeta !== undefined && props.extraMeta.length > 0;
108
211
  const hasAlternates = alternateLinks.length > 0;
109
- if (hasExtraLinks || hasExtraMeta || hasAlternates) {
110
- const extend = {};
111
- const link = [];
112
- if (hasExtraLinks) {
113
- for (const entry of props.extraLinks)
114
- link.push({ ...entry });
115
- }
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) {
128
- extend.meta = props.extraMeta.map((meta) => ({ ...meta }));
212
+ const link = [];
213
+ if (hasExtraLinks) {
214
+ for (const entry of props.extraLinks)
215
+ link.push({ ...entry });
216
+ }
217
+ if (hasAlternates) {
218
+ for (const entry of alternateLinks) {
219
+ link.push({
220
+ rel: entry.rel,
221
+ href: entry.href,
222
+ hreflang: entry.hreflang,
223
+ });
129
224
  }
225
+ }
226
+ const extend = {};
227
+ if (link.length > 0)
228
+ extend.link = link;
229
+ if (meta.length > 0)
230
+ extend.meta = meta;
231
+ if (extend.link !== undefined || extend.meta !== undefined) {
130
232
  astroSeo.extend = extend;
131
233
  }
132
234
  return astroSeo;
@@ -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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"}
@@ -11,6 +11,11 @@ type AstroImageFunction = any;
11
11
  * `image()` from the collection schema factory so the image is processed
12
12
  * through Astro's asset pipeline.
13
13
  *
14
+ * `alt` is **required** — missing alt text is an accessibility failure
15
+ * and an SEO failure. Decorative images should use `alt: ''` explicitly.
16
+ * If you want to make the whole image optional, wrap the schema:
17
+ * `imageSchema(image).optional()`.
18
+ *
14
19
  * @example
15
20
  * ```ts
16
21
  * import { defineCollection, z } from 'astro:content';
@@ -27,13 +32,13 @@ type AstroImageFunction = any;
27
32
  */
28
33
  export declare function imageSchema(image: AstroImageFunction): z.ZodObject<{
29
34
  src: any;
30
- alt: z.ZodOptional<z.ZodString>;
35
+ alt: z.ZodString;
31
36
  }, "strip", z.ZodTypeAny, {
37
+ alt: string;
32
38
  src?: any;
33
- alt?: string | undefined;
34
39
  }, {
40
+ alt: string;
35
41
  src?: any;
36
- alt?: string | undefined;
37
42
  }>;
38
43
  /**
39
44
  * Zod schema for a nested `seo` field holding per-entry SEO overrides:
@@ -57,13 +62,13 @@ export declare function seoSchema(image: AstroImageFunction): z.ZodObject<{
57
62
  description: z.ZodOptional<z.ZodString>;
58
63
  image: z.ZodOptional<z.ZodObject<{
59
64
  src: any;
60
- alt: z.ZodOptional<z.ZodString>;
65
+ alt: z.ZodString;
61
66
  }, "strip", z.ZodTypeAny, {
67
+ alt: string;
62
68
  src?: any;
63
- alt?: string | undefined;
64
69
  }, {
70
+ alt: string;
65
71
  src?: any;
66
- alt?: string | undefined;
67
72
  }>>;
68
73
  pageType: z.ZodDefault<z.ZodEnum<["website", "article"]>>;
69
74
  }, "strip", z.ZodTypeAny, {
@@ -71,15 +76,15 @@ export declare function seoSchema(image: AstroImageFunction): z.ZodObject<{
71
76
  title?: string | undefined;
72
77
  description?: string | undefined;
73
78
  image?: {
79
+ alt: string;
74
80
  src?: any;
75
- alt?: string | undefined;
76
81
  } | undefined;
77
82
  }, {
78
83
  title?: string | undefined;
79
84
  description?: string | undefined;
80
85
  image?: {
86
+ alt: string;
81
87
  src?: any;
82
- alt?: string | undefined;
83
88
  } | undefined;
84
89
  pageType?: "website" | "article" | undefined;
85
90
  }>;
@@ -1 +1 @@
1
- {"version":3,"file":"content-helpers.d.ts","sourceRoot":"","sources":["../src/content-helpers.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,CAAC,EAAE,MAAM,KAAK,CAAC;AAExB;;;;;GAKG;AAEH,KAAK,kBAAkB,GAAG,GAAG,CAAC;AAE9B;;;;;;;;;;;;;;;;;;GAkBG;AACH,wBAAgB,WAAW,CAAC,KAAK,EAAE,kBAAkB;;;;;;;;;GAKpD;AAED;;;;;;;;;;;;;;;;GAgBG;AACH,wBAAgB,SAAS,CAAC,KAAK,EAAE,kBAAkB;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAOlD"}
1
+ {"version":3,"file":"content-helpers.d.ts","sourceRoot":"","sources":["../src/content-helpers.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,CAAC,EAAE,MAAM,KAAK,CAAC;AAExB;;;;;GAKG;AAEH,KAAK,kBAAkB,GAAG,GAAG,CAAC;AAE9B;;;;;;;;;;;;;;;;;;;;;;;GAuBG;AACH,wBAAgB,WAAW,CAAC,KAAK,EAAE,kBAAkB;;;;;;;;;GAKpD;AAED;;;;;;;;;;;;;;;;GAgBG;AACH,wBAAgB,SAAS,CAAC,KAAK,EAAE,kBAAkB;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAOlD"}
@@ -4,6 +4,11 @@ import { z } from 'zod';
4
4
  * `image()` from the collection schema factory so the image is processed
5
5
  * through Astro's asset pipeline.
6
6
  *
7
+ * `alt` is **required** — missing alt text is an accessibility failure
8
+ * and an SEO failure. Decorative images should use `alt: ''` explicitly.
9
+ * If you want to make the whole image optional, wrap the schema:
10
+ * `imageSchema(image).optional()`.
11
+ *
7
12
  * @example
8
13
  * ```ts
9
14
  * import { defineCollection, z } from 'astro:content';
@@ -21,7 +26,7 @@ import { z } from 'zod';
21
26
  export function imageSchema(image) {
22
27
  return z.object({
23
28
  src: image(),
24
- alt: z.string().optional(),
29
+ alt: z.string(),
25
30
  });
26
31
  }
27
32
  /**
@@ -1 +1 @@
1
- {"version":3,"file":"content-helpers.js","sourceRoot":"","sources":["../src/content-helpers.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,CAAC,EAAE,MAAM,KAAK,CAAC;AAWxB;;;;;;;;;;;;;;;;;;GAkBG;AACH,MAAM,UAAU,WAAW,CAAC,KAAyB;IACjD,OAAO,CAAC,CAAC,MAAM,CAAC;QACZ,GAAG,EAAE,KAAK,EAAE;QACZ,GAAG,EAAE,CAAC,CAAC,MAAM,EAAE,CAAC,QAAQ,EAAE;KAC7B,CAAC,CAAC;AACP,CAAC;AAED;;;;;;;;;;;;;;;;GAgBG;AACH,MAAM,UAAU,SAAS,CAAC,KAAyB;IAC/C,OAAO,CAAC,CAAC,MAAM,CAAC;QACZ,KAAK,EAAE,CAAC,CAAC,MAAM,EAAE,CAAC,GAAG,CAAC,CAAC,CAAC,CAAC,GAAG,CAAC,GAAG,CAAC,CAAC,QAAQ,EAAE;QAC5C,WAAW,EAAE,CAAC,CAAC,MAAM,EAAE,CAAC,GAAG,CAAC,EAAE,CAAC,CAAC,GAAG,CAAC,GAAG,CAAC,CAAC,QAAQ,EAAE;QACnD,KAAK,EAAE,WAAW,CAAC,KAAK,CAAC,CAAC,QAAQ,EAAE;QACpC,QAAQ,EAAE,CAAC,CAAC,IAAI,CAAC,CAAC,SAAS,EAAE,SAAS,CAAC,CAAC,CAAC,OAAO,CAAC,SAAS,CAAC;KAC9D,CAAC,CAAC;AACP,CAAC"}
1
+ {"version":3,"file":"content-helpers.js","sourceRoot":"","sources":["../src/content-helpers.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,CAAC,EAAE,MAAM,KAAK,CAAC;AAWxB;;;;;;;;;;;;;;;;;;;;;;;GAuBG;AACH,MAAM,UAAU,WAAW,CAAC,KAAyB;IACjD,OAAO,CAAC,CAAC,MAAM,CAAC;QACZ,GAAG,EAAE,KAAK,EAAE;QACZ,GAAG,EAAE,CAAC,CAAC,MAAM,EAAE;KAClB,CAAC,CAAC;AACP,CAAC;AAED;;;;;;;;;;;;;;;;GAgBG;AACH,MAAM,UAAU,SAAS,CAAC,KAAyB;IAC/C,OAAO,CAAC,CAAC,MAAM,CAAC;QACZ,KAAK,EAAE,CAAC,CAAC,MAAM,EAAE,CAAC,GAAG,CAAC,CAAC,CAAC,CAAC,GAAG,CAAC,GAAG,CAAC,CAAC,QAAQ,EAAE;QAC5C,WAAW,EAAE,CAAC,CAAC,MAAM,EAAE,CAAC,GAAG,CAAC,EAAE,CAAC,CAAC,GAAG,CAAC,GAAG,CAAC,CAAC,QAAQ,EAAE;QACnD,KAAK,EAAE,WAAW,CAAC,KAAK,CAAC,CAAC,QAAQ,EAAE;QACpC,QAAQ,EAAE,CAAC,CAAC,IAAI,CAAC,CAAC,SAAS,EAAE,SAAS,CAAC,CAAC,CAAC,OAAO,CAAC,SAAS,CAAC;KAC9D,CAAC,CAAC;AACP,CAAC"}
@@ -0,0 +1,48 @@
1
+ interface BuildDoneHook {
2
+ dir: URL;
3
+ logger: {
4
+ warn: (msg: string) => void;
5
+ info: (msg: string) => void;
6
+ [key: string]: any;
7
+ };
8
+ }
9
+ interface AstroIntegrationLike {
10
+ name: string;
11
+ hooks: {
12
+ 'astro:build:done'?: (args: BuildDoneHook) => void | Promise<void>;
13
+ };
14
+ }
15
+ export interface SeoGraphIntegrationOptions {
16
+ /**
17
+ * Warn when a built page has zero or more than one `<h1>` element.
18
+ * Defaults to `true`. Only static pages are checked (SSR pages are
19
+ * not present on disk at build time).
20
+ */
21
+ validateH1?: boolean;
22
+ }
23
+ /**
24
+ * Count `<h1>` elements in an HTML string. Matches the opening tag only;
25
+ * tolerant of attributes and whitespace. Doesn't parse — good enough for
26
+ * a lint warning.
27
+ */
28
+ export declare function countH1s(html: string): number;
29
+ /**
30
+ * Astro integration for `@jdevalk/astro-seo-graph`.
31
+ *
32
+ * Currently:
33
+ * - Warns about built pages with zero or more than one `<h1>` element
34
+ * (a common SEO and accessibility issue).
35
+ *
36
+ * ```js
37
+ * // astro.config.mjs
38
+ * import { defineConfig } from 'astro/config';
39
+ * import seoGraph from '@jdevalk/astro-seo-graph/integration';
40
+ *
41
+ * export default defineConfig({
42
+ * integrations: [seoGraph()],
43
+ * });
44
+ * ```
45
+ */
46
+ export default function seoGraph(options?: SeoGraphIntegrationOptions): AstroIntegrationLike;
47
+ export {};
48
+ //# sourceMappingURL=integration.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"integration.d.ts","sourceRoot":"","sources":["../src/integration.ts"],"names":[],"mappings":"AAOA,UAAU,aAAa;IACnB,GAAG,EAAE,GAAG,CAAC;IAET,MAAM,EAAE;QAAE,IAAI,EAAE,CAAC,GAAG,EAAE,MAAM,KAAK,IAAI,CAAC;QAAC,IAAI,EAAE,CAAC,GAAG,EAAE,MAAM,KAAK,IAAI,CAAC;QAAC,CAAC,GAAG,EAAE,MAAM,GAAG,GAAG,CAAA;KAAE,CAAC;CAC5F;AAED,UAAU,oBAAoB;IAC1B,IAAI,EAAE,MAAM,CAAC;IACb,KAAK,EAAE;QACH,kBAAkB,CAAC,EAAE,CAAC,IAAI,EAAE,aAAa,KAAK,IAAI,GAAG,OAAO,CAAC,IAAI,CAAC,CAAC;KACtE,CAAC;CACL;AAED,MAAM,WAAW,0BAA0B;IACvC;;;;OAIG;IACH,UAAU,CAAC,EAAE,OAAO,CAAC;CACxB;AAED;;;;GAIG;AACH,wBAAgB,QAAQ,CAAC,IAAI,EAAE,MAAM,GAAG,MAAM,CAG7C;AAgBD;;;;;;;;;;;;;;;;GAgBG;AACH,MAAM,CAAC,OAAO,UAAU,QAAQ,CAAC,OAAO,GAAE,0BAA+B,GAAG,oBAAoB,CAoC/F"}
@@ -0,0 +1,78 @@
1
+ import { readFile, readdir } from 'node:fs/promises';
2
+ import { join, relative } from 'node:path';
3
+ import { fileURLToPath } from 'node:url';
4
+ /**
5
+ * Count `<h1>` elements in an HTML string. Matches the opening tag only;
6
+ * tolerant of attributes and whitespace. Doesn't parse — good enough for
7
+ * a lint warning.
8
+ */
9
+ export function countH1s(html) {
10
+ const matches = html.match(/<h1[\s>]/gi);
11
+ return matches ? matches.length : 0;
12
+ }
13
+ async function collectHtmlFiles(dir, base = dir) {
14
+ const entries = await readdir(dir, { withFileTypes: true });
15
+ const files = [];
16
+ for (const entry of entries) {
17
+ const fullPath = join(dir, entry.name);
18
+ if (entry.isDirectory()) {
19
+ files.push(...(await collectHtmlFiles(fullPath, base)));
20
+ }
21
+ else if (entry.isFile() && entry.name.endsWith('.html')) {
22
+ files.push(relative(base, fullPath));
23
+ }
24
+ }
25
+ return files;
26
+ }
27
+ /**
28
+ * Astro integration for `@jdevalk/astro-seo-graph`.
29
+ *
30
+ * Currently:
31
+ * - Warns about built pages with zero or more than one `<h1>` element
32
+ * (a common SEO and accessibility issue).
33
+ *
34
+ * ```js
35
+ * // astro.config.mjs
36
+ * import { defineConfig } from 'astro/config';
37
+ * import seoGraph from '@jdevalk/astro-seo-graph/integration';
38
+ *
39
+ * export default defineConfig({
40
+ * integrations: [seoGraph()],
41
+ * });
42
+ * ```
43
+ */
44
+ export default function seoGraph(options = {}) {
45
+ const { validateH1 = true } = options;
46
+ return {
47
+ name: '@jdevalk/astro-seo-graph',
48
+ hooks: {
49
+ 'astro:build:done': async ({ dir, logger }) => {
50
+ if (!validateH1)
51
+ return;
52
+ const buildDir = fileURLToPath(dir);
53
+ const htmlFiles = await collectHtmlFiles(buildDir);
54
+ const missing = [];
55
+ const multiple = [];
56
+ for (const file of htmlFiles) {
57
+ const content = await readFile(join(buildDir, file), 'utf8');
58
+ const count = countH1s(content);
59
+ if (count === 0)
60
+ missing.push(file);
61
+ else if (count > 1)
62
+ multiple.push({ file, count });
63
+ }
64
+ if (missing.length === 0 && multiple.length === 0) {
65
+ logger.info(`H1 validation: ${htmlFiles.length} pages checked, all good.`);
66
+ return;
67
+ }
68
+ for (const file of missing) {
69
+ logger.warn(`H1 validation: ${file} has no <h1>.`);
70
+ }
71
+ for (const { file, count } of multiple) {
72
+ logger.warn(`H1 validation: ${file} has ${count} <h1> elements (expected 1).`);
73
+ }
74
+ },
75
+ },
76
+ };
77
+ }
78
+ //# sourceMappingURL=integration.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"integration.js","sourceRoot":"","sources":["../src/integration.ts"],"names":[],"mappings":"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"}
package/dist/routes.d.ts CHANGED
@@ -58,10 +58,6 @@ export interface SchemaMapEntry {
58
58
  path: string;
59
59
  /** When the resource at this path was last modified. */
60
60
  lastModified: Date;
61
- /** Defaults to `'daily'`. */
62
- changeFreq?: 'always' | 'hourly' | 'daily' | 'weekly' | 'monthly' | 'yearly' | 'never';
63
- /** 0.0–1.0. Defaults to `0.8`. */
64
- priority?: number;
65
61
  }
66
62
  export interface SchemaMapOptions {
67
63
  /** Canonical site URL. Trailing slash is stripped. */
@@ -1 +1 @@
1
- {"version":3,"file":"routes.d.ts","sourceRoot":"","sources":["../src/routes.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,QAAQ,EAAE,MAAM,OAAO,CAAC;AACtC,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,yBAAyB,CAAC;AAG3D,MAAM,WAAW,qBAAqB,CAAC,KAAK;IACxC;;;;OAIG;IACH,OAAO,EAAE,MAAM,OAAO,CAAC,SAAS,KAAK,EAAE,CAAC,CAAC;IACzC;;;;OAIG;IACH,MAAM,EAAE,CAAC,KAAK,EAAE,KAAK,KAAK,aAAa,CAAC,WAAW,CAAC,CAAC;IACrD;;;OAGG;IACH,YAAY,CAAC,EAAE,MAAM,GAAG,IAAI,CAAC;IAC7B;;OAEG;IACH,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB;;;OAGG;IACH,MAAM,CAAC,EAAE,MAAM,CAAC;CACnB;AAED;;;;;;;;;;;;;;;;;;;;GAoBG;AACH,wBAAgB,oBAAoB,CAAC,KAAK,EAAE,OAAO,EAAE,qBAAqB,CAAC,KAAK,CAAC,GAAG,QAAQ,CAY3F;AAED,MAAM,WAAW,cAAc;IAC3B;;;OAGG;IACH,IAAI,EAAE,MAAM,CAAC;IACb,wDAAwD;IACxD,YAAY,EAAE,IAAI,CAAC;IACnB,6BAA6B;IAC7B,UAAU,CAAC,EAAE,QAAQ,GAAG,QAAQ,GAAG,OAAO,GAAG,QAAQ,GAAG,SAAS,GAAG,QAAQ,GAAG,OAAO,CAAC;IACvF,kCAAkC;IAClC,QAAQ,CAAC,EAAE,MAAM,CAAC;CACrB;AAED,MAAM,WAAW,gBAAgB;IAC7B,sDAAsD;IACtD,OAAO,EAAE,MAAM,CAAC;IAChB,qCAAqC;IACrC,OAAO,EAAE,SAAS,cAAc,EAAE,CAAC;IACnC,sDAAsD;IACtD,YAAY,CAAC,EAAE,MAAM,GAAG,IAAI,CAAC;CAChC;AAWD;;;;;;;;;;;;;;;;;;GAkBG;AACH,wBAAgB,eAAe,CAAC,OAAO,EAAE,gBAAgB,GAAG,QAAQ,CA8BnE"}
1
+ {"version":3,"file":"routes.d.ts","sourceRoot":"","sources":["../src/routes.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,QAAQ,EAAE,MAAM,OAAO,CAAC;AACtC,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,yBAAyB,CAAC;AAG3D,MAAM,WAAW,qBAAqB,CAAC,KAAK;IACxC;;;;OAIG;IACH,OAAO,EAAE,MAAM,OAAO,CAAC,SAAS,KAAK,EAAE,CAAC,CAAC;IACzC;;;;OAIG;IACH,MAAM,EAAE,CAAC,KAAK,EAAE,KAAK,KAAK,aAAa,CAAC,WAAW,CAAC,CAAC;IACrD;;;OAGG;IACH,YAAY,CAAC,EAAE,MAAM,GAAG,IAAI,CAAC;IAC7B;;OAEG;IACH,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB;;;OAGG;IACH,MAAM,CAAC,EAAE,MAAM,CAAC;CACnB;AAED;;;;;;;;;;;;;;;;;;;;GAoBG;AACH,wBAAgB,oBAAoB,CAAC,KAAK,EAAE,OAAO,EAAE,qBAAqB,CAAC,KAAK,CAAC,GAAG,QAAQ,CAe3F;AAED,MAAM,WAAW,cAAc;IAC3B;;;OAGG;IACH,IAAI,EAAE,MAAM,CAAC;IACb,wDAAwD;IACxD,YAAY,EAAE,IAAI,CAAC;CACtB;AAED,MAAM,WAAW,gBAAgB;IAC7B,sDAAsD;IACtD,OAAO,EAAE,MAAM,CAAC;IAChB,qCAAqC;IACrC,OAAO,EAAE,SAAS,cAAc,EAAE,CAAC;IACnC,sDAAsD;IACtD,YAAY,CAAC,EAAE,MAAM,GAAG,IAAI,CAAC;CAChC;AAWD;;;;;;;;;;;;;;;;;;GAkBG;AACH,wBAAgB,eAAe,CAAC,OAAO,EAAE,gBAAgB,GAAG,QAAQ,CA6BnE"}
package/dist/routes.js CHANGED
@@ -27,7 +27,10 @@ export function createSchemaEndpoint(options) {
27
27
  return async () => {
28
28
  const entries = await options.entries();
29
29
  const graph = aggregate({ entries, mapper: options.mapper });
30
- const headers = { 'Content-Type': contentType };
30
+ const headers = {
31
+ 'Content-Type': contentType,
32
+ 'X-Robots-Tag': 'noindex, follow',
33
+ };
31
34
  if (cacheControl !== null)
32
35
  headers['Cache-Control'] = cacheControl;
33
36
  return new Response(JSON.stringify(graph, null, indent), { headers });
@@ -69,13 +72,9 @@ export function createSchemaMap(options) {
69
72
  const pathPart = entry.path.startsWith('/') ? entry.path : `/${entry.path}`;
70
73
  const loc = escapeXml(`${site}${pathPart}`);
71
74
  const lastmod = entry.lastModified.toISOString().split('T')[0];
72
- const changefreq = entry.changeFreq ?? 'daily';
73
- const priority = entry.priority ?? 0.8;
74
75
  return ` <url contentType="structuredData/schema.org">
75
76
  <loc>${loc}</loc>
76
77
  <lastmod>${lastmod}</lastmod>
77
- <changefreq>${changefreq}</changefreq>
78
- <priority>${priority}</priority>
79
78
  </url>`;
80
79
  })
81
80
  .join('\n');
@@ -83,7 +82,10 @@ export function createSchemaMap(options) {
83
82
  <urlset xmlns="http://www.sitemaps.org/schemas/sitemap/0.9">
84
83
  ${urls}
85
84
  </urlset>`;
86
- const headers = { 'Content-Type': 'application/xml' };
85
+ const headers = {
86
+ 'Content-Type': 'application/xml',
87
+ 'X-Robots-Tag': 'noindex, follow',
88
+ };
87
89
  if (cacheControl !== null)
88
90
  headers['Cache-Control'] = cacheControl;
89
91
  return new Response(xml, { headers });
@@ -1 +1 @@
1
- {"version":3,"file":"routes.js","sourceRoot":"","sources":["../src/routes.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"routes.js","sourceRoot":"","sources":["../src/routes.ts"],"names":[],"mappings":"AAEA,OAAO,EAAE,SAAS,EAAE,MAAM,iBAAiB,CAAC;AA+B5C;;;;;;;;;;;;;;;;;;;;GAoBG;AACH,MAAM,UAAU,oBAAoB,CAAQ,OAAqC;IAC7E,MAAM,YAAY,GAAG,OAAO,CAAC,YAAY,KAAK,SAAS,CAAC,CAAC,CAAC,aAAa,CAAC,CAAC,CAAC,OAAO,CAAC,YAAY,CAAC;IAC/F,MAAM,WAAW,GAAG,OAAO,CAAC,WAAW,IAAI,qBAAqB,CAAC;IACjE,MAAM,MAAM,GAAG,OAAO,CAAC,MAAM,IAAI,CAAC,CAAC;IAEnC,OAAO,KAAK,IAAI,EAAE;QACd,MAAM,OAAO,GAAG,MAAM,OAAO,CAAC,OAAO,EAAE,CAAC;QACxC,MAAM,KAAK,GAAG,SAAS,CAAC,EAAE,OAAO,EAAE,MAAM,EAAE,OAAO,CAAC,MAAM,EAAE,CAAC,CAAC;QAC7D,MAAM,OAAO,GAA2B;YACpC,cAAc,EAAE,WAAW;YAC3B,cAAc,EAAE,iBAAiB;SACpC,CAAC;QACF,IAAI,YAAY,KAAK,IAAI;YAAE,OAAO,CAAC,eAAe,CAAC,GAAG,YAAY,CAAC;QACnE,OAAO,IAAI,QAAQ,CAAC,IAAI,CAAC,SAAS,CAAC,KAAK,EAAE,IAAI,EAAE,MAAM,CAAC,EAAE,EAAE,OAAO,EAAE,CAAC,CAAC;IAC1E,CAAC,CAAC;AACN,CAAC;AAqBD,SAAS,SAAS,CAAC,KAAa;IAC5B,OAAO,KAAK;SACP,OAAO,CAAC,IAAI,EAAE,OAAO,CAAC;SACtB,OAAO,CAAC,IAAI,EAAE,MAAM,CAAC;SACrB,OAAO,CAAC,IAAI,EAAE,MAAM,CAAC;SACrB,OAAO,CAAC,IAAI,EAAE,QAAQ,CAAC;SACvB,OAAO,CAAC,IAAI,EAAE,QAAQ,CAAC,CAAC;AACjC,CAAC;AAED;;;;;;;;;;;;;;;;;;GAkBG;AACH,MAAM,UAAU,eAAe,CAAC,OAAyB;IACrD,MAAM,YAAY,GAAG,OAAO,CAAC,YAAY,KAAK,SAAS,CAAC,CAAC,CAAC,aAAa,CAAC,CAAC,CAAC,OAAO,CAAC,YAAY,CAAC;IAC/F,MAAM,IAAI,GAAG,OAAO,CAAC,OAAO,CAAC,OAAO,CAAC,MAAM,EAAE,EAAE,CAAC,CAAC;IAEjD,OAAO,KAAK,IAAI,EAAE;QACd,MAAM,IAAI,GAAG,OAAO,CAAC,OAAO;aACvB,GAAG,CAAC,CAAC,KAAK,EAAE,EAAE;YACX,MAAM,QAAQ,GAAG,KAAK,CAAC,IAAI,CAAC,UAAU,CAAC,GAAG,CAAC,CAAC,CAAC,CAAC,KAAK,CAAC,IAAI,CAAC,CAAC,CAAC,IAAI,KAAK,CAAC,IAAI,EAAE,CAAC;YAC5E,MAAM,GAAG,GAAG,SAAS,CAAC,GAAG,IAAI,GAAG,QAAQ,EAAE,CAAC,CAAC;YAC5C,MAAM,OAAO,GAAG,KAAK,CAAC,YAAY,CAAC,WAAW,EAAE,CAAC,KAAK,CAAC,GAAG,CAAC,CAAC,CAAC,CAAC,CAAC;YAC/D,OAAO;WACZ,GAAG;eACC,OAAO;SACb,CAAC;QACE,CAAC,CAAC;aACD,IAAI,CAAC,IAAI,CAAC,CAAC;QAEhB,MAAM,GAAG,GAAG;;EAElB,IAAI;UACI,CAAC;QAEH,MAAM,OAAO,GAA2B;YACpC,cAAc,EAAE,iBAAiB;YACjC,cAAc,EAAE,iBAAiB;SACpC,CAAC;QACF,IAAI,YAAY,KAAK,IAAI;YAAE,OAAO,CAAC,eAAe,CAAC,GAAG,YAAY,CAAC;QACnE,OAAO,IAAI,QAAQ,CAAC,GAAG,EAAE,EAAE,OAAO,EAAE,CAAC,CAAC;IAC1C,CAAC,CAAC;AACN,CAAC"}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@jdevalk/astro-seo-graph",
3
- "version": "0.5.2",
3
+ "version": "0.6.0",
4
4
  "description": "Astro integration for @jdevalk/seo-graph-core. Seo component, route factories, content-collection aggregator, Zod content helpers.",
5
5
  "keywords": [
6
6
  "astro",
@@ -23,7 +23,11 @@
23
23
  "import": "./dist/index.js"
24
24
  },
25
25
  "./Seo.astro": "./dist/components/Seo.astro",
26
- "./FuzzyRedirect.astro": "./dist/components/FuzzyRedirect.astro"
26
+ "./FuzzyRedirect.astro": "./dist/components/FuzzyRedirect.astro",
27
+ "./integration": {
28
+ "types": "./dist/integration.d.ts",
29
+ "import": "./dist/integration.js"
30
+ }
27
31
  },
28
32
  "files": [
29
33
  "dist",