|
1 | 1 | # @jdevalk/astro-seo-graph |
2 | 2 |
|
| 3 | +## 2.0.0 |
| 4 | + |
| 5 | +### Major Changes |
| 6 | + |
| 7 | +- a32a1d4: **Breaking:** drop Astro 5 from peer deps; require Astro 6 and zod 4. |
| 8 | + |
| 9 | + Why: Astro 5.x ships zod 3 and Astro 6.x ships zod 4. Supporting both Astro majors meant shipping zod 3 as a runtime dep while users on Astro 6 had zod 4 from `astro:content`. zod brands schemas with version-specific symbols, so `seoSchema(image)` (returned as a zod 3 schema) couldn't compose cleanly into a user's `z.object({ ... })` from `astro:content` (zod 4) — composition produced TS type errors and worked at runtime only by accident. |
| 10 | + |
| 11 | + **What changed:** |
| 12 | + - `peerDependencies.astro` is now `^6.0.0` (was `^5.0.0 || ^6.0.0`). |
| 13 | + - `dependencies.zod` is now `^4.4.3` (was `^3.24.0`). |
| 14 | + |
| 15 | + **What didn't change:** |
| 16 | + - The exported API surface (`<Seo>`, `createSchemaEndpoint`, `createSchemaMap`, `createApiCatalog`, `createMarkdownEndpoint`, `createIndexNowKeyRoute`, `seoSchema`, `imageSchema`, `buildAlternateLinks`, `breadcrumbsFromUrl`, `gitLastmod`, `aggregate`, `renderLlmsTxt`, `renderMarkdownAlternate`, all types) — all unchanged. |
| 17 | + - Our zod usage is core surface only (`z.object`, `z.string`, `z.enum`, `.min`, `.max`, `.optional`, `.default`) — no zod 4-specific syntax. |
| 18 | + |
| 19 | + **Migration:** |
| 20 | + |
| 21 | + If you're already on Astro 6, install: `pnpm add @jdevalk/astro-seo-graph@2`. Your `seo` and `featureImage` collection fields will start composing without the silent type drift they had under 1.x. |
| 22 | + |
| 23 | + If you're still on Astro 5, stay on `@jdevalk/astro-seo-graph@1.4.1`. The 1.x line gets bug fixes only — no new features ship to it. |
| 24 | + |
3 | 25 | ## 1.4.1 |
4 | 26 |
|
5 | 27 | ### Patch Changes |
|
156 | 178 | and renders every `<head>` tag directly. Net result: cleaner output, two |
157 | 179 | silent bugs fixed, lighter install. |
158 | 180 |
|
159 | | - ### Bug fixes (behavior changes that fix wrong output) |
160 | | - - **`articlePublisher` now actually renders.** Previously the prop typechecked |
161 | | - but was silently dropped — `astro-seo`'s `OpenGraphArticleTags` doesn't |
162 | | - destructure `publisher`, and its `openGraph.article` type doesn't include |
163 | | - the field. Anyone using `articlePublisher` was getting no output. `<Seo>` |
164 | | - now emits `<meta property="article:publisher" content="…">` as documented. |
165 | | - Filed upstream: [jonasmerlin/astro-seo#110](https://github.com/jonasmerlin/astro-seo/issues/110). |
166 | | - - **`<link rel="canonical">` no longer leaks on `noindex` pages.** The |
167 | | - intent ("omit canonical on noindex" per Google recommendation) was |
168 | | - defeated by `astro-seo`'s built-in canonical fallback that reconstructs |
169 | | - the tag from `Astro.url` when the prop is undefined. `<Seo>` now omits |
170 | | - the tag cleanly. Tracked upstream: |
171 | | - [jonasmerlin/astro-seo#107](https://github.com/jonasmerlin/astro-seo/issues/107). |
172 | | - - **Single `og:image` tag instead of `og:image` + `og:image:url`.** The |
173 | | - `astro-seo` path emitted both with the same value — synonymous, harmless, |
174 | | - but noise. `<Seo>` emits just `og:image`. |
175 | | - - **Single `<meta name="robots">` tag.** The `astro-seo` path always |
176 | | - emitted its own default `index, follow` tag that couldn't be suppressed, |
177 | | - while we needed our own to add `max-snippet:-1, max-image-preview:large, |
| 181 | + ### Bug fixes (behavior changes that fix wrong output) |
| 182 | + - **`articlePublisher` now actually renders.** Previously the prop typechecked |
| 183 | + but was silently dropped — `astro-seo`'s `OpenGraphArticleTags` doesn't |
| 184 | + destructure `publisher`, and its `openGraph.article` type doesn't include |
| 185 | + the field. Anyone using `articlePublisher` was getting no output. `<Seo>` |
| 186 | + now emits `<meta property="article:publisher" content="…">` as documented. |
| 187 | + Filed upstream: [jonasmerlin/astro-seo#110](https://github.com/jonasmerlin/astro-seo/issues/110). |
| 188 | + - **`<link rel="canonical">` no longer leaks on `noindex` pages.** The |
| 189 | + intent ("omit canonical on noindex" per Google recommendation) was |
| 190 | + defeated by `astro-seo`'s built-in canonical fallback that reconstructs |
| 191 | + the tag from `Astro.url` when the prop is undefined. `<Seo>` now omits |
| 192 | + the tag cleanly. Tracked upstream: |
| 193 | + [jonasmerlin/astro-seo#107](https://github.com/jonasmerlin/astro-seo/issues/107). |
| 194 | + - **Single `og:image` tag instead of `og:image` + `og:image:url`.** The |
| 195 | + `astro-seo` path emitted both with the same value — synonymous, harmless, |
| 196 | + but noise. `<Seo>` emits just `og:image`. |
| 197 | + - **Single `<meta name="robots">` tag.** The `astro-seo` path always |
| 198 | + emitted its own default `index, follow` tag that couldn't be suppressed, |
| 199 | + while we needed our own to add `max-snippet:-1, max-image-preview:large, |
178 | 200 |
|
179 | 201 | max-video-preview:-1`. Result was two robots tags per page. `<Seo>` now |
180 | 202 | emits one merged tag with all directives. |
181 | 203 |
|
182 | | - ### Cosmetic changes (HTML diff but equivalent meaning) |
183 | | - |
184 | | - The byte-for-byte head output differs from 0.x in tag ordering. Search engines |
185 | | - treat these as identical, but consumers running snapshot tests against |
186 | | - `<Seo>` output will see diffs. |
187 | | - - **Tag grouping by concern**: title → canonical → description → robots → |
188 | | - OG basic → OG optional → OG image meta → OG article → twitter → |
189 | | - hreflang → author → extras → JSON-LD. Previously the order was driven |
190 | | - by `astro-seo`'s sub-components. |
191 | | - - **Twitter overrides** follow the same field order as their OG counterparts |
192 | | - (card / site / creator / title / description / image / imageAlt). The |
193 | | - legacy order was card / site / title / image / imageAlt / description / creator. |
194 | | - - **Hreflang link attribute order**: `rel` → `hreflang` → `href`. |
195 | | - The legacy order was `rel` → `href` → `hreflang`. |
196 | | - |
197 | | - ### Breaking API changes |
198 | | - - **Removed `buildAstroSeoProps`**. Replaced by `buildSeoContext`, which |
199 | | - returns a flat, render-ready normalization (`SeoContext`) rather than the |
200 | | - nested `astro-seo`-shaped adapter. Migration: |
201 | | - |
202 | | - ```diff |
203 | | - - import { buildAstroSeoProps } from '@jdevalk/astro-seo-graph'; |
204 | | - - const props = buildAstroSeoProps(seo, Astro.url.href); |
205 | | - - props.openGraph.basic.title // nested |
206 | | - + import { buildSeoContext } from '@jdevalk/astro-seo-graph'; |
207 | | - + const ctx = buildSeoContext(seo, Astro.url.href); |
208 | | - + ctx.og.title // flat |
209 | | - ``` |
210 | | - |
211 | | - - **Removed `AstroSeoProps` type export**. The intermediate shape no |
212 | | - longer exists. Use `SeoContext` (the new normalized shape) or `SeoProps` |
213 | | - (the public input shape) depending on which side of the boundary you're on. |
214 | | - - **Removed `astro-seo` from dependencies**. If you imported it transitively |
215 | | - through this package, install it directly: `pnpm add astro-seo`. |
216 | | - |
217 | | - ### New exports |
218 | | - - `buildSeoContext(props, url): SeoContext` — pure-TS normalization. |
219 | | - - `SeoContext` type — flat render-ready shape. |
220 | | - - `ROBOTS_EXTRAS` constant — the `max-snippet:-1, max-image-preview:large, |
| 204 | + ### Cosmetic changes (HTML diff but equivalent meaning) |
| 205 | + |
| 206 | + The byte-for-byte head output differs from 0.x in tag ordering. Search engines |
| 207 | + treat these as identical, but consumers running snapshot tests against |
| 208 | + `<Seo>` output will see diffs. |
| 209 | + - **Tag grouping by concern**: title → canonical → description → robots → |
| 210 | + OG basic → OG optional → OG image meta → OG article → twitter → |
| 211 | + hreflang → author → extras → JSON-LD. Previously the order was driven |
| 212 | + by `astro-seo`'s sub-components. |
| 213 | + - **Twitter overrides** follow the same field order as their OG counterparts |
| 214 | + (card / site / creator / title / description / image / imageAlt). The |
| 215 | + legacy order was card / site / title / image / imageAlt / description / creator. |
| 216 | + - **Hreflang link attribute order**: `rel` → `hreflang` → `href`. |
| 217 | + The legacy order was `rel` → `href` → `hreflang`. |
| 218 | + |
| 219 | + ### Breaking API changes |
| 220 | + - **Removed `buildAstroSeoProps`**. Replaced by `buildSeoContext`, which |
| 221 | + returns a flat, render-ready normalization (`SeoContext`) rather than the |
| 222 | + nested `astro-seo`-shaped adapter. Migration: |
| 223 | + |
| 224 | + ```diff |
| 225 | + - import { buildAstroSeoProps } from '@jdevalk/astro-seo-graph'; |
| 226 | + - const props = buildAstroSeoProps(seo, Astro.url.href); |
| 227 | + - props.openGraph.basic.title // nested |
| 228 | + + import { buildSeoContext } from '@jdevalk/astro-seo-graph'; |
| 229 | + + const ctx = buildSeoContext(seo, Astro.url.href); |
| 230 | + + ctx.og.title // flat |
| 231 | + ``` |
| 232 | + |
| 233 | + - **Removed `AstroSeoProps` type export**. The intermediate shape no |
| 234 | + longer exists. Use `SeoContext` (the new normalized shape) or `SeoProps` |
| 235 | + (the public input shape) depending on which side of the boundary you're on. |
| 236 | + - **Removed `astro-seo` from dependencies**. If you imported it transitively |
| 237 | + through this package, install it directly: `pnpm add astro-seo`. |
| 238 | + |
| 239 | + ### New exports |
| 240 | + - `buildSeoContext(props, url): SeoContext` — pure-TS normalization. |
| 241 | + - `SeoContext` type — flat render-ready shape. |
| 242 | + - `ROBOTS_EXTRAS` constant — the `max-snippet:-1, max-image-preview:large, |
221 | 243 |
|
222 | 244 | max-video-preview:-1`directives`<Seo>` always appends to the robots tag. |
223 | 245 |
|
|
0 commit comments