@mannisto/astro-metadata 1.0.0-beta.3 → 1.0.0-beta.5

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
@@ -8,30 +8,7 @@
8
8
 
9
9
  Astro components for managing your page head — metadata, social sharing, favicons, and SEO.
10
10
 
11
- ## Table of contents
12
-
13
- - [Installation](#installation)
14
- - [Usage](#usage)
15
- - [Head component](#1-head-component)
16
- - [Individual components](#2-individual-components)
17
- - [Metadata utility](#3-metadata-utility)
18
- - [Components](#components)
19
- - [Canonical](#canonical)
20
- - [Description](#description)
21
- - [Favicon](#favicon)
22
- - [Head](#head)
23
- - [Keywords](#keywords)
24
- - [LanguageAlternates](#languagealternates)
25
- - [OpenGraph](#opengraph)
26
- - [Robots](#robots)
27
- - [Schema](#schema)
28
- - [Title](#title)
29
- - [Twitter](#twitter)
30
- - [Contributing](#contributing)
31
- - [License](#license)
32
-
33
11
  ## Installation
34
-
35
12
  ```bash
36
13
  # pnpm
37
14
  pnpm add @mannisto/astro-metadata
@@ -47,104 +24,43 @@ yarn add @mannisto/astro-metadata
47
24
 
48
25
  There are three ways to use this package. Pick what suits your project, or combine them freely.
49
26
 
50
- ### 1. Head component
51
-
52
- The simplest approach. Use `Head` in your layout and pass props down from your pages. Charset and viewport are included automatically.
27
+ ### Head component
53
28
 
29
+ The simplest approach — use `Head` in your layout and pass props down from pages. Title, description and image flow into Open Graph and Twitter automatically.
54
30
  ```astro
55
31
  ---
56
- // layouts/Layout.astro
57
32
  import { Head } from "@mannisto/astro-metadata"
58
- import type { HeadProps } from "@mannisto/astro-metadata"
59
-
60
- interface Props extends HeadProps {}
61
-
62
- const { title, description, ...rest } = Astro.props
63
33
  ---
64
34
 
65
35
  <html>
66
- <Head title={title} description={description} titleTemplate="%s | My Site" {...rest} />
36
+ <Head
37
+ title="Home"
38
+ titleTemplate="%s | My Site"
39
+ description="Welcome to my site"
40
+ image={{ url: "/og.jpg", alt: "My Site", width: 1200, height: 630 }}
41
+ />
67
42
  <body>
68
43
  <slot />
69
44
  </body>
70
45
  </html>
71
46
  ```
72
47
 
73
- ```astro
74
- ---
75
- // pages/index.astro
76
- import Layout from "../layouts/Layout.astro"
77
- ---
78
-
79
- <Layout title="Home" description="Welcome to my site">
80
- <h1>Hello</h1>
81
- </Layout>
82
- ```
83
-
84
- Best for simple sites where pages pass metadata as props to their layout.
48
+ [Read more →](docs/usage/head.md)
85
49
 
86
- ### 2. Individual components
87
-
88
- Use components directly inside your own `<head>`. Useful when you only need specific pieces, or want full control over the structure.
89
-
90
- ```astro
91
- ---
92
- import { Title, Description, OpenGraph, Favicon } from "@mannisto/astro-metadata"
93
- ---
94
-
95
- <html>
96
- <head>
97
- <meta charset="UTF-8" />
98
- <meta name="viewport" content="width=device-width, initial-scale=1.0" />
99
- <Title value="My Page" template="%s | My Site" />
100
- <Description value="Welcome to my site" />
101
- <OpenGraph
102
- title="My Page"
103
- description="Welcome to my site"
104
- image={{
105
- url: "/og.jpg",
106
- alt: "My Site",
107
- width: 1200,
108
- height: 630,
109
- }}
110
- />
111
- <Favicon icons={[{ path: "/favicon.ico" }, { path: "/favicon.svg" }]} />
112
- </head>
113
- <body>
114
- <slot />
115
- </body>
116
- </html>
117
- ```
118
-
119
- Best for when you want to compose only what you need, or when `Head` is too opinionated for your setup.
120
-
121
- ### 3. Metadata utility
122
-
123
- Set metadata in your page, resolve it in your layout. Eliminates prop drilling through nested layout layers.
50
+ ### Metadata API
124
51
 
52
+ Set metadata in pages, resolve in layouts — no prop drilling.
125
53
  ```astro
126
54
  ---
127
55
  // pages/about.astro
128
56
  import { Metadata } from "@mannisto/astro-metadata"
129
- import Layout from "../layouts/Layout.astro"
130
57
 
131
58
  Metadata.set({
132
59
  title: "About",
133
60
  description: "Learn more about us",
134
- openGraph: {
135
- image: {
136
- url: "/og/about.jpg",
137
- alt: "About",
138
- },
139
- },
140
61
  })
141
62
  ---
142
-
143
- <Layout>
144
- <h1>About</h1>
145
- </Layout>
146
63
  ```
147
-
148
64
  ```astro
149
65
  ---
150
66
  // layouts/Layout.astro
@@ -152,7 +68,6 @@ import { Head, Metadata } from "@mannisto/astro-metadata"
152
68
 
153
69
  const meta = Metadata.resolve({
154
70
  title: "My Site",
155
- description: "Default description",
156
71
  titleTemplate: "%s | My Site",
157
72
  })
158
73
  ---
@@ -165,329 +80,52 @@ const meta = Metadata.resolve({
165
80
  </html>
166
81
  ```
167
82
 
168
- `Metadata.resolve()` merges page values over your layout defaults — whatever the page sets wins, everything else falls back gracefully.
169
-
170
- Best for sites with deeply nested layouts, or when you want to keep metadata co-located with page content.
171
-
172
- ## Components
173
-
174
- ### Canonical
83
+ [Read more →](docs/usage/metadata.md)
175
84
 
176
- Renders a canonical link tag. Falls back to `Astro.url.href` when no value is provided, so every page gets a canonical tag with zero configuration.
85
+ ### Individual components
177
86
 
87
+ Use components directly in your `<head>` for full control.
178
88
  ```astro
179
- <Canonical value="https://example.com/page" />
180
- ```
181
-
182
- | Prop | Type | Description |
183
- | ------- | -------- | -------------------------------------------- |
184
- | `value` | `string` | Canonical URL. Defaults to `Astro.url.href`. |
185
-
186
- ### Description
187
-
188
- ```astro
189
- <Description value="Welcome to my site" />
190
- ```
191
-
192
- | Prop | Type | Description |
193
- | ------- | -------- | ---------------- |
194
- | `value` | `string` | Page description |
195
-
196
- ### Favicon
197
-
198
- Favicon support with light and dark mode variants, automatic MIME type detection, and automatic sorting.
199
-
200
- ```astro
201
- <Favicon
202
- icons={[
203
- { path: "/favicon.ico" },
204
- { path: "/favicon.svg" },
205
- { path: "/favicon-96x96.png", size: 96 },
206
- { path: "/apple-touch-icon.png", size: 180, apple: true },
207
- { path: "/favicon-dark.svg", theme: "dark" },
208
- { path: "/favicon-light.svg", theme: "light" },
209
- ]}
210
- manifest="/site.webmanifest"
211
- />
212
- ```
213
-
214
- Icons are automatically sorted in the recommended browser order: `ico` → `png` → `svg` → `apple` → themed variants. Pass `sort={false}` to preserve the original order.
215
-
216
- | Prop | Type | Default | Description |
217
- | ---------- | --------------- | ------- | --------------------------------------- |
218
- | `icons` | `FaviconFile[]` | — | List of favicon files |
219
- | `manifest` | `string` | — | Path to web app manifest |
220
- | `sort` | `boolean` | `true` | Sort icons in recommended browser order |
221
-
222
- #### FaviconFile
223
-
224
- | Prop | Type | Description |
225
- | ------- | ------------------- | ----------------------------------------------------------- |
226
- | `path` | `string` | Path to the file. MIME type is detected automatically. |
227
- | `size` | `number` | Size in pixels. Rendered as `NxN` in the `sizes` attribute. |
228
- | `theme` | `"light" \| "dark"` | Adds a `prefers-color-scheme` media query |
229
- | `apple` | `boolean` | Renders as `<link rel="apple-touch-icon">` |
230
-
231
- ### Head
232
-
233
- Wraps the entire page head and composes all sub-components internally. Charset and viewport are always included and can be overridden if needed.
234
-
235
- ```astro
236
- <Head
237
- title="Home"
238
- titleTemplate="%s | My Site"
239
- description="Welcome to my site"
240
- openGraph={{
241
- image: {
242
- url: "/og.jpg",
243
- alt: "My Site",
244
- width: 1200,
245
- height: 630,
246
- },
247
- }}
248
- favicon={{
249
- icons: [{ path: "/favicon.ico" }, { path: "/favicon.svg" }],
250
- }}
251
- />
252
- ```
253
-
254
- | Prop | Type | Default | Description |
255
- | -------------------- | ---------------------------- | ----------------------------------------- | --------------------------------------------------------- |
256
- | `title` | `string` | — | Page title. Required. |
257
- | `titleTemplate` | `` `${string}%s${string}` `` | — | Title template. Must contain `%s`, e.g. `"%s \| My Site"` |
258
- | `description` | `string` | — | Page description |
259
- | `canonical` | `string` | `Astro.url.href` | Canonical URL |
260
- | `keywords` | `string[]` | — | List of keywords |
261
- | `charset` | `string` | `"UTF-8"` | Document charset |
262
- | `viewport` | `string` | `"width=device-width, initial-scale=1.0"` | Viewport meta content |
263
- | `robots` | `RobotsProps` | — | Robots directives |
264
- | `openGraph` | `OpenGraphProps` | — | Open Graph tags |
265
- | `twitter` | `TwitterProps` | — | Twitter card tags |
266
- | `favicon` | `FaviconProps` | — | Favicon configuration |
267
- | `schema` | `SchemaProps` | — | JSON-LD structured data |
268
- | `languageAlternates` | `LanguageAlternate[]` | — | Hreflang alternate links |
269
-
270
- #### Slots
271
-
272
- ```astro
273
- <Head title="My Site">
274
- <!-- Renders before charset and viewport -->
275
- <meta slot="top" http-equiv="X-UA-Compatible" content="IE=edge" />
276
-
277
- <!-- Renders at the end of <head> -->
278
- <script src={analyticsUrl}></script>
279
- </Head>
280
- ```
281
-
282
- ### Keywords
283
-
284
- ```astro
285
- <Keywords value={["astro", "seo", "metadata"]} />
286
- ```
287
-
288
- | Prop | Type | Description |
289
- | ------- | ---------- | ---------------- |
290
- | `value` | `string[]` | List of keywords |
291
-
292
- ### LanguageAlternates
293
-
294
- Renders `<link rel="alternate" hreflang>` tags for multilingual sites. Tells search engines which language version to serve for a given region.
295
-
296
- ```astro
297
- <LanguageAlternates
298
- alternates={[
299
- { href: "https://example.com/en", hreflang: "en" },
300
- { href: "https://example.com/fi", hreflang: "fi" },
301
- { href: "https://example.com", hreflang: "x-default" },
302
- ]}
303
- />
304
- ```
305
-
306
- | Prop | Type | Description |
307
- | ----------------------- | --------------------- | -------------------------------------------------------------- |
308
- | `alternates` | `LanguageAlternate[]` | List of alternate language pages |
309
- | `alternates[].href` | `string` | Full URL of the alternate page |
310
- | `alternates[].hreflang` | `string` | Language or region code, e.g. `en`, `fi`, `en-US`, `x-default` |
311
-
312
- ### OpenGraph
313
-
314
- Renders Open Graph meta tags for rich previews when your pages are shared on social platforms. When used inside `Head`, `title`, `description` and `url` fall back to the page values automatically.
315
-
316
- ```astro
317
- <OpenGraph
318
- title="My Page"
319
- description="Welcome to my site"
320
- url="https://example.com"
321
- type="website"
322
- siteName="My Site"
323
- locale="en_US"
324
- localeAlternate={["fi_FI", "fr_FR"]}
325
- image={{
326
- url: "/og.jpg",
327
- secureUrl: "https://example.com/og.jpg",
328
- type: "image/jpeg",
329
- alt: "My Site",
330
- width: 1200,
331
- height: 630,
332
- }}
333
- video={{
334
- url: "https://example.com/video.mp4",
335
- secureUrl: "https://example.com/video.mp4",
336
- type: "video/mp4",
337
- width: 1280,
338
- height: 720,
339
- }}
340
- audio={{
341
- url: "https://example.com/audio.mp3",
342
- secureUrl: "https://example.com/audio.mp3",
343
- type: "audio/mpeg",
344
- }}
345
- />
346
- ```
347
-
348
- | Prop | Type | Default | Description |
349
- | ----------------- | ---------------- | ----------- | -------------------------------------------- |
350
- | `title` | `string` | — | OG title |
351
- | `description` | `string` | — | OG description |
352
- | `url` | `string` | — | Canonical URL for the OG object |
353
- | `type` | `string` | `"website"` | OG type |
354
- | `siteName` | `string` | — | Name of the site |
355
- | `locale` | `string` | — | Locale, e.g. `en_US` |
356
- | `localeAlternate` | `string[]` | — | Alternate locales, e.g. `["fi_FI", "fr_FR"]` |
357
- | `image` | `OpenGraphImage` | — | Image metadata |
358
- | `video` | `OpenGraphVideo` | — | Video metadata |
359
- | `audio` | `OpenGraphAudio` | — | Audio metadata |
360
-
361
- #### OpenGraphImage
362
-
363
- | Prop | Type | Description |
364
- | ----------- | -------- | ------------------------------------------ |
365
- | `url` | `string` | Image URL. Required if image is set. |
366
- | `secureUrl` | `string` | HTTPS image URL |
367
- | `type` | `string` | MIME type, e.g. `"image/jpeg"` |
368
- | `alt` | `string` | Image alt text |
369
- | `width` | `number` | Image width in pixels. Recommended: `1200` |
370
- | `height` | `number` | Image height in pixels. Recommended: `630` |
371
-
372
- #### OpenGraphVideo
373
-
374
- | Prop | Type | Description |
375
- | ----------- | -------- | ------------------------------------ |
376
- | `url` | `string` | Video URL. Required if video is set. |
377
- | `secureUrl` | `string` | HTTPS video URL |
378
- | `type` | `string` | MIME type, e.g. `"video/mp4"` |
379
- | `width` | `number` | Video width in pixels |
380
- | `height` | `number` | Video height in pixels |
381
-
382
- #### OpenGraphAudio
383
-
384
- | Prop | Type | Description |
385
- | ----------- | -------- | ------------------------------------ |
386
- | `url` | `string` | Audio URL. Required if audio is set. |
387
- | `secureUrl` | `string` | HTTPS audio URL |
388
- | `type` | `string` | MIME type, e.g. `"audio/mpeg"` |
389
-
390
- ### Robots
391
-
392
- Controls how search engines crawl and index your page. Defaults to `index, follow`.
393
-
394
- ```astro
395
- <Robots archive={false} extra="max-snippet:-1, max-image-preview:large, max-video-preview:-1" />
396
- ```
397
-
398
- | Prop | Type | Default | Description |
399
- | --------- | --------- | ------- | ----------------------------------------------------------------------- |
400
- | `index` | `boolean` | `true` | Allow indexing |
401
- | `follow` | `boolean` | `true` | Allow following links |
402
- | `archive` | `boolean` | `true` | Allow search engines to cache the page |
403
- | `snippet` | `boolean` | `true` | Allow text snippets in search results |
404
- | `extra` | `string` | — | Additional directives, e.g. `"max-snippet:-1, max-image-preview:large"` |
405
-
406
- ### Schema
407
-
408
- Outputs a `<script type="application/ld+json">` tag for structured data. Use it to help search engines understand your content and qualify for rich results.
409
-
410
- ```astro
411
- <Schema
412
- schema={{
413
- "@context": "https://schema.org",
414
- "@type": "Person",
415
- name: "Ere Männistö",
416
- url: "https://example.com",
417
- }}
418
- />
419
- ```
420
-
421
- | Prop | Type | Description |
422
- | -------- | ------------------------- | -------------- |
423
- | `schema` | `Record<string, unknown>` | JSON-LD object |
424
-
425
- ### Title
426
-
427
- Renders the `<title>` tag. The template must contain `%s`, which is replaced with the page title — TypeScript enforces this at the type level.
89
+ ---
90
+ import { Title, Description, OpenGraph } from "@mannisto/astro-metadata"
91
+ ---
428
92
 
429
- ```astro
430
- <Title value="My Page" template="%s | My Site" />
93
+ <html>
94
+ <head>
95
+ <meta charset="UTF-8" />
96
+ <meta name="viewport" content="width=device-width, initial-scale=1.0" />
97
+ <Title value="My Page" template="%s | My Site" />
98
+ <Description value="Welcome to my site" />
99
+ <OpenGraph
100
+ title="My Page"
101
+ image={{ url: "/og.jpg", alt: "My Site" }}
102
+ />
103
+ </head>
104
+ <body>
105
+ <slot />
106
+ </body>
107
+ </html>
431
108
  ```
432
109
 
433
- | Prop | Type | Description |
434
- | ---------- | ---------------------------- | ----------------------------------- |
435
- | `value` | `string` | Page title. Required. |
436
- | `template` | `` `${string}%s${string}` `` | Template string. Must contain `%s`. |
437
-
438
- ### Twitter
110
+ [Read more →](docs/usage/components.md)
439
111
 
440
- Renders Twitter card meta tags for rich previews on X. When used inside `Head`, `title` and `description` fall back to the page values automatically.
441
-
442
- ```astro
443
- <Twitter
444
- card="summary_large_image"
445
- site="@mysite"
446
- creator="@myhandle"
447
- url="https://example.com"
448
- image={{
449
- url: "/og.jpg",
450
- alt: "My Site",
451
- }}
452
- />
453
- ```
112
+ ## Components
454
113
 
455
- | Prop | Type | Default | Description |
456
- | ------------- | --------------------------------------------------------- | ----------------------- | ------------------------------------------ |
457
- | `title` | `string` | — | Card title |
458
- | `description` | `string` | — | Card description |
459
- | `url` | `string` | — | Canonical URL for the card |
460
- | `card` | `"summary" \| "summary_large_image" \| "player" \| "app"` | `"summary_large_image"` | Card type |
461
- | `site` | `string` | — | Twitter handle of the site, e.g. `@mysite` |
462
- | `creator` | `string` | — | Twitter handle of the content author |
463
- | `image.url` | `string` | — | Image URL. Required if image is set. |
464
- | `image.alt` | `string` | — | Image alt text |
114
+ - [Canonical](docs/components/canonical.md)
115
+ - [Description](docs/components/description.md)
116
+ - [Favicon](docs/components/favicon.md)
117
+ - [Head](docs/components/head.md)
118
+ - [Keywords](docs/components/keywords.md)
119
+ - [LanguageAlternates](docs/components/language-alternates.md)
120
+ - [OpenGraph](docs/components/open-graph.md)
121
+ - [Robots](docs/components/robots.md)
122
+ - [Schema](docs/components/schema.md)
123
+ - [Title](docs/components/title.md)
124
+ - [Twitter](docs/components/twitter.md)
465
125
 
466
126
  ## Contributing
467
127
 
468
- Clone the repo and run `pnpm run init` to install dependencies, link the local package to the fixture project, and set up Playwright. Then use `pnpm test:unit`, `pnpm test:e2e`, or `pnpm test:all` to run tests, and `pnpm check` / `pnpm format` for linting and formatting. All PRs must pass `pnpm check` — enforced via GitHub Actions.
469
-
470
- ### Project structure
471
-
472
- ```
473
- astro-metadata/
474
- src/
475
- components/
476
- lib/
477
- tests/
478
- e2e/
479
- components/
480
- fixtures/
481
- unit/
482
- metadata.test.ts
483
- scripts/
484
- init.sh
485
- index.ts
486
- playwright.config.ts
487
- vitest.config.ts
488
- biome.json
489
- prettier.config.ts
490
- ```
128
+ See [CONTRIBUTING.md](CONTRIBUTING.md) for setup and contribution guidelines.
491
129
 
492
130
  ## License
493
131
 
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@mannisto/astro-metadata",
3
- "version": "1.0.0-beta.3",
3
+ "version": "1.0.0-beta.5",
4
4
  "description": "Astro components for managing your page head — metadata, social sharing, favicons, and SEO.",
5
5
  "license": "MIT",
6
6
  "type": "module",
@@ -10,21 +10,23 @@ import Twitter from "./Twitter.astro"
10
10
  import Favicon from "./Favicon.astro"
11
11
  import Schema from "./Schema.astro"
12
12
  import LanguageAlternates from "./LanguageAlternates.astro"
13
+ import type { OpenGraphImage } from "./OpenGraph.astro"
13
14
 
14
15
  export type Props = {
15
16
  title: string
16
17
  titleTemplate?: `${string}%s${string}`
17
- description?: string
18
- canonical?: string
19
- keywords?: string[]
18
+ description?: string | false
19
+ canonical?: string | false
20
+ keywords?: string[] | false
20
21
  charset?: string
21
22
  viewport?: string
22
- robots?: ComponentProps<typeof Robots>
23
- openGraph?: ComponentProps<typeof OpenGraph>
24
- twitter?: ComponentProps<typeof Twitter>
25
- favicon?: ComponentProps<typeof Favicon>
26
- schema?: ComponentProps<typeof Schema>
27
- languageAlternates?: ComponentProps<typeof LanguageAlternates>["alternates"]
23
+ image?: OpenGraphImage | false
24
+ robots?: ComponentProps<typeof Robots> | false
25
+ openGraph?: ComponentProps<typeof OpenGraph> | false
26
+ twitter?: ComponentProps<typeof Twitter> | false
27
+ favicon?: ComponentProps<typeof Favicon> | false
28
+ schema?: ComponentProps<typeof Schema> | false
29
+ languageAlternates?: ComponentProps<typeof LanguageAlternates>["alternates"] | false
28
30
  }
29
31
 
30
32
  const {
@@ -35,6 +37,7 @@ const {
35
37
  keywords,
36
38
  charset = "UTF-8",
37
39
  viewport = "width=device-width, initial-scale=1.0",
40
+ image,
38
41
  robots,
39
42
  openGraph,
40
43
  twitter,
@@ -42,6 +45,32 @@ const {
42
45
  schema,
43
46
  languageAlternates,
44
47
  } = Astro.props
48
+
49
+ const enabled = <T,>(value: T | false | undefined): value is T => {
50
+ return value !== false && value !== undefined
51
+ }
52
+
53
+ const og =
54
+ openGraph !== false
55
+ ? {
56
+ title,
57
+ description: enabled(description) ? description : undefined,
58
+ image: enabled(image) ? image : undefined,
59
+ url: enabled(canonical) ? canonical : undefined,
60
+ ...openGraph,
61
+ }
62
+ : false
63
+
64
+ const tw =
65
+ twitter !== false
66
+ ? {
67
+ title,
68
+ description: enabled(description) ? description : undefined,
69
+ image: enabled(image) ? image : undefined,
70
+ url: enabled(canonical) ? canonical : undefined,
71
+ ...twitter,
72
+ }
73
+ : false
45
74
  ---
46
75
 
47
76
  <head>
@@ -51,53 +80,16 @@ const {
51
80
  <meta name="viewport" content={viewport} />
52
81
 
53
82
  <Title value={title} template={titleTemplate} />
54
- <Description value={description} />
55
- <Canonical value={canonical} />
56
- <Keywords value={keywords} />
57
- <Robots
58
- index={robots?.index}
59
- follow={robots?.follow}
60
- archive={robots?.archive}
61
- snippet={robots?.snippet}
62
- extra={robots?.extra}
63
- />
64
-
65
- {languageAlternates && <LanguageAlternates alternates={languageAlternates} />}
66
-
67
- {
68
- openGraph && (
69
- <OpenGraph
70
- title={openGraph.title ?? title}
71
- description={openGraph.description ?? description}
72
- url={openGraph.url ?? canonical}
73
- type={openGraph.type}
74
- siteName={openGraph.siteName}
75
- locale={openGraph.locale}
76
- localeAlternate={openGraph.localeAlternate}
77
- image={openGraph.image}
78
- video={openGraph.video}
79
- audio={openGraph.audio}
80
- />
81
- )
82
- }
83
-
84
- {
85
- twitter && (
86
- <Twitter
87
- title={twitter.title ?? title}
88
- description={twitter.description ?? description}
89
- image={twitter.image}
90
- card={twitter.card}
91
- site={twitter.site}
92
- creator={twitter.creator}
93
- url={twitter.url ?? canonical}
94
- />
95
- )
96
- }
97
-
98
- {favicon && <Favicon icons={favicon.icons} manifest={favicon.manifest} sort={favicon.sort} />}
99
83
 
100
- {schema && <Schema schema={schema.schema} />}
84
+ {enabled(description) && <Description value={description} />}
85
+ {enabled(canonical) && <Canonical value={canonical} />}
86
+ {enabled(keywords) && <Keywords value={keywords} />}
87
+ {enabled(robots) && <Robots {...robots} />}
88
+ {enabled(og) && <OpenGraph {...og} />}
89
+ {enabled(tw) && <Twitter {...tw} />}
90
+ {enabled(favicon) && <Favicon {...favicon} />}
91
+ {enabled(schema) && <Schema {...schema} />}
92
+ {enabled(languageAlternates) && <LanguageAlternates alternates={languageAlternates} />}
101
93
 
102
94
  <slot />
103
95
  </head>
@@ -52,6 +52,9 @@ export const Metadata = {
52
52
  get keywords() {
53
53
  return store.keywords
54
54
  },
55
+ get image() {
56
+ return store.image
57
+ },
55
58
  get robots() {
56
59
  return store.robots
57
60
  },