@growth-labs/seo 0.2.2 → 0.2.4

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
@@ -134,6 +134,11 @@ export default {
134
134
  - `/podcast.xml`, `/listen.xml`
135
135
  - `POST /_seo/revalidate` — CMS webhook target (when `onDemandRevalidation: true`)
136
136
 
137
+ **Head-tag component** (`<AeoHead />`):
138
+ - Consumers import `import AeoHead from '@growth-labs/seo/components/AeoHead.astro'` and render `<AeoHead />` inside their layout's `<head>`. Required because integrations can't directly inject arbitrary HTML into every page's `<head>`.
139
+ - Emits the Apple News discovery link (`<link rel="alternate" type="application/rss+xml">`) when `appleNews.discoveryLink: true`.
140
+ - Emits a per-page markdown twin link (`<link rel="alternate" type="text/markdown" href="<twinUrl>">`) for discoverability on prerendered HTML served by Cloudflare Assets where middleware doesn't run.
141
+
137
142
  **Build-time:**
138
143
  - Emits `.md` twins + summary twins for public items (static/both modes) under `dist/client/`.
139
144
  - Validates hreflang reciprocity.
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@growth-labs/seo",
3
- "version": "0.2.2",
3
+ "version": "0.2.4",
4
4
  "type": "module",
5
5
  "exports": {
6
6
  ".": {
@@ -40,10 +40,12 @@
40
40
  "./cron": {
41
41
  "types": "./dist/cron/prune-aeo-r2.d.ts",
42
42
  "import": "./dist/cron/prune-aeo-r2.js"
43
- }
43
+ },
44
+ "./components/AeoHead.astro": "./src/components/AeoHead.astro"
44
45
  },
45
46
  "files": [
46
47
  "dist",
48
+ "src/components",
47
49
  "README.md"
48
50
  ],
49
51
  "publishConfig": {
@@ -0,0 +1,65 @@
1
+ ---
2
+ // Side-effect: seeds state in whatever environment Vite bundles for.
3
+ // Required so `getConfig()` works in the Cloudflare prerender Worker.
4
+ import 'virtual:growth-labs/seo/config'
5
+
6
+ // Use package self-imports (not relative ../options.js / ../state.js) because
7
+ // this component ships as source from src/components/ but options.ts and state.ts
8
+ // compile to dist/. Relative `../options.js` would resolve to the non-existent
9
+ // src/options.js inside the consumer's node_modules. Package specifiers route
10
+ // through the exports map to the compiled dist output.
11
+ import { getConfig, resolveAeoTwins } from '@growth-labs/seo'
12
+
13
+ export interface Props {
14
+ /**
15
+ * Override the URL this page resolves to for twin-link emission.
16
+ * Defaults to `Astro.url`. Useful when rendering head tags for a URL that
17
+ * differs from the request URL (e.g. canonicalizing away trailing slash).
18
+ */
19
+ canonical?: URL | string
20
+ }
21
+
22
+ const { canonical } = Astro.props
23
+ const config = getConfig()
24
+ const aeo = resolveAeoTwins(config.aeoTwins)
25
+
26
+ // ─── Apple News discovery link ───
27
+ const appleNews = config.appleNews
28
+ const appleNewsHref =
29
+ appleNews?.enabled && appleNews.discoveryLink
30
+ ? `${config.site.replace(/\/$/, '')}${appleNews.feedPath}`
31
+ : null
32
+
33
+ // ─── Per-page markdown twin link (rel="alternate") ───
34
+ // Addresses the gap where prerendered HTML is served directly by Cloudflare
35
+ // Assets — middleware never runs, so the Link: HTTP header it would append
36
+ // is absent. Embedding a <link rel="alternate"> in <head> gives static hosts
37
+ // the same AEO discoverability signal as middleware-backed modes.
38
+ const pageUrl = (() => {
39
+ if (!canonical) return Astro.url.toString()
40
+ if (typeof canonical === 'string') return canonical
41
+ return canonical.toString()
42
+ })()
43
+
44
+ function defaultTwinUrl(url: string): string {
45
+ return `${url.replace(/\/+$/, '')}.md`
46
+ }
47
+
48
+ const twinHref =
49
+ aeo && aeo.mode !== 'middleware'
50
+ ? (aeo.twinUrl ?? defaultTwinUrl)(pageUrl)
51
+ : null
52
+ ---
53
+
54
+ {appleNewsHref && (
55
+ <link
56
+ rel="alternate"
57
+ type="application/rss+xml"
58
+ title={`${appleNews?.channelName} on Apple News`}
59
+ href={appleNewsHref}
60
+ />
61
+ )}
62
+
63
+ {twinHref && (
64
+ <link rel="alternate" type="text/markdown" href={twinHref} />
65
+ )}