react-seo-optimize 1.0.0 → 2.0.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 +216 -18
- package/bin/generate-schema.js +118 -8
- package/package.json +12 -14
- package/src/SEOptimize.jsx +358 -39
- package/src/index.d.ts +317 -8
- package/src/index.js +12 -1
- package/src/schemaGenerators.js +218 -0
- package/src/utils/metaTags.js +62 -0
- package/src/utils/schemaValidation.js +34 -0
- package/src/utils/ssr.js +218 -0
package/src/utils/ssr.js
ADDED
|
@@ -0,0 +1,218 @@
|
|
|
1
|
+
export const renderSEOTags = (props) => {
|
|
2
|
+
const {
|
|
3
|
+
title,
|
|
4
|
+
description,
|
|
5
|
+
keywords,
|
|
6
|
+
canonical,
|
|
7
|
+
ogTitle,
|
|
8
|
+
ogDescription,
|
|
9
|
+
ogUrl,
|
|
10
|
+
ogImage,
|
|
11
|
+
ogType = 'website',
|
|
12
|
+
ogImageWidth,
|
|
13
|
+
ogImageHeight,
|
|
14
|
+
ogImageAlt,
|
|
15
|
+
ogImageSecureUrl,
|
|
16
|
+
ogSiteName,
|
|
17
|
+
ogLocale,
|
|
18
|
+
twitterCard = 'summary_large_image',
|
|
19
|
+
twitterTitle,
|
|
20
|
+
twitterDescription,
|
|
21
|
+
twitterImage,
|
|
22
|
+
twitterImageAlt,
|
|
23
|
+
twitterSite,
|
|
24
|
+
twitterCreator,
|
|
25
|
+
robots,
|
|
26
|
+
author,
|
|
27
|
+
htmlLang,
|
|
28
|
+
themeColor,
|
|
29
|
+
viewport,
|
|
30
|
+
charset = 'UTF-8',
|
|
31
|
+
articlePublishedTime,
|
|
32
|
+
articleModifiedTime,
|
|
33
|
+
articleAuthor,
|
|
34
|
+
articleSection,
|
|
35
|
+
articleTag,
|
|
36
|
+
schema,
|
|
37
|
+
structuredData,
|
|
38
|
+
customMeta = {},
|
|
39
|
+
} = props;
|
|
40
|
+
|
|
41
|
+
const finalOgTitle = ogTitle || title;
|
|
42
|
+
const finalOgDescription = ogDescription || description;
|
|
43
|
+
const finalOgUrl = ogUrl || canonical;
|
|
44
|
+
const finalTwitterTitle = twitterTitle || title;
|
|
45
|
+
const finalTwitterDescription = twitterDescription || description;
|
|
46
|
+
const finalTwitterImage = twitterImage || ogImage;
|
|
47
|
+
|
|
48
|
+
const tags = [];
|
|
49
|
+
|
|
50
|
+
if (htmlLang) {
|
|
51
|
+
tags.push(`<html lang="${escapeHtml(htmlLang)}">`);
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
if (charset) {
|
|
55
|
+
tags.push(`<meta charset="${escapeHtml(charset)}">`);
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
if (viewport) {
|
|
59
|
+
tags.push(`<meta name="viewport" content="${escapeHtml(viewport)}">`);
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
if (themeColor) {
|
|
63
|
+
tags.push(`<meta name="theme-color" content="${escapeHtml(themeColor)}">`);
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
if (title) {
|
|
67
|
+
tags.push(`<title>${escapeHtml(title)}</title>`);
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
if (description) {
|
|
71
|
+
tags.push(`<meta name="description" content="${escapeHtml(description)}">`);
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
if (keywords) {
|
|
75
|
+
tags.push(`<meta name="keywords" content="${escapeHtml(keywords)}">`);
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
if (canonical) {
|
|
79
|
+
tags.push(`<link rel="canonical" href="${escapeHtml(canonical)}">`);
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
if (robots) {
|
|
83
|
+
tags.push(`<meta name="robots" content="${escapeHtml(robots)}">`);
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
if (author) {
|
|
87
|
+
tags.push(`<meta name="author" content="${escapeHtml(author)}">`);
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
if (finalOgTitle) {
|
|
91
|
+
tags.push(`<meta property="og:title" content="${escapeHtml(finalOgTitle)}">`);
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
if (finalOgDescription) {
|
|
95
|
+
tags.push(`<meta property="og:description" content="${escapeHtml(finalOgDescription)}">`);
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
if (finalOgUrl) {
|
|
99
|
+
tags.push(`<meta property="og:url" content="${escapeHtml(finalOgUrl)}">`);
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
if (ogType) {
|
|
103
|
+
tags.push(`<meta property="og:type" content="${escapeHtml(ogType)}">`);
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
if (ogImage) {
|
|
107
|
+
tags.push(`<meta property="og:image" content="${escapeHtml(ogImage)}">`);
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
if (ogImage && (ogImage.startsWith('https://') || ogImageSecureUrl)) {
|
|
111
|
+
tags.push(`<meta property="og:image:secure_url" content="${escapeHtml(ogImageSecureUrl || ogImage)}">`);
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
if (ogImageWidth) {
|
|
115
|
+
tags.push(`<meta property="og:image:width" content="${escapeHtml(ogImageWidth)}">`);
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
if (ogImageHeight) {
|
|
119
|
+
tags.push(`<meta property="og:image:height" content="${escapeHtml(ogImageHeight)}">`);
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
if (ogImageAlt) {
|
|
123
|
+
tags.push(`<meta property="og:image:alt" content="${escapeHtml(ogImageAlt)}">`);
|
|
124
|
+
}
|
|
125
|
+
|
|
126
|
+
if (ogSiteName) {
|
|
127
|
+
tags.push(`<meta property="og:site_name" content="${escapeHtml(ogSiteName)}">`);
|
|
128
|
+
}
|
|
129
|
+
|
|
130
|
+
if (ogLocale) {
|
|
131
|
+
tags.push(`<meta property="og:locale" content="${escapeHtml(ogLocale)}">`);
|
|
132
|
+
}
|
|
133
|
+
|
|
134
|
+
if (ogType === 'article') {
|
|
135
|
+
if (articlePublishedTime) {
|
|
136
|
+
tags.push(`<meta property="article:published_time" content="${escapeHtml(articlePublishedTime)}">`);
|
|
137
|
+
}
|
|
138
|
+
if (articleModifiedTime) {
|
|
139
|
+
tags.push(`<meta property="article:modified_time" content="${escapeHtml(articleModifiedTime)}">`);
|
|
140
|
+
}
|
|
141
|
+
if (articleAuthor) {
|
|
142
|
+
tags.push(`<meta property="article:author" content="${escapeHtml(articleAuthor)}">`);
|
|
143
|
+
}
|
|
144
|
+
if (articleSection) {
|
|
145
|
+
tags.push(`<meta property="article:section" content="${escapeHtml(articleSection)}">`);
|
|
146
|
+
}
|
|
147
|
+
if (articleTag && Array.isArray(articleTag)) {
|
|
148
|
+
articleTag.forEach((tag) => {
|
|
149
|
+
tags.push(`<meta property="article:tag" content="${escapeHtml(tag)}">`);
|
|
150
|
+
});
|
|
151
|
+
}
|
|
152
|
+
}
|
|
153
|
+
|
|
154
|
+
if (twitterCard) {
|
|
155
|
+
tags.push(`<meta name="twitter:card" content="${escapeHtml(twitterCard)}">`);
|
|
156
|
+
}
|
|
157
|
+
|
|
158
|
+
if (twitterSite) {
|
|
159
|
+
tags.push(`<meta name="twitter:site" content="${escapeHtml(twitterSite)}">`);
|
|
160
|
+
}
|
|
161
|
+
|
|
162
|
+
if (twitterCreator) {
|
|
163
|
+
tags.push(`<meta name="twitter:creator" content="${escapeHtml(twitterCreator)}">`);
|
|
164
|
+
}
|
|
165
|
+
|
|
166
|
+
if (finalTwitterTitle) {
|
|
167
|
+
tags.push(`<meta name="twitter:title" content="${escapeHtml(finalTwitterTitle)}">`);
|
|
168
|
+
}
|
|
169
|
+
|
|
170
|
+
if (finalTwitterDescription) {
|
|
171
|
+
tags.push(`<meta name="twitter:description" content="${escapeHtml(finalTwitterDescription)}">`);
|
|
172
|
+
}
|
|
173
|
+
|
|
174
|
+
if (finalOgUrl) {
|
|
175
|
+
tags.push(`<meta name="twitter:url" content="${escapeHtml(finalOgUrl)}">`);
|
|
176
|
+
}
|
|
177
|
+
|
|
178
|
+
if (finalTwitterImage) {
|
|
179
|
+
tags.push(`<meta name="twitter:image" content="${escapeHtml(finalTwitterImage)}">`);
|
|
180
|
+
}
|
|
181
|
+
|
|
182
|
+
if (twitterImageAlt) {
|
|
183
|
+
tags.push(`<meta name="twitter:image:alt" content="${escapeHtml(twitterImageAlt)}">`);
|
|
184
|
+
}
|
|
185
|
+
|
|
186
|
+
const schemas = structuredData || schema;
|
|
187
|
+
if (schemas) {
|
|
188
|
+
try {
|
|
189
|
+
const schemaArray = Array.isArray(schemas) ? schemas : [schemas];
|
|
190
|
+
const mergedSchema = schemaArray.length === 1
|
|
191
|
+
? schemaArray[0]
|
|
192
|
+
: {
|
|
193
|
+
'@context': 'https://schema.org',
|
|
194
|
+
'@graph': schemaArray
|
|
195
|
+
};
|
|
196
|
+
const schemaJson = JSON.stringify(mergedSchema);
|
|
197
|
+
tags.push(`<script type="application/ld+json">${schemaJson}</script>`);
|
|
198
|
+
} catch (error) {
|
|
199
|
+
console.error('Failed to stringify schema:', error);
|
|
200
|
+
}
|
|
201
|
+
}
|
|
202
|
+
|
|
203
|
+
Object.entries(customMeta).forEach(([key, value]) => {
|
|
204
|
+
tags.push(`<meta name="${escapeHtml(key)}" content="${escapeHtml(value)}">`);
|
|
205
|
+
});
|
|
206
|
+
|
|
207
|
+
return tags.join('\n');
|
|
208
|
+
};
|
|
209
|
+
|
|
210
|
+
const escapeHtml = (str) => {
|
|
211
|
+
if (typeof str !== 'string') return '';
|
|
212
|
+
return str
|
|
213
|
+
.replace(/&/g, '&')
|
|
214
|
+
.replace(/</g, '<')
|
|
215
|
+
.replace(/>/g, '>')
|
|
216
|
+
.replace(/"/g, '"')
|
|
217
|
+
.replace(/'/g, ''');
|
|
218
|
+
};
|