@mannisto/astro-meta 0.0.1
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/LICENSE +21 -0
- package/README.md +124 -0
- package/package.json +19 -0
- package/src/components/Canonical.astro +13 -0
- package/src/components/Description.astro +13 -0
- package/src/components/Favicon.astro +155 -0
- package/src/components/Head.astro +84 -0
- package/src/components/Keywords.astro +13 -0
- package/src/components/OpenGraph.astro +31 -0
- package/src/components/Robots.astro +26 -0
- package/src/components/Title.astro +13 -0
- package/src/components/Twitter.astro +28 -0
package/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 Ere Männistö
|
|
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.
|
package/README.md
ADDED
|
@@ -0,0 +1,124 @@
|
|
|
1
|
+
# Astro Meta
|
|
2
|
+
|
|
3
|
+
Easy to use metadata components for Astro.
|
|
4
|
+
|
|
5
|
+
## Installation
|
|
6
|
+
```bash
|
|
7
|
+
pnpm add @mannisto/astro-meta
|
|
8
|
+
```
|
|
9
|
+
|
|
10
|
+
## Usage
|
|
11
|
+
|
|
12
|
+
Use the `Head` component in your layout:
|
|
13
|
+
```astro
|
|
14
|
+
---
|
|
15
|
+
import { Head } from "@mannisto/astro-meta"
|
|
16
|
+
---
|
|
17
|
+
|
|
18
|
+
<html>
|
|
19
|
+
<Head
|
|
20
|
+
title="My Site"
|
|
21
|
+
description="Welcome to my site"
|
|
22
|
+
canonical="https://example.com"
|
|
23
|
+
robots={{ index: true, follow: true }}
|
|
24
|
+
og={{
|
|
25
|
+
title: "My Site",
|
|
26
|
+
image: "/og.jpg",
|
|
27
|
+
siteName: "My Site",
|
|
28
|
+
locale: "en_US",
|
|
29
|
+
}}
|
|
30
|
+
twitter={{
|
|
31
|
+
card: "summary_large_image",
|
|
32
|
+
site: "@mysite",
|
|
33
|
+
}}
|
|
34
|
+
favicon={{
|
|
35
|
+
icons: {
|
|
36
|
+
default: {
|
|
37
|
+
ico: { path: "/favicon.ico" },
|
|
38
|
+
svg: { path: "/favicon.svg" },
|
|
39
|
+
png: [{ path: "/favicon-96x96.png", size: 96 }],
|
|
40
|
+
apple: { path: "/apple-touch-icon.png", size: 180 },
|
|
41
|
+
},
|
|
42
|
+
lightMode: {
|
|
43
|
+
svg: { path: "/favicon-light.svg" },
|
|
44
|
+
},
|
|
45
|
+
darkMode: {
|
|
46
|
+
svg: { path: "/favicon-dark.svg" },
|
|
47
|
+
},
|
|
48
|
+
},
|
|
49
|
+
manifest: "/site.webmanifest",
|
|
50
|
+
cacheBust: true,
|
|
51
|
+
}}
|
|
52
|
+
/>
|
|
53
|
+
<body>
|
|
54
|
+
...
|
|
55
|
+
</body>
|
|
56
|
+
</html>
|
|
57
|
+
```
|
|
58
|
+
|
|
59
|
+
## Props
|
|
60
|
+
|
|
61
|
+
| Prop | Type | Required | Description |
|
|
62
|
+
|------|------|----------|-------------|
|
|
63
|
+
| `title` | `string` | Yes | Page title |
|
|
64
|
+
| `description` | `string` | No | Page description |
|
|
65
|
+
| `canonical` | `string` | No | Canonical URL |
|
|
66
|
+
| `keywords` | `string[]` | No | List of keywords |
|
|
67
|
+
| `robots.index` | `boolean` | No | Allow indexing, defaults to `true` |
|
|
68
|
+
| `robots.follow` | `boolean` | No | Allow following links, defaults to `true` |
|
|
69
|
+
| `robots.noArchive` | `boolean` | No | Prevent caching |
|
|
70
|
+
| `robots.noSnippet` | `boolean` | No | Prevent text snippets in search results |
|
|
71
|
+
| `og.title` | `string` | No | Defaults to `title` |
|
|
72
|
+
| `og.description` | `string` | No | Defaults to `description` |
|
|
73
|
+
| `og.image` | `string` | No | Open Graph image URL |
|
|
74
|
+
| `og.url` | `string` | No | Defaults to `canonical` |
|
|
75
|
+
| `og.type` | `string` | No | Defaults to `website` |
|
|
76
|
+
| `og.siteName` | `string` | No | Site name |
|
|
77
|
+
| `og.locale` | `string` | No | Locale, e.g. `en_US` |
|
|
78
|
+
| `twitter.title` | `string` | No | Defaults to `title` |
|
|
79
|
+
| `twitter.description` | `string` | No | Defaults to `description` |
|
|
80
|
+
| `twitter.image` | `string` | No | Twitter card image URL |
|
|
81
|
+
| `twitter.card` | `"summary" \| "summary_large_image"` | No | Defaults to `summary_large_image` |
|
|
82
|
+
| `twitter.site` | `string` | No | Twitter handle of the site, e.g. `@mysite` |
|
|
83
|
+
| `twitter.creator` | `string` | No | Twitter handle of the author |
|
|
84
|
+
| `favicon.icons.default` | `FaviconSet` | No | Default favicon set |
|
|
85
|
+
| `favicon.icons.lightMode` | `FaviconSet` | No | Favicons for light mode |
|
|
86
|
+
| `favicon.icons.darkMode` | `FaviconSet` | No | Favicons for dark mode |
|
|
87
|
+
| `favicon.manifest` | `string` | No | Path to web manifest |
|
|
88
|
+
| `favicon.cacheBust` | `boolean` | No | Append timestamp to favicon URLs to prevent caching |
|
|
89
|
+
|
|
90
|
+
### FaviconSet
|
|
91
|
+
|
|
92
|
+
| Prop | Type | Description |
|
|
93
|
+
|------|------|-------------|
|
|
94
|
+
| `ico` | `{ path: string, size?: number }` | `.ico` favicon |
|
|
95
|
+
| `svg` | `{ path: string, size?: number }` | `.svg` favicon |
|
|
96
|
+
| `png` | `{ path: string, size?: number } \| { path: string, size?: number }[]` | `.png` favicon(s) |
|
|
97
|
+
| `apple` | `{ path: string, size?: number }` | Apple touch icon |
|
|
98
|
+
|
|
99
|
+
### Slots
|
|
100
|
+
|
|
101
|
+
Two slots are available for injecting additional tags:
|
|
102
|
+
```astro
|
|
103
|
+
<Head title="My Site">
|
|
104
|
+
<meta slot="top" http-equiv="X-UA-Compatible" content="IE=edge" />
|
|
105
|
+
<script src={fontAwesomeUrl} />
|
|
106
|
+
</Head>
|
|
107
|
+
```
|
|
108
|
+
|
|
109
|
+
`slot="top"` renders before charset and viewport. The default slot renders at the end.
|
|
110
|
+
|
|
111
|
+
### Individual components
|
|
112
|
+
|
|
113
|
+
All components are available as named exports:
|
|
114
|
+
```astro
|
|
115
|
+
---
|
|
116
|
+
import { OpenGraph, Twitter } from "@mannisto/astro-meta"
|
|
117
|
+
---
|
|
118
|
+
```
|
|
119
|
+
|
|
120
|
+
## License
|
|
121
|
+
|
|
122
|
+
MIT License
|
|
123
|
+
|
|
124
|
+
Copyright (c) 2026 Ere Männistö
|
package/package.json
ADDED
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@mannisto/astro-meta",
|
|
3
|
+
"version": "0.0.1",
|
|
4
|
+
"type": "module",
|
|
5
|
+
"description": "Reusable metadata components for Astro",
|
|
6
|
+
"license": "MIT",
|
|
7
|
+
"files": ["src"],
|
|
8
|
+
"exports": {
|
|
9
|
+
".": "./src/index.ts"
|
|
10
|
+
},
|
|
11
|
+
"peerDependencies": {
|
|
12
|
+
"astro": "^5.0.0"
|
|
13
|
+
},
|
|
14
|
+
"devDependencies": {
|
|
15
|
+
"astro": "^5.17.2",
|
|
16
|
+
"typescript": "^5.9.3"
|
|
17
|
+
},
|
|
18
|
+
"keywords": ["astro", "meta", "seo", "head"]
|
|
19
|
+
}
|
|
@@ -0,0 +1,155 @@
|
|
|
1
|
+
---
|
|
2
|
+
type Icon = {
|
|
3
|
+
path: string
|
|
4
|
+
size?: number
|
|
5
|
+
}
|
|
6
|
+
|
|
7
|
+
type FaviconSet = {
|
|
8
|
+
ico?: Icon
|
|
9
|
+
png?: Icon | Icon[]
|
|
10
|
+
svg?: Icon
|
|
11
|
+
apple?: Icon
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
interface Props {
|
|
15
|
+
icons: {
|
|
16
|
+
default?: FaviconSet
|
|
17
|
+
lightMode?: FaviconSet
|
|
18
|
+
darkMode?: FaviconSet
|
|
19
|
+
}
|
|
20
|
+
manifest?: string
|
|
21
|
+
cacheBust?: boolean
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
const { icons, manifest, cacheBust } = Astro.props
|
|
25
|
+
|
|
26
|
+
const bust = cacheBust ? `?v=${Date.now()}` : ""
|
|
27
|
+
|
|
28
|
+
type PreparedIcon = {
|
|
29
|
+
path: string
|
|
30
|
+
size?: string
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
type PreparedFaviconSet = {
|
|
34
|
+
ico?: PreparedIcon
|
|
35
|
+
png?: PreparedIcon[]
|
|
36
|
+
svg?: PreparedIcon
|
|
37
|
+
apple?: PreparedIcon
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
function prepareIcon(icon?: Icon): PreparedIcon | undefined {
|
|
41
|
+
if (!icon) return undefined
|
|
42
|
+
return {
|
|
43
|
+
path: `${icon.path}${bust}`,
|
|
44
|
+
size: icon.size ? `${icon.size}x${icon.size}` : undefined,
|
|
45
|
+
}
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
function prepareFaviconSet(set?: FaviconSet): PreparedFaviconSet | undefined {
|
|
49
|
+
if (!set) return undefined
|
|
50
|
+
return {
|
|
51
|
+
ico: prepareIcon(set.ico),
|
|
52
|
+
svg: prepareIcon(set.svg),
|
|
53
|
+
apple: prepareIcon(set.apple),
|
|
54
|
+
png: set.png
|
|
55
|
+
? (Array.isArray(set.png) ? set.png : [set.png]).map((icon) => prepareIcon(icon)!)
|
|
56
|
+
: undefined,
|
|
57
|
+
}
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
const prepared = {
|
|
61
|
+
default: prepareFaviconSet(icons.default),
|
|
62
|
+
lightMode: prepareFaviconSet(icons.lightMode),
|
|
63
|
+
darkMode: prepareFaviconSet(icons.darkMode),
|
|
64
|
+
}
|
|
65
|
+
---
|
|
66
|
+
|
|
67
|
+
{manifest && <link rel="manifest" href={manifest} />}
|
|
68
|
+
|
|
69
|
+
{prepared.default?.ico && (
|
|
70
|
+
<link
|
|
71
|
+
rel="icon"
|
|
72
|
+
type="image/x-icon"
|
|
73
|
+
href={prepared.default.ico.path}
|
|
74
|
+
sizes={prepared.default.ico.size}
|
|
75
|
+
/>
|
|
76
|
+
)}
|
|
77
|
+
{prepared.default?.svg && (
|
|
78
|
+
<link
|
|
79
|
+
rel="icon"
|
|
80
|
+
type="image/svg+xml"
|
|
81
|
+
href={prepared.default.svg.path}
|
|
82
|
+
sizes={prepared.default.svg.size}
|
|
83
|
+
/>
|
|
84
|
+
)}
|
|
85
|
+
{prepared.default?.apple && (
|
|
86
|
+
<link
|
|
87
|
+
rel="apple-touch-icon"
|
|
88
|
+
href={prepared.default.apple.path}
|
|
89
|
+
sizes={prepared.default.apple.size}
|
|
90
|
+
/>
|
|
91
|
+
)}
|
|
92
|
+
{prepared.default?.png?.map((png) => (
|
|
93
|
+
<link
|
|
94
|
+
rel="icon"
|
|
95
|
+
type="image/png"
|
|
96
|
+
href={png.path}
|
|
97
|
+
sizes={png.size}
|
|
98
|
+
/>
|
|
99
|
+
))}
|
|
100
|
+
|
|
101
|
+
{prepared.lightMode?.ico && (
|
|
102
|
+
<link
|
|
103
|
+
rel="icon"
|
|
104
|
+
type="image/x-icon"
|
|
105
|
+
href={prepared.lightMode.ico.path}
|
|
106
|
+
sizes={prepared.lightMode.ico.size}
|
|
107
|
+
media="(prefers-color-scheme: light)"
|
|
108
|
+
/>
|
|
109
|
+
)}
|
|
110
|
+
{prepared.lightMode?.svg && (
|
|
111
|
+
<link
|
|
112
|
+
rel="icon"
|
|
113
|
+
type="image/svg+xml"
|
|
114
|
+
href={prepared.lightMode.svg.path}
|
|
115
|
+
sizes={prepared.lightMode.svg.size}
|
|
116
|
+
media="(prefers-color-scheme: light)"
|
|
117
|
+
/>
|
|
118
|
+
)}
|
|
119
|
+
{prepared.lightMode?.png?.map((png) => (
|
|
120
|
+
<link
|
|
121
|
+
rel="icon"
|
|
122
|
+
type="image/png"
|
|
123
|
+
href={png.path}
|
|
124
|
+
sizes={png.size}
|
|
125
|
+
media="(prefers-color-scheme: light)"
|
|
126
|
+
/>
|
|
127
|
+
))}
|
|
128
|
+
|
|
129
|
+
{prepared.darkMode?.ico && (
|
|
130
|
+
<link
|
|
131
|
+
rel="icon"
|
|
132
|
+
type="image/x-icon"
|
|
133
|
+
href={prepared.darkMode.ico.path}
|
|
134
|
+
sizes={prepared.darkMode.ico.size}
|
|
135
|
+
media="(prefers-color-scheme: dark)"
|
|
136
|
+
/>
|
|
137
|
+
)}
|
|
138
|
+
{prepared.darkMode?.svg && (
|
|
139
|
+
<link
|
|
140
|
+
rel="icon"
|
|
141
|
+
type="image/svg+xml"
|
|
142
|
+
href={prepared.darkMode.svg.path}
|
|
143
|
+
sizes={prepared.darkMode.svg.size}
|
|
144
|
+
media="(prefers-color-scheme: dark)"
|
|
145
|
+
/>
|
|
146
|
+
)}
|
|
147
|
+
{prepared.darkMode?.png?.map((png) => (
|
|
148
|
+
<link
|
|
149
|
+
rel="icon"
|
|
150
|
+
type="image/png"
|
|
151
|
+
href={png.path}
|
|
152
|
+
sizes={png.size}
|
|
153
|
+
media="(prefers-color-scheme: dark)"
|
|
154
|
+
/>
|
|
155
|
+
))}
|
|
@@ -0,0 +1,84 @@
|
|
|
1
|
+
---
|
|
2
|
+
import type { ComponentProps } from "astro/types"
|
|
3
|
+
import Title from "./Title.astro"
|
|
4
|
+
import Description from "./Description.astro"
|
|
5
|
+
import Canonical from "./Canonical.astro"
|
|
6
|
+
import Keywords from "./Keywords.astro"
|
|
7
|
+
import Robots from "./Robots.astro"
|
|
8
|
+
import OpenGraph from "./OpenGraph.astro"
|
|
9
|
+
import Twitter from "./Twitter.astro"
|
|
10
|
+
import Favicon from "./Favicon.astro"
|
|
11
|
+
|
|
12
|
+
interface Props {
|
|
13
|
+
title: string
|
|
14
|
+
description?: string
|
|
15
|
+
canonical?: string
|
|
16
|
+
keywords?: string[]
|
|
17
|
+
robots?: ComponentProps<typeof Robots>
|
|
18
|
+
og?: ComponentProps<typeof OpenGraph>
|
|
19
|
+
twitter?: ComponentProps<typeof Twitter>
|
|
20
|
+
favicon?: ComponentProps<typeof Favicon>
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
const {
|
|
24
|
+
title,
|
|
25
|
+
description,
|
|
26
|
+
canonical,
|
|
27
|
+
keywords,
|
|
28
|
+
robots = { index: true, follow: true },
|
|
29
|
+
og,
|
|
30
|
+
twitter,
|
|
31
|
+
favicon,
|
|
32
|
+
} = Astro.props
|
|
33
|
+
---
|
|
34
|
+
|
|
35
|
+
<head>
|
|
36
|
+
<slot name="top" />
|
|
37
|
+
|
|
38
|
+
<meta charset="UTF-8" />
|
|
39
|
+
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
|
40
|
+
|
|
41
|
+
<Title value={title} />
|
|
42
|
+
<Description value={description} />
|
|
43
|
+
<Canonical value={canonical} />
|
|
44
|
+
<Keywords value={keywords} />
|
|
45
|
+
<Robots
|
|
46
|
+
index={robots.index}
|
|
47
|
+
follow={robots.follow}
|
|
48
|
+
noArchive={robots.noArchive}
|
|
49
|
+
noSnippet={robots.noSnippet}
|
|
50
|
+
/>
|
|
51
|
+
|
|
52
|
+
{og && (
|
|
53
|
+
<OpenGraph
|
|
54
|
+
title={og.title ?? title}
|
|
55
|
+
description={og.description ?? description}
|
|
56
|
+
image={og.image}
|
|
57
|
+
url={og.url ?? canonical}
|
|
58
|
+
type={og.type}
|
|
59
|
+
siteName={og.siteName}
|
|
60
|
+
locale={og.locale}
|
|
61
|
+
/>
|
|
62
|
+
)}
|
|
63
|
+
|
|
64
|
+
{twitter && (
|
|
65
|
+
<Twitter
|
|
66
|
+
title={twitter.title ?? title}
|
|
67
|
+
description={twitter.description ?? description}
|
|
68
|
+
image={twitter.image}
|
|
69
|
+
card={twitter.card}
|
|
70
|
+
site={twitter.site}
|
|
71
|
+
creator={twitter.creator}
|
|
72
|
+
/>
|
|
73
|
+
)}
|
|
74
|
+
|
|
75
|
+
{favicon && (
|
|
76
|
+
<Favicon
|
|
77
|
+
icons={favicon.icons}
|
|
78
|
+
manifest={favicon.manifest}
|
|
79
|
+
cacheBust={favicon.cacheBust}
|
|
80
|
+
/>
|
|
81
|
+
)}
|
|
82
|
+
|
|
83
|
+
<slot />
|
|
84
|
+
</head>
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
---
|
|
2
|
+
|
|
3
|
+
interface Props {
|
|
4
|
+
title?: string
|
|
5
|
+
description?: string
|
|
6
|
+
image?: string
|
|
7
|
+
url?: string
|
|
8
|
+
type?: string
|
|
9
|
+
siteName?: string
|
|
10
|
+
locale?: string
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
const {
|
|
14
|
+
title,
|
|
15
|
+
description,
|
|
16
|
+
image,
|
|
17
|
+
url,
|
|
18
|
+
type = "website",
|
|
19
|
+
siteName,
|
|
20
|
+
locale
|
|
21
|
+
} = Astro.props
|
|
22
|
+
|
|
23
|
+
---
|
|
24
|
+
|
|
25
|
+
{title && <meta property="og:title" content={title} />}
|
|
26
|
+
{description && <meta property="og:description" content={description} />}
|
|
27
|
+
{image && <meta property="og:image" content={image} />}
|
|
28
|
+
{url && <meta property="og:url" content={url} />}
|
|
29
|
+
{type && <meta property="og:type" content={type} />}
|
|
30
|
+
{siteName && <meta property="og:site_name" content={siteName} />}
|
|
31
|
+
{locale && <meta property="og:locale" content={locale} />}
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
---
|
|
2
|
+
|
|
3
|
+
interface Props {
|
|
4
|
+
index: boolean
|
|
5
|
+
follow: boolean
|
|
6
|
+
noArchive?: boolean
|
|
7
|
+
noSnippet?: boolean
|
|
8
|
+
}
|
|
9
|
+
|
|
10
|
+
const {
|
|
11
|
+
index,
|
|
12
|
+
follow,
|
|
13
|
+
noArchive,
|
|
14
|
+
noSnippet
|
|
15
|
+
} = Astro.props
|
|
16
|
+
|
|
17
|
+
const directives = [
|
|
18
|
+
index ? "index" : "noindex",
|
|
19
|
+
follow ? "follow" : "nofollow",
|
|
20
|
+
noArchive && "noarchive",
|
|
21
|
+
noSnippet && "nosnippet",
|
|
22
|
+
].filter(Boolean).join(", ")
|
|
23
|
+
|
|
24
|
+
---
|
|
25
|
+
|
|
26
|
+
<meta name="robots" content={directives} />
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
---
|
|
2
|
+
|
|
3
|
+
interface Props {
|
|
4
|
+
title?: string
|
|
5
|
+
description?: string
|
|
6
|
+
image?: string
|
|
7
|
+
card?: "summary" | "summary_large_image"
|
|
8
|
+
site?: string
|
|
9
|
+
creator?: string
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
const {
|
|
13
|
+
title,
|
|
14
|
+
description,
|
|
15
|
+
image,
|
|
16
|
+
card = "summary_large_image",
|
|
17
|
+
site,
|
|
18
|
+
creator
|
|
19
|
+
} = Astro.props
|
|
20
|
+
|
|
21
|
+
---
|
|
22
|
+
|
|
23
|
+
{card && <meta name="twitter:card" content={card} />}
|
|
24
|
+
{title && <meta name="twitter:title" content={title} />}
|
|
25
|
+
{description && <meta name="twitter:description" content={description} />}
|
|
26
|
+
{image && <meta name="twitter:image" content={image} />}
|
|
27
|
+
{site && <meta name="twitter:site" content={site} />}
|
|
28
|
+
{creator && <meta name="twitter:creator" content={creator} />}
|