react-ssr-seo-toolkit 1.1.0 → 1.2.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 +592 -512
- package/dist/adapters/nextjs.d.cts +1 -1
- package/dist/adapters/nextjs.d.ts +1 -1
- package/dist/adapters/react-router.d.cts +1 -1
- package/dist/adapters/react-router.d.ts +1 -1
- package/dist/chunk-3UCLUFOI.js +5 -0
- package/dist/chunk-3UCLUFOI.js.map +1 -0
- package/dist/chunk-A62THBIS.cjs +3 -0
- package/dist/chunk-A62THBIS.cjs.map +1 -0
- package/dist/chunk-GNLXGAS6.js +3 -0
- package/dist/chunk-GNLXGAS6.js.map +1 -0
- package/dist/chunk-LEOORZQY.cjs +5 -0
- package/dist/chunk-LEOORZQY.cjs.map +1 -0
- package/dist/chunk-ROA74LH6.cjs +61 -0
- package/dist/chunk-ROA74LH6.cjs.map +1 -0
- package/dist/chunk-WM5VVPED.js +61 -0
- package/dist/chunk-WM5VVPED.js.map +1 -0
- package/dist/chunk-X535MU7Z.js +5 -0
- package/dist/chunk-X535MU7Z.js.map +1 -0
- package/dist/chunk-ZADUJHVR.cjs +5 -0
- package/dist/chunk-ZADUJHVR.cjs.map +1 -0
- package/dist/chunk-ZECSTNEE.cjs +3 -0
- package/dist/chunk-ZECSTNEE.cjs.map +1 -0
- package/dist/chunk-ZVFEGG25.js +3 -0
- package/dist/chunk-ZVFEGG25.js.map +1 -0
- package/dist/components.cjs +1 -1
- package/dist/components.d.cts +2 -2
- package/dist/components.d.ts +2 -2
- package/dist/components.js +1 -1
- package/dist/index-Br7Sh9Ur.d.cts +329 -0
- package/dist/index-Br7Sh9Ur.d.ts +329 -0
- package/dist/{index-DAGfo2Fc.d.ts → index-CKNcgAj8.d.ts} +10 -2
- package/dist/{index-RBSUcdqN.d.cts → index-Wgogf4CX.d.cts} +10 -2
- package/dist/index.cjs +1 -1
- package/dist/index.d.cts +7 -4
- package/dist/index.d.ts +7 -4
- package/dist/index.js +1 -1
- package/dist/og.cjs +2 -0
- package/dist/og.cjs.map +1 -0
- package/dist/og.d.cts +5 -0
- package/dist/og.d.ts +5 -0
- package/dist/og.js +2 -0
- package/dist/og.js.map +1 -0
- package/dist/schema.cjs +1 -1
- package/dist/schema.d.cts +6 -2
- package/dist/schema.d.ts +6 -2
- package/dist/schema.js +1 -1
- package/dist/sitemap.cjs +2 -0
- package/dist/sitemap.cjs.map +1 -0
- package/dist/sitemap.d.cts +12 -0
- package/dist/sitemap.d.ts +12 -0
- package/dist/sitemap.js +2 -0
- package/dist/sitemap.js.map +1 -0
- package/dist/validation.cjs +2 -0
- package/dist/validation.cjs.map +1 -0
- package/dist/validation.d.cts +8 -0
- package/dist/validation.d.ts +8 -0
- package/dist/validation.js +2 -0
- package/dist/validation.js.map +1 -0
- package/package.json +38 -2
- package/dist/chunk-63ETSZTD.cjs +0 -3
- package/dist/chunk-63ETSZTD.cjs.map +0 -1
- package/dist/chunk-AYIAPQTP.js +0 -2
- package/dist/chunk-AYIAPQTP.js.map +0 -1
- package/dist/chunk-ES4OXVOR.js +0 -3
- package/dist/chunk-ES4OXVOR.js.map +0 -1
- package/dist/chunk-T7EB7Y2S.cjs +0 -2
- package/dist/chunk-T7EB7Y2S.cjs.map +0 -1
- package/dist/index-Dr2yktvz.d.cts +0 -136
- package/dist/index-Dr2yktvz.d.ts +0 -136
package/README.md
CHANGED
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
|
|
3
3
|
<br />
|
|
4
4
|
|
|
5
|
-
<img src="https://img.shields.io/badge/react--ssr--seo--toolkit-v1.0
|
|
5
|
+
<img src="https://img.shields.io/badge/react--ssr--seo--toolkit-v1.2.0-000000?style=for-the-badge&labelColor=000000" alt="react-ssr-seo-toolkit" />
|
|
6
6
|
|
|
7
7
|
<br />
|
|
8
8
|
<br />
|
|
@@ -23,7 +23,7 @@
|
|
|
23
23
|
|
|
24
24
|
<br />
|
|
25
25
|
|
|
26
|
-
**Meta Tags** • **Open Graph** • **Twitter Cards** • **JSON-LD** • **Canonical URLs** • **Hreflang** • **Robots**
|
|
26
|
+
**Meta Tags** • **Open Graph** • **Twitter Cards** • **JSON-LD** • **Canonical URLs** • **Hreflang** • **Robots** • **Sitemap** • **SEO Validation** • **OG Image**
|
|
27
27
|
|
|
28
28
|
All in one package. Zero dependencies. Fully typed. SSR-safe.
|
|
29
29
|
|
|
@@ -50,8 +50,8 @@ All in one package. Zero dependencies. Fully typed. SSR-safe.
|
|
|
50
50
|
- Most SEO packages are **locked to Next.js**
|
|
51
51
|
- Many rely on **browser-only APIs** (`window`, `document`)
|
|
52
52
|
- JSON-LD usually needs a **separate package**
|
|
53
|
-
-
|
|
54
|
-
-
|
|
53
|
+
- No sitemap or robots.txt generation
|
|
54
|
+
- No way to validate SEO completeness at build time
|
|
55
55
|
|
|
56
56
|
</td>
|
|
57
57
|
<td width="50%">
|
|
@@ -60,9 +60,11 @@ All in one package. Zero dependencies. Fully typed. SSR-safe.
|
|
|
60
60
|
|
|
61
61
|
- **Framework-agnostic** — works everywhere
|
|
62
62
|
- **Zero browser globals** — fully SSR-safe
|
|
63
|
-
- **JSON-LD built-in** —
|
|
64
|
-
- **
|
|
65
|
-
- **
|
|
63
|
+
- **JSON-LD built-in** — 9 schema types including Person, Recipe, JobPosting
|
|
64
|
+
- **Sitemap + robots.txt** generator built-in
|
|
65
|
+
- **SEO validation + scoring** — catch missing tags at build time
|
|
66
|
+
- **OG image SVG generator** — no external service needed
|
|
67
|
+
- **Social preview component** — see how your page looks on Twitter, Facebook, etc.
|
|
66
68
|
|
|
67
69
|
</td>
|
|
68
70
|
</tr>
|
|
@@ -124,14 +126,12 @@ my-app/
|
|
|
124
126
|
|
|
125
127
|
### 3. Create Site Config (once)
|
|
126
128
|
|
|
127
|
-
This file holds defaults that every page inherits. Pages override only what they need.
|
|
128
|
-
|
|
129
129
|
```tsx
|
|
130
130
|
// config/seo.ts
|
|
131
131
|
import { createSEOConfig } from "react-ssr-seo-toolkit";
|
|
132
132
|
|
|
133
133
|
export const siteConfig = createSEOConfig({
|
|
134
|
-
titleTemplate: "%s | MySite",
|
|
134
|
+
titleTemplate: "%s | MySite",
|
|
135
135
|
description: "Default site description for SEO.",
|
|
136
136
|
openGraph: { siteName: "MySite", type: "website", locale: "en_US" },
|
|
137
137
|
twitter: { card: "summary_large_image", site: "@mysite" },
|
|
@@ -140,14 +140,10 @@ export const siteConfig = createSEOConfig({
|
|
|
140
140
|
export const SITE_URL = "https://mysite.com";
|
|
141
141
|
```
|
|
142
142
|
|
|
143
|
-
> **Tip:** `titleTemplate` uses `%s` as a placeholder. Setting `title: "About"` renders as `About | MySite`.
|
|
144
|
-
|
|
145
143
|
<br />
|
|
146
144
|
|
|
147
145
|
### 3.5. Create a Document Component
|
|
148
146
|
|
|
149
|
-
The Document handles `<html>`, `<head>`, `<SEOHead>`, and `<body>` — so pages never have to.
|
|
150
|
-
|
|
151
147
|
```tsx
|
|
152
148
|
// components/Document.tsx
|
|
153
149
|
import { SEOHead, JsonLd } from "react-ssr-seo-toolkit";
|
|
@@ -179,14 +175,10 @@ export function Document({ children, seo, schemas }: DocumentProps) {
|
|
|
179
175
|
}
|
|
180
176
|
```
|
|
181
177
|
|
|
182
|
-
> This is the same pattern used by Next.js (`layout.tsx`), Remix (`root.tsx`), and React Router's root component. We call it `Document` to distinguish it from route-level layouts.
|
|
183
|
-
|
|
184
178
|
<br />
|
|
185
179
|
|
|
186
180
|
### 4. Add to Any Page
|
|
187
181
|
|
|
188
|
-
Merge the shared config with page-specific values. **No `<html>` or `<head>` tags needed** — the Document handles that.
|
|
189
|
-
|
|
190
182
|
```tsx
|
|
191
183
|
// pages/AboutPage.tsx
|
|
192
184
|
import { mergeSEOConfig, buildCanonicalUrl } from "react-ssr-seo-toolkit";
|
|
@@ -209,7 +201,7 @@ export function AboutPage() {
|
|
|
209
201
|
}
|
|
210
202
|
```
|
|
211
203
|
|
|
212
|
-
**That's it.**
|
|
204
|
+
**That's it.** Keep reading for sitemap generation, SEO validation, OG images, and more.
|
|
213
205
|
|
|
214
206
|
<br />
|
|
215
207
|
|
|
@@ -219,14 +211,316 @@ export function AboutPage() {
|
|
|
219
211
|
|
|
220
212
|
## Real-World Examples
|
|
221
213
|
|
|
222
|
-
|
|
214
|
+
<br />
|
|
215
|
+
|
|
216
|
+
### Sitemap Generation
|
|
217
|
+
|
|
218
|
+
```ts
|
|
219
|
+
// scripts/generate-sitemap.ts
|
|
220
|
+
import { generateSitemap } from "react-ssr-seo-toolkit/sitemap";
|
|
221
|
+
import { writeFileSync } from "fs";
|
|
222
|
+
|
|
223
|
+
const sitemap = generateSitemap({
|
|
224
|
+
baseUrl: "https://trustix.uk",
|
|
225
|
+
routes: [
|
|
226
|
+
{ path: "/", priority: 1.0, changefreq: "daily" },
|
|
227
|
+
{ path: "/tickets", priority: 0.9, changefreq: "hourly" },
|
|
228
|
+
{ path: "/about", priority: 0.5, changefreq: "monthly" },
|
|
229
|
+
"/blog",
|
|
230
|
+
"/contact",
|
|
231
|
+
],
|
|
232
|
+
exclude: ["/dashboard/*", "/admin/*", "/login"],
|
|
233
|
+
defaultChangefreq: "weekly",
|
|
234
|
+
defaultPriority: 0.7,
|
|
235
|
+
});
|
|
236
|
+
|
|
237
|
+
writeFileSync("public/sitemap.xml", sitemap);
|
|
238
|
+
```
|
|
239
|
+
|
|
240
|
+
**Output:**
|
|
241
|
+
|
|
242
|
+
```xml
|
|
243
|
+
<?xml version="1.0" encoding="UTF-8"?>
|
|
244
|
+
<urlset xmlns="http://www.sitemaps.org/schemas/sitemap/0.9">
|
|
245
|
+
<url>
|
|
246
|
+
<loc>https://trustix.uk</loc>
|
|
247
|
+
<lastmod>2026-05-08</lastmod>
|
|
248
|
+
<changefreq>daily</changefreq>
|
|
249
|
+
<priority>1.0</priority>
|
|
250
|
+
</url>
|
|
251
|
+
...
|
|
252
|
+
</urlset>
|
|
253
|
+
```
|
|
254
|
+
|
|
255
|
+
> **Tip:** Call this in a build script or as an Express endpoint: `res.type("application/xml").send(sitemap)`.
|
|
256
|
+
|
|
257
|
+
<br />
|
|
258
|
+
|
|
259
|
+
---
|
|
260
|
+
|
|
261
|
+
<br />
|
|
262
|
+
|
|
263
|
+
### robots.txt Generation
|
|
264
|
+
|
|
265
|
+
```ts
|
|
266
|
+
import { generateRobots } from "react-ssr-seo-toolkit/sitemap";
|
|
267
|
+
|
|
268
|
+
const robots = generateRobots({
|
|
269
|
+
rules: [
|
|
270
|
+
{
|
|
271
|
+
userAgent: "*",
|
|
272
|
+
allow: "/",
|
|
273
|
+
disallow: ["/dashboard", "/admin", "/login"],
|
|
274
|
+
},
|
|
275
|
+
{
|
|
276
|
+
userAgent: "Googlebot",
|
|
277
|
+
allow: "/",
|
|
278
|
+
crawlDelay: 2,
|
|
279
|
+
},
|
|
280
|
+
],
|
|
281
|
+
sitemap: "https://trustix.uk/sitemap.xml",
|
|
282
|
+
});
|
|
283
|
+
```
|
|
284
|
+
|
|
285
|
+
**Output:**
|
|
286
|
+
|
|
287
|
+
```
|
|
288
|
+
User-agent: *
|
|
289
|
+
Allow: /
|
|
290
|
+
Disallow: /dashboard
|
|
291
|
+
Disallow: /admin
|
|
292
|
+
Disallow: /login
|
|
293
|
+
|
|
294
|
+
User-agent: Googlebot
|
|
295
|
+
Allow: /
|
|
296
|
+
Crawl-delay: 2
|
|
297
|
+
|
|
298
|
+
Sitemap: https://trustix.uk/sitemap.xml
|
|
299
|
+
```
|
|
300
|
+
|
|
301
|
+
<br />
|
|
302
|
+
|
|
303
|
+
---
|
|
304
|
+
|
|
305
|
+
<br />
|
|
306
|
+
|
|
307
|
+
### Breadcrumb Auto-Generation from URL
|
|
308
|
+
|
|
309
|
+
```ts
|
|
310
|
+
import { autoBreadcrumb } from "react-ssr-seo-toolkit/sitemap";
|
|
311
|
+
import { createBreadcrumbSchema } from "react-ssr-seo-toolkit";
|
|
312
|
+
|
|
313
|
+
// /ticket/liverpool-vs-arsenal → breadcrumb items automatically
|
|
314
|
+
const items = autoBreadcrumb("/ticket/liverpool-vs-arsenal", {
|
|
315
|
+
baseUrl: "https://trustix.uk",
|
|
316
|
+
labels: { "/ticket": "Tickets" }, // optional custom label overrides
|
|
317
|
+
});
|
|
318
|
+
|
|
319
|
+
// Result:
|
|
320
|
+
// [
|
|
321
|
+
// { name: "Home", url: "https://trustix.uk/" },
|
|
322
|
+
// { name: "Tickets", url: "https://trustix.uk/ticket" },
|
|
323
|
+
// { name: "Liverpool Vs Arsenal", url: "https://trustix.uk/ticket/liverpool-vs-arsenal" },
|
|
324
|
+
// ]
|
|
325
|
+
|
|
326
|
+
// Feed directly into schema:
|
|
327
|
+
const schema = createBreadcrumbSchema(items);
|
|
328
|
+
```
|
|
329
|
+
|
|
330
|
+
<br />
|
|
331
|
+
|
|
332
|
+
---
|
|
333
|
+
|
|
334
|
+
<br />
|
|
335
|
+
|
|
336
|
+
### SEO Validation (build-time warnings)
|
|
337
|
+
|
|
338
|
+
Catch missing tags before they reach production:
|
|
339
|
+
|
|
340
|
+
```ts
|
|
341
|
+
import { validateSEO, printValidationReport } from "react-ssr-seo-toolkit/validation";
|
|
342
|
+
|
|
343
|
+
const issues = validateSEO([
|
|
344
|
+
{
|
|
345
|
+
path: "/",
|
|
346
|
+
name: "HomePage",
|
|
347
|
+
seo: { title: "Trustix UK", description: "Buy tickets online." },
|
|
348
|
+
},
|
|
349
|
+
{
|
|
350
|
+
path: "/tickets",
|
|
351
|
+
name: "TicketListPage",
|
|
352
|
+
seo: {
|
|
353
|
+
title: "Tickets",
|
|
354
|
+
description: "Browse all tickets.",
|
|
355
|
+
canonical: "https://trustix.uk/tickets",
|
|
356
|
+
twitter: { card: "summary_large_image" },
|
|
357
|
+
// og:image missing — will warn
|
|
358
|
+
},
|
|
359
|
+
},
|
|
360
|
+
]);
|
|
361
|
+
|
|
362
|
+
console.log(printValidationReport(issues));
|
|
363
|
+
```
|
|
364
|
+
|
|
365
|
+
**Console output:**
|
|
366
|
+
|
|
367
|
+
```
|
|
368
|
+
HomePage:
|
|
369
|
+
❌ [canonical] Missing canonical URL
|
|
370
|
+
⚠️ [og:image] Missing og:image
|
|
371
|
+
⚠️ [twitter:card] Missing twitter:card
|
|
372
|
+
⚠️ [structured-data] No structured data (JSON-LD)
|
|
373
|
+
|
|
374
|
+
TicketListPage:
|
|
375
|
+
⚠️ [og:image] Missing og:image
|
|
376
|
+
⚠️ [structured-data] No structured data (JSON-LD)
|
|
377
|
+
```
|
|
378
|
+
|
|
379
|
+
<br />
|
|
380
|
+
|
|
381
|
+
---
|
|
382
|
+
|
|
383
|
+
<br />
|
|
384
|
+
|
|
385
|
+
### SEO Score per Page
|
|
386
|
+
|
|
387
|
+
```ts
|
|
388
|
+
import { getSEOScore, formatSEOScore } from "react-ssr-seo-toolkit/validation";
|
|
389
|
+
|
|
390
|
+
const result = getSEOScore(
|
|
391
|
+
{
|
|
392
|
+
title: "Liverpool vs Arsenal — Premier League Tickets",
|
|
393
|
+
description: "Buy tickets for Liverpool vs Arsenal at Anfield.",
|
|
394
|
+
canonical: "https://trustix.uk/tickets/liverpool-arsenal",
|
|
395
|
+
openGraph: {
|
|
396
|
+
images: [{ url: "https://trustix.uk/og/liverpool-arsenal.jpg" }],
|
|
397
|
+
},
|
|
398
|
+
twitter: { card: "summary_large_image" },
|
|
399
|
+
jsonLd: { "@context": "https://schema.org", "@type": "Event" },
|
|
400
|
+
},
|
|
401
|
+
"TicketDetailPage"
|
|
402
|
+
);
|
|
403
|
+
|
|
404
|
+
console.log(formatSEOScore(result));
|
|
405
|
+
```
|
|
406
|
+
|
|
407
|
+
**Console output:**
|
|
408
|
+
|
|
409
|
+
```
|
|
410
|
+
🟢 SEO Score: TicketDetailPage 75/100 (75%)
|
|
411
|
+
✅ Title
|
|
412
|
+
✅ Description
|
|
413
|
+
✅ Canonical URL
|
|
414
|
+
✅ og:image
|
|
415
|
+
✅ og:title
|
|
416
|
+
✅ og:description
|
|
417
|
+
✅ Twitter Card
|
|
418
|
+
✅ Structured Data
|
|
419
|
+
❌ Robots Directives (-5) — No robots directives
|
|
420
|
+
❌ Hreflang (-5) — No hreflang alternates
|
|
421
|
+
```
|
|
422
|
+
|
|
423
|
+
<br />
|
|
424
|
+
|
|
425
|
+
---
|
|
426
|
+
|
|
427
|
+
<br />
|
|
428
|
+
|
|
429
|
+
### OG Image Generation (no external service)
|
|
430
|
+
|
|
431
|
+
Generate 1200×630 SVG images server-side — no Puppeteer, no Vercel Edge, no API keys:
|
|
432
|
+
|
|
433
|
+
```ts
|
|
434
|
+
import { createOGImageSVG } from "react-ssr-seo-toolkit/og";
|
|
435
|
+
|
|
436
|
+
// Default template — dark gradient background
|
|
437
|
+
const svg = createOGImageSVG({
|
|
438
|
+
title: "How to Build an SSR App",
|
|
439
|
+
description: "A complete guide to server-rendered React.",
|
|
440
|
+
brand: "My Blog",
|
|
441
|
+
accentColor: "#6366f1",
|
|
442
|
+
});
|
|
443
|
+
|
|
444
|
+
// Serve from an Express endpoint:
|
|
445
|
+
app.get("/og/default.svg", (req, res) => {
|
|
446
|
+
res.type("image/svg+xml").send(
|
|
447
|
+
createOGImageSVG({ title: req.query.title as string, brand: "My Blog" })
|
|
448
|
+
);
|
|
449
|
+
});
|
|
450
|
+
```
|
|
451
|
+
|
|
452
|
+
**Article template** (light background with category tag):
|
|
453
|
+
|
|
454
|
+
```ts
|
|
455
|
+
const svg = createOGImageSVG({
|
|
456
|
+
title: "Premier League Preview 2026",
|
|
457
|
+
description: "Everything you need to know about the upcoming season.",
|
|
458
|
+
template: "article",
|
|
459
|
+
category: "Sports",
|
|
460
|
+
author: "James Walker",
|
|
461
|
+
dateString: "May 8, 2026",
|
|
462
|
+
brand: "Trustix UK",
|
|
463
|
+
accentColor: "#e63946",
|
|
464
|
+
});
|
|
465
|
+
```
|
|
466
|
+
|
|
467
|
+
**Sports event template** (VS layout):
|
|
468
|
+
|
|
469
|
+
```ts
|
|
470
|
+
const svg = createOGImageSVG({
|
|
471
|
+
title: "Liverpool vs Arsenal",
|
|
472
|
+
template: "sports-event",
|
|
473
|
+
homeTeam: "Liverpool",
|
|
474
|
+
awayTeam: "Arsenal",
|
|
475
|
+
eventDate: "Saturday, May 15, 2026 · 17:30",
|
|
476
|
+
brand: "Trustix UK",
|
|
477
|
+
accentColor: "#ffd700",
|
|
478
|
+
});
|
|
479
|
+
```
|
|
480
|
+
|
|
481
|
+
> The SVG output can be used directly as `og:image` or converted to PNG with `sharp`:
|
|
482
|
+
> ```ts
|
|
483
|
+
> import sharp from "sharp";
|
|
484
|
+
> const png = await sharp(Buffer.from(svg)).png().toBuffer();
|
|
485
|
+
> ```
|
|
486
|
+
|
|
487
|
+
<br />
|
|
488
|
+
|
|
489
|
+
---
|
|
490
|
+
|
|
491
|
+
<br />
|
|
492
|
+
|
|
493
|
+
### Social Preview Component (dev mode)
|
|
494
|
+
|
|
495
|
+
See exactly how your page will look on Twitter, Facebook, LinkedIn, and Google — without leaving the browser:
|
|
496
|
+
|
|
497
|
+
```tsx
|
|
498
|
+
import { SEOPreview } from "react-ssr-seo-toolkit/components";
|
|
499
|
+
|
|
500
|
+
// Twitter card preview
|
|
501
|
+
<SEOPreview config={seoConfig} platform="twitter" />
|
|
502
|
+
|
|
503
|
+
// Facebook / Open Graph preview
|
|
504
|
+
<SEOPreview config={seoConfig} platform="facebook" />
|
|
505
|
+
|
|
506
|
+
// LinkedIn preview
|
|
507
|
+
<SEOPreview config={seoConfig} platform="linkedin" />
|
|
508
|
+
|
|
509
|
+
// Google search result preview
|
|
510
|
+
<SEOPreview config={seoConfig} platform="google" />
|
|
511
|
+
```
|
|
512
|
+
|
|
513
|
+
> **Tip:** Wrap in `{process.env.NODE_ENV === "development" && ...}` so it never ships to production.
|
|
514
|
+
|
|
515
|
+
<br />
|
|
516
|
+
|
|
517
|
+
---
|
|
223
518
|
|
|
224
519
|
<br />
|
|
225
520
|
|
|
226
521
|
### Blog / Article Page
|
|
227
522
|
|
|
228
523
|
```tsx
|
|
229
|
-
// pages/BlogPost.tsx
|
|
230
524
|
import {
|
|
231
525
|
mergeSEOConfig, buildCanonicalUrl,
|
|
232
526
|
createArticleSchema, createBreadcrumbSchema,
|
|
@@ -235,40 +529,25 @@ import { siteConfig, SITE_URL } from "../config/seo";
|
|
|
235
529
|
import { Document } from "../components/Document";
|
|
236
530
|
|
|
237
531
|
export function BlogPostPage() {
|
|
238
|
-
// ── Page SEO ──────────────────────────────────────────────
|
|
239
532
|
const seo = mergeSEOConfig(siteConfig, {
|
|
240
533
|
title: "How to Build an SSR App",
|
|
241
534
|
description: "A complete guide to building server-rendered React apps with proper SEO.",
|
|
242
535
|
canonical: buildCanonicalUrl(SITE_URL, "/blog/ssr-guide"),
|
|
243
536
|
openGraph: {
|
|
244
537
|
title: "How to Build an SSR App",
|
|
245
|
-
description: "A complete guide to SSR with React.",
|
|
246
538
|
type: "article",
|
|
247
539
|
url: "https://myblog.com/blog/ssr-guide",
|
|
248
|
-
images: [{
|
|
249
|
-
url: "https://myblog.com/images/ssr-guide.jpg",
|
|
250
|
-
width: 1200, height: 630,
|
|
251
|
-
alt: "SSR Guide Cover",
|
|
252
|
-
}],
|
|
253
|
-
},
|
|
254
|
-
twitter: {
|
|
255
|
-
title: "How to Build an SSR App",
|
|
256
|
-
creator: "@authorhandle",
|
|
257
|
-
image: "https://myblog.com/images/ssr-guide.jpg",
|
|
540
|
+
images: [{ url: "https://myblog.com/images/ssr-guide.jpg", width: 1200, height: 630, alt: "SSR Guide" }],
|
|
258
541
|
},
|
|
542
|
+
twitter: { title: "How to Build an SSR App", creator: "@authorhandle", image: "https://myblog.com/images/ssr-guide.jpg" },
|
|
259
543
|
});
|
|
260
544
|
|
|
261
|
-
// ── Structured Data ───────────────────────────────────────
|
|
262
545
|
const article = createArticleSchema({
|
|
263
546
|
headline: "How to Build an SSR App",
|
|
264
547
|
url: "https://myblog.com/blog/ssr-guide",
|
|
265
|
-
description: "A complete guide to SSR with React.",
|
|
266
548
|
datePublished: "2025-06-15",
|
|
267
549
|
dateModified: "2025-07-01",
|
|
268
|
-
author: [
|
|
269
|
-
{ name: "Jane Doe", url: "https://myblog.com/authors/jane" },
|
|
270
|
-
{ name: "John Smith" },
|
|
271
|
-
],
|
|
550
|
+
author: [{ name: "Jane Doe", url: "https://myblog.com/authors/jane" }],
|
|
272
551
|
publisher: { name: "My Blog", logo: "https://myblog.com/logo.png" },
|
|
273
552
|
images: ["https://myblog.com/images/ssr-guide.jpg"],
|
|
274
553
|
section: "Technology",
|
|
@@ -281,12 +560,10 @@ export function BlogPostPage() {
|
|
|
281
560
|
{ name: "How to Build an SSR App", url: "https://myblog.com/blog/ssr-guide" },
|
|
282
561
|
]);
|
|
283
562
|
|
|
284
|
-
// ── Render — no <html> or <head> tags! ────────────────────
|
|
285
563
|
return (
|
|
286
564
|
<Document seo={seo} schemas={[article, breadcrumbs]}>
|
|
287
565
|
<article>
|
|
288
566
|
<h1>How to Build an SSR App</h1>
|
|
289
|
-
<p>Your article content here...</p>
|
|
290
567
|
</article>
|
|
291
568
|
</Document>
|
|
292
569
|
);
|
|
@@ -295,36 +572,6 @@ export function BlogPostPage() {
|
|
|
295
572
|
|
|
296
573
|
<br />
|
|
297
574
|
|
|
298
|
-
### Generated HTML Output
|
|
299
|
-
|
|
300
|
-
```html
|
|
301
|
-
<head>
|
|
302
|
-
<!-- Basic -->
|
|
303
|
-
<title>How to Build an SSR App | My Blog</title>
|
|
304
|
-
<meta name="description" content="A complete guide to building server-rendered React apps..." />
|
|
305
|
-
<link rel="canonical" href="https://myblog.com/blog/ssr-guide" />
|
|
306
|
-
|
|
307
|
-
<!-- Open Graph -->
|
|
308
|
-
<meta property="og:title" content="How to Build an SSR App" />
|
|
309
|
-
<meta property="og:description" content="A complete guide to SSR with React." />
|
|
310
|
-
<meta property="og:type" content="article" />
|
|
311
|
-
<meta property="og:url" content="https://myblog.com/blog/ssr-guide" />
|
|
312
|
-
<meta property="og:site_name" content="My Blog" />
|
|
313
|
-
<meta property="og:image" content="https://myblog.com/images/ssr-guide.jpg" />
|
|
314
|
-
|
|
315
|
-
<!-- Twitter Card -->
|
|
316
|
-
<meta name="twitter:card" content="summary_large_image" />
|
|
317
|
-
<meta name="twitter:site" content="@myblog" />
|
|
318
|
-
<meta name="twitter:title" content="How to Build an SSR App" />
|
|
319
|
-
|
|
320
|
-
<!-- JSON-LD -->
|
|
321
|
-
<script type="application/ld+json">{"@context":"https://schema.org","@type":"Article",...}</script>
|
|
322
|
-
<script type="application/ld+json">{"@context":"https://schema.org","@type":"BreadcrumbList",...}</script>
|
|
323
|
-
</head>
|
|
324
|
-
```
|
|
325
|
-
|
|
326
|
-
<br />
|
|
327
|
-
|
|
328
575
|
---
|
|
329
576
|
|
|
330
577
|
<br />
|
|
@@ -332,59 +579,38 @@ export function BlogPostPage() {
|
|
|
332
579
|
### E-Commerce Product Page
|
|
333
580
|
|
|
334
581
|
```tsx
|
|
335
|
-
import {
|
|
336
|
-
mergeSEOConfig, buildCanonicalUrl,
|
|
337
|
-
createProductSchema,
|
|
338
|
-
} from "react-ssr-seo-toolkit";
|
|
339
|
-
import { siteConfig, SITE_URL } from "../config/seo";
|
|
340
|
-
import { Document } from "../components/Document";
|
|
582
|
+
import { mergeSEOConfig, buildCanonicalUrl, createProductSchema } from "react-ssr-seo-toolkit";
|
|
341
583
|
|
|
342
584
|
function ProductPage() {
|
|
343
|
-
const product = {
|
|
344
|
-
name: "Ergonomic Mechanical Keyboard",
|
|
345
|
-
description: "Premium split keyboard with Cherry MX Brown switches.",
|
|
346
|
-
price: 189.99,
|
|
347
|
-
image: "https://acmestore.com/images/keyboard.jpg",
|
|
348
|
-
brand: "Acme Peripherals",
|
|
349
|
-
sku: "ACME-KB-001",
|
|
350
|
-
inStock: true,
|
|
351
|
-
rating: 4.7,
|
|
352
|
-
reviewCount: 342,
|
|
353
|
-
};
|
|
354
|
-
|
|
355
585
|
const url = buildCanonicalUrl(SITE_URL, "/products/ergonomic-keyboard");
|
|
356
586
|
|
|
357
587
|
const seo = mergeSEOConfig(siteConfig, {
|
|
358
|
-
title:
|
|
359
|
-
description:
|
|
588
|
+
title: "Ergonomic Mechanical Keyboard",
|
|
589
|
+
description: "Premium split keyboard with Cherry MX Brown switches.",
|
|
360
590
|
canonical: url,
|
|
361
591
|
openGraph: {
|
|
362
|
-
title: product.name,
|
|
363
|
-
description: product.description,
|
|
364
592
|
type: "product",
|
|
365
593
|
url,
|
|
366
|
-
images: [{ url:
|
|
594
|
+
images: [{ url: "https://acmestore.com/images/keyboard.jpg", width: 800, height: 800, alt: "Keyboard" }],
|
|
367
595
|
},
|
|
368
596
|
});
|
|
369
597
|
|
|
370
598
|
const schema = createProductSchema({
|
|
371
|
-
name:
|
|
599
|
+
name: "Ergonomic Mechanical Keyboard",
|
|
372
600
|
url,
|
|
373
|
-
|
|
374
|
-
price: product.price,
|
|
601
|
+
price: 189.99,
|
|
375
602
|
priceCurrency: "USD",
|
|
376
|
-
availability:
|
|
377
|
-
brand:
|
|
378
|
-
sku:
|
|
379
|
-
images: [
|
|
380
|
-
ratingValue:
|
|
381
|
-
reviewCount:
|
|
603
|
+
availability: "InStock",
|
|
604
|
+
brand: "Acme Peripherals",
|
|
605
|
+
sku: "ACME-KB-001",
|
|
606
|
+
images: ["https://acmestore.com/images/keyboard.jpg"],
|
|
607
|
+
ratingValue: 4.7,
|
|
608
|
+
reviewCount: 342,
|
|
382
609
|
});
|
|
383
610
|
|
|
384
611
|
return (
|
|
385
612
|
<Document seo={seo} schemas={[schema]}>
|
|
386
|
-
<h1>
|
|
387
|
-
<p>${product.price}</p>
|
|
613
|
+
<h1>Ergonomic Mechanical Keyboard</h1>
|
|
388
614
|
</Document>
|
|
389
615
|
);
|
|
390
616
|
}
|
|
@@ -396,40 +622,62 @@ function ProductPage() {
|
|
|
396
622
|
|
|
397
623
|
<br />
|
|
398
624
|
|
|
399
|
-
###
|
|
625
|
+
### Job Posting Schema
|
|
400
626
|
|
|
401
627
|
```tsx
|
|
402
|
-
import {
|
|
403
|
-
|
|
404
|
-
|
|
405
|
-
|
|
406
|
-
|
|
628
|
+
import { createJobPostingSchema } from "react-ssr-seo-toolkit";
|
|
629
|
+
|
|
630
|
+
const schema = createJobPostingSchema({
|
|
631
|
+
title: "Senior React Engineer",
|
|
632
|
+
description: "Build and maintain our SSR platform...",
|
|
633
|
+
datePosted: "2026-05-01",
|
|
634
|
+
validThrough: "2026-06-30",
|
|
635
|
+
employmentType: "FULL_TIME",
|
|
636
|
+
hiringOrganization: {
|
|
637
|
+
name: "Trustix UK",
|
|
638
|
+
sameAs: "https://trustix.uk",
|
|
639
|
+
logo: "https://trustix.uk/logo.png",
|
|
640
|
+
},
|
|
641
|
+
jobLocation: { addressLocality: "London", addressCountry: "GB" },
|
|
642
|
+
remote: true,
|
|
643
|
+
baseSalary: {
|
|
644
|
+
currency: "GBP",
|
|
645
|
+
value: { minValue: 70000, maxValue: 95000 },
|
|
646
|
+
unitText: "YEAR",
|
|
647
|
+
},
|
|
648
|
+
});
|
|
649
|
+
```
|
|
407
650
|
|
|
408
|
-
|
|
409
|
-
const faqs = [
|
|
410
|
-
{ question: "What payment methods do you accept?", answer: "Visa, MasterCard, PayPal, Apple Pay." },
|
|
411
|
-
{ question: "How long does shipping take?", answer: "Standard: 3-5 business days." },
|
|
412
|
-
{ question: "What is your return policy?", answer: "30-day money-back guarantee." },
|
|
413
|
-
];
|
|
651
|
+
<br />
|
|
414
652
|
|
|
415
|
-
|
|
416
|
-
title: "FAQ",
|
|
417
|
-
description: "Frequently asked questions about our products and services.",
|
|
418
|
-
canonical: buildCanonicalUrl(SITE_URL, "/faq"),
|
|
419
|
-
});
|
|
653
|
+
---
|
|
420
654
|
|
|
421
|
-
|
|
422
|
-
|
|
423
|
-
|
|
424
|
-
|
|
425
|
-
|
|
426
|
-
|
|
427
|
-
|
|
428
|
-
|
|
429
|
-
|
|
430
|
-
|
|
431
|
-
|
|
432
|
-
}
|
|
655
|
+
<br />
|
|
656
|
+
|
|
657
|
+
### Recipe Schema
|
|
658
|
+
|
|
659
|
+
```tsx
|
|
660
|
+
import { createRecipeSchema } from "react-ssr-seo-toolkit";
|
|
661
|
+
|
|
662
|
+
const schema = createRecipeSchema({
|
|
663
|
+
name: "Chicken Biryani",
|
|
664
|
+
description: "Authentic Dhaka-style biryani with aromatic spices.",
|
|
665
|
+
images: ["https://food.com/biryani.jpg"],
|
|
666
|
+
author: { name: "Chef Rahman" },
|
|
667
|
+
prepTime: "PT30M",
|
|
668
|
+
cookTime: "PT1H",
|
|
669
|
+
totalTime: "PT1H30M",
|
|
670
|
+
recipeYield: "6 servings",
|
|
671
|
+
recipeCategory: "Main Course",
|
|
672
|
+
recipeCuisine: "Bengali",
|
|
673
|
+
recipeIngredient: ["1kg chicken", "500g basmati rice", "onions"],
|
|
674
|
+
recipeInstructions: [
|
|
675
|
+
{ name: "Marinate", text: "Marinate chicken with spices for 30 minutes." },
|
|
676
|
+
{ name: "Cook Rice", text: "Parboil basmati rice until 70% done." },
|
|
677
|
+
],
|
|
678
|
+
ratingValue: 4.9,
|
|
679
|
+
reviewCount: 128,
|
|
680
|
+
});
|
|
433
681
|
```
|
|
434
682
|
|
|
435
683
|
<br />
|
|
@@ -438,59 +686,49 @@ function FAQPage() {
|
|
|
438
686
|
|
|
439
687
|
<br />
|
|
440
688
|
|
|
441
|
-
###
|
|
689
|
+
### Person Schema
|
|
442
690
|
|
|
443
691
|
```tsx
|
|
444
|
-
import {
|
|
445
|
-
|
|
446
|
-
|
|
447
|
-
|
|
448
|
-
|
|
449
|
-
|
|
692
|
+
import { createPersonSchema } from "react-ssr-seo-toolkit";
|
|
693
|
+
|
|
694
|
+
const schema = createPersonSchema({
|
|
695
|
+
name: "Tonmoy Khan",
|
|
696
|
+
url: "https://tonmoykhan.site",
|
|
697
|
+
jobTitle: "Software Engineer",
|
|
698
|
+
image: "https://tonmoykhan.site/photo.jpg",
|
|
699
|
+
sameAs: [
|
|
700
|
+
"https://github.com/Tonmoy01",
|
|
701
|
+
"https://linkedin.com/in/tonmoy",
|
|
702
|
+
],
|
|
703
|
+
worksFor: { name: "Trustix UK", url: "https://trustix.uk" },
|
|
704
|
+
});
|
|
705
|
+
```
|
|
450
706
|
|
|
451
|
-
|
|
452
|
-
const seo = mergeSEOConfig(siteConfig, {
|
|
453
|
-
title: "Home",
|
|
454
|
-
canonical: "https://acme.com",
|
|
455
|
-
openGraph: {
|
|
456
|
-
title: "Acme — Building the future",
|
|
457
|
-
url: "https://acme.com",
|
|
458
|
-
images: [{ url: "https://acme.com/og-home.jpg", width: 1200, height: 630, alt: "Acme" }],
|
|
459
|
-
},
|
|
460
|
-
});
|
|
707
|
+
<br />
|
|
461
708
|
|
|
462
|
-
|
|
463
|
-
name: "Acme Inc",
|
|
464
|
-
url: "https://acme.com",
|
|
465
|
-
logo: "https://acme.com/logo.png",
|
|
466
|
-
description: "Leading provider of quality products.",
|
|
467
|
-
sameAs: [
|
|
468
|
-
"https://twitter.com/acme",
|
|
469
|
-
"https://linkedin.com/company/acme",
|
|
470
|
-
"https://facebook.com/acme",
|
|
471
|
-
],
|
|
472
|
-
contactPoint: {
|
|
473
|
-
telephone: "+1-800-555-0199",
|
|
474
|
-
contactType: "customer service",
|
|
475
|
-
email: "support@acme.com",
|
|
476
|
-
areaServed: "US",
|
|
477
|
-
availableLanguage: ["English", "Spanish"],
|
|
478
|
-
},
|
|
479
|
-
});
|
|
709
|
+
---
|
|
480
710
|
|
|
481
|
-
|
|
482
|
-
name: "Acme Inc",
|
|
483
|
-
url: "https://acme.com",
|
|
484
|
-
description: "Leading provider of quality products.",
|
|
485
|
-
searchUrl: "https://acme.com/search", // enables Google sitelinks searchbox
|
|
486
|
-
});
|
|
711
|
+
<br />
|
|
487
712
|
|
|
488
|
-
|
|
489
|
-
|
|
490
|
-
|
|
491
|
-
|
|
492
|
-
|
|
493
|
-
|
|
713
|
+
### Event Page (Sports Match)
|
|
714
|
+
|
|
715
|
+
```tsx
|
|
716
|
+
import { createEventSchema } from "react-ssr-seo-toolkit";
|
|
717
|
+
|
|
718
|
+
// @type auto-switches to "SportsEvent" when sport/homeTeam/awayTeam is provided
|
|
719
|
+
const schema = createEventSchema({
|
|
720
|
+
name: "El Clásico",
|
|
721
|
+
startDate: "2026-04-20T21:00:00+02:00",
|
|
722
|
+
sport: "Soccer",
|
|
723
|
+
homeTeam: { name: "FC Barcelona", url: "https://fcbarcelona.com" },
|
|
724
|
+
awayTeam: { name: "Real Madrid CF", url: "https://realmadrid.com" },
|
|
725
|
+
location: {
|
|
726
|
+
name: "Camp Nou",
|
|
727
|
+
address: { addressLocality: "Barcelona", addressCountry: "ES" },
|
|
728
|
+
},
|
|
729
|
+
eventStatus: "EventScheduled",
|
|
730
|
+
eventAttendanceMode: "OfflineEventAttendanceMode",
|
|
731
|
+
});
|
|
494
732
|
```
|
|
495
733
|
|
|
496
734
|
<br />
|
|
@@ -512,11 +750,6 @@ const seo = mergeSEOConfig(siteConfig, {
|
|
|
512
750
|
{ hreflang: "x-default", href: "https://mysite.com/products" },
|
|
513
751
|
],
|
|
514
752
|
});
|
|
515
|
-
|
|
516
|
-
// Generates:
|
|
517
|
-
// <link rel="alternate" hreflang="en" href="https://mysite.com/en/products" />
|
|
518
|
-
// <link rel="alternate" hreflang="es" href="https://mysite.com/es/products" />
|
|
519
|
-
// ...
|
|
520
753
|
```
|
|
521
754
|
|
|
522
755
|
<br />
|
|
@@ -525,57 +758,20 @@ const seo = mergeSEOConfig(siteConfig, {
|
|
|
525
758
|
|
|
526
759
|
<br />
|
|
527
760
|
|
|
528
|
-
### No-Index Pages
|
|
761
|
+
### No-Index Pages
|
|
529
762
|
|
|
530
763
|
```tsx
|
|
531
764
|
import { mergeSEOConfig, noIndex, noIndexNoFollow } from "react-ssr-seo-toolkit";
|
|
532
765
|
|
|
533
|
-
// Login page: don't index, but follow links
|
|
534
766
|
const loginSeo = mergeSEOConfig(siteConfig, {
|
|
535
767
|
title: "Login",
|
|
536
768
|
robots: noIndex(), // "noindex, follow"
|
|
537
769
|
});
|
|
538
770
|
|
|
539
|
-
// Admin page: don't index, don't follow
|
|
540
771
|
const adminSeo = mergeSEOConfig(siteConfig, {
|
|
541
772
|
title: "Admin Dashboard",
|
|
542
773
|
robots: noIndexNoFollow(), // "noindex, nofollow"
|
|
543
774
|
});
|
|
544
|
-
|
|
545
|
-
// Fine-grained control
|
|
546
|
-
const archiveSeo = mergeSEOConfig(siteConfig, {
|
|
547
|
-
title: "Archive",
|
|
548
|
-
robots: {
|
|
549
|
-
index: true,
|
|
550
|
-
follow: true,
|
|
551
|
-
noarchive: true,
|
|
552
|
-
nosnippet: true,
|
|
553
|
-
maxSnippet: 50,
|
|
554
|
-
maxImagePreview: "standard",
|
|
555
|
-
},
|
|
556
|
-
});
|
|
557
|
-
```
|
|
558
|
-
|
|
559
|
-
<br />
|
|
560
|
-
|
|
561
|
-
---
|
|
562
|
-
|
|
563
|
-
<br />
|
|
564
|
-
|
|
565
|
-
### Combine Multiple Schemas
|
|
566
|
-
|
|
567
|
-
```tsx
|
|
568
|
-
import { composeSchemas, createOrganizationSchema, createWebsiteSchema, JsonLd } from "react-ssr-seo-toolkit";
|
|
569
|
-
|
|
570
|
-
// Merge into a single JSON-LD block with @graph array
|
|
571
|
-
const combined = composeSchemas(
|
|
572
|
-
createOrganizationSchema({ name: "Acme", url: "https://acme.com" }),
|
|
573
|
-
createWebsiteSchema({ name: "Acme", url: "https://acme.com" }),
|
|
574
|
-
);
|
|
575
|
-
|
|
576
|
-
<JsonLd data={combined} />
|
|
577
|
-
|
|
578
|
-
// Output: single <script> tag with {"@context":"https://schema.org","@graph":[...]}
|
|
579
775
|
```
|
|
580
776
|
|
|
581
777
|
<br />
|
|
@@ -588,13 +784,6 @@ const combined = composeSchemas(
|
|
|
588
784
|
|
|
589
785
|
### Next.js App Router
|
|
590
786
|
|
|
591
|
-
Use the `toNextMetadata()` adapter to convert any `SEOConfig` directly into a Next.js `Metadata` object.
|
|
592
|
-
|
|
593
|
-
```bash
|
|
594
|
-
# no extra install needed — adapter is included in the package
|
|
595
|
-
import { toNextMetadata } from 'react-ssr-seo-toolkit/adapters/nextjs';
|
|
596
|
-
```
|
|
597
|
-
|
|
598
787
|
```tsx
|
|
599
788
|
// app/blog/[slug]/page.tsx
|
|
600
789
|
import { toNextMetadata } from "react-ssr-seo-toolkit/adapters/nextjs";
|
|
@@ -603,7 +792,6 @@ import type { Metadata } from "next";
|
|
|
603
792
|
|
|
604
793
|
export async function generateMetadata({ params }): Promise<Metadata> {
|
|
605
794
|
const post = await getPost(params.slug);
|
|
606
|
-
|
|
607
795
|
return toNextMetadata({
|
|
608
796
|
title: post.title,
|
|
609
797
|
titleTemplate: "%s | My Blog",
|
|
@@ -614,63 +802,8 @@ export async function generateMetadata({ params }): Promise<Metadata> {
|
|
|
614
802
|
type: "article",
|
|
615
803
|
images: [{ url: post.image, width: 1200, height: 630, alt: post.title }],
|
|
616
804
|
},
|
|
617
|
-
twitter: { card: "summary_large_image"
|
|
618
|
-
robots: { index: true, follow: true },
|
|
619
|
-
});
|
|
620
|
-
}
|
|
621
|
-
|
|
622
|
-
export default async function BlogPost({ params }) {
|
|
623
|
-
const post = await getPost(params.slug);
|
|
624
|
-
|
|
625
|
-
const schema = createArticleSchema({
|
|
626
|
-
headline: post.title,
|
|
627
|
-
url: `https://myblog.com/blog/${params.slug}`,
|
|
628
|
-
datePublished: post.date,
|
|
629
|
-
author: { name: post.author },
|
|
805
|
+
twitter: { card: "summary_large_image" },
|
|
630
806
|
});
|
|
631
|
-
|
|
632
|
-
return (
|
|
633
|
-
<>
|
|
634
|
-
<script
|
|
635
|
-
type="application/ld+json"
|
|
636
|
-
dangerouslySetInnerHTML={{ __html: safeJsonLdSerialize(schema) }}
|
|
637
|
-
/>
|
|
638
|
-
<article>
|
|
639
|
-
<h1>{post.title}</h1>
|
|
640
|
-
<p>{post.content}</p>
|
|
641
|
-
</article>
|
|
642
|
-
</>
|
|
643
|
-
);
|
|
644
|
-
}
|
|
645
|
-
```
|
|
646
|
-
|
|
647
|
-
<br />
|
|
648
|
-
|
|
649
|
-
### Next.js Pages Router
|
|
650
|
-
|
|
651
|
-
```tsx
|
|
652
|
-
// pages/about.tsx — no <html> tags, Next.js handles that
|
|
653
|
-
import Head from "next/head";
|
|
654
|
-
import { SEOHead, mergeSEOConfig } from "react-ssr-seo-toolkit";
|
|
655
|
-
import { siteConfig } from "../config/seo";
|
|
656
|
-
|
|
657
|
-
export default function AboutPage() {
|
|
658
|
-
const seo = mergeSEOConfig(siteConfig, {
|
|
659
|
-
title: "About Us",
|
|
660
|
-
description: "Learn about our mission.",
|
|
661
|
-
canonical: "https://mysite.com/about",
|
|
662
|
-
});
|
|
663
|
-
|
|
664
|
-
return (
|
|
665
|
-
<>
|
|
666
|
-
<Head>
|
|
667
|
-
<SEOHead {...seo} />
|
|
668
|
-
</Head>
|
|
669
|
-
<main>
|
|
670
|
-
<h1>About Us</h1>
|
|
671
|
-
</main>
|
|
672
|
-
</>
|
|
673
|
-
);
|
|
674
807
|
}
|
|
675
808
|
```
|
|
676
809
|
|
|
@@ -678,14 +811,6 @@ export default function AboutPage() {
|
|
|
678
811
|
|
|
679
812
|
### React Router 7
|
|
680
813
|
|
|
681
|
-
Use the `toRouterMeta()` adapter to convert any `SEOConfig` into React Router 7's `MetaDescriptor[]` array — the same format returned by a route's `meta()` export.
|
|
682
|
-
|
|
683
|
-
```bash
|
|
684
|
-
import { toRouterMeta, toRouterLinks } from 'react-ssr-seo-toolkit/adapters/react-router';
|
|
685
|
-
```
|
|
686
|
-
|
|
687
|
-
**Option A — `meta()` export (recommended, handles everything in one place):**
|
|
688
|
-
|
|
689
814
|
```tsx
|
|
690
815
|
// app/routes/about.tsx
|
|
691
816
|
import { toRouterMeta } from "react-ssr-seo-toolkit/adapters/react-router";
|
|
@@ -705,79 +830,6 @@ export function meta({}: Route.MetaArgs) {
|
|
|
705
830
|
);
|
|
706
831
|
}
|
|
707
832
|
|
|
708
|
-
export default function AboutPage() {
|
|
709
|
-
return (
|
|
710
|
-
<main>
|
|
711
|
-
<h1>About Us</h1>
|
|
712
|
-
</main>
|
|
713
|
-
);
|
|
714
|
-
}
|
|
715
|
-
```
|
|
716
|
-
|
|
717
|
-
**Option B — `meta()` + `links()` separated:**
|
|
718
|
-
|
|
719
|
-
```tsx
|
|
720
|
-
// app/routes/about.tsx
|
|
721
|
-
import { toRouterMeta, toRouterLinks } from "react-ssr-seo-toolkit/adapters/react-router";
|
|
722
|
-
|
|
723
|
-
const seoConfig = {
|
|
724
|
-
title: "About Us",
|
|
725
|
-
canonical: "https://mysite.com/about",
|
|
726
|
-
alternates: [{ hreflang: "en", href: "https://mysite.com/en/about" }],
|
|
727
|
-
openGraph: { type: "website", title: "About Us" },
|
|
728
|
-
};
|
|
729
|
-
|
|
730
|
-
// meta tags (title, og:*, twitter:*, robots)
|
|
731
|
-
export function meta() {
|
|
732
|
-
return toRouterMeta(seoConfig);
|
|
733
|
-
}
|
|
734
|
-
|
|
735
|
-
// link tags (canonical, hreflang alternates)
|
|
736
|
-
export function links() {
|
|
737
|
-
return toRouterLinks(seoConfig);
|
|
738
|
-
}
|
|
739
|
-
```
|
|
740
|
-
|
|
741
|
-
**Option C — Classic loader pattern with `<SEOHead>`:**
|
|
742
|
-
|
|
743
|
-
```tsx
|
|
744
|
-
// app/root.tsx — only the root layout writes <html>
|
|
745
|
-
import { Outlet, useMatches } from "react-router";
|
|
746
|
-
import { SEOHead } from "react-ssr-seo-toolkit";
|
|
747
|
-
|
|
748
|
-
export default function Root() {
|
|
749
|
-
const matches = useMatches();
|
|
750
|
-
const seo = matches.at(-1)?.data?.seo;
|
|
751
|
-
|
|
752
|
-
return (
|
|
753
|
-
<html lang="en">
|
|
754
|
-
<head>
|
|
755
|
-
<meta charSet="utf-8" />
|
|
756
|
-
<meta name="viewport" content="width=device-width, initial-scale=1" />
|
|
757
|
-
{seo && <SEOHead {...seo} />}
|
|
758
|
-
</head>
|
|
759
|
-
<body>
|
|
760
|
-
<Outlet />
|
|
761
|
-
</body>
|
|
762
|
-
</html>
|
|
763
|
-
);
|
|
764
|
-
}
|
|
765
|
-
```
|
|
766
|
-
|
|
767
|
-
```tsx
|
|
768
|
-
// app/routes/about.tsx
|
|
769
|
-
import { mergeSEOConfig, buildCanonicalUrl } from "react-ssr-seo-toolkit";
|
|
770
|
-
import { siteConfig, SITE_URL } from "../config/seo";
|
|
771
|
-
|
|
772
|
-
export function loader() {
|
|
773
|
-
return {
|
|
774
|
-
seo: mergeSEOConfig(siteConfig, {
|
|
775
|
-
title: "About",
|
|
776
|
-
canonical: buildCanonicalUrl(SITE_URL, "/about"),
|
|
777
|
-
}),
|
|
778
|
-
};
|
|
779
|
-
}
|
|
780
|
-
|
|
781
833
|
export default function AboutPage() {
|
|
782
834
|
return <main><h1>About Us</h1></main>;
|
|
783
835
|
}
|
|
@@ -788,54 +840,26 @@ export default function AboutPage() {
|
|
|
788
840
|
### Express + React SSR
|
|
789
841
|
|
|
790
842
|
```tsx
|
|
791
|
-
|
|
792
|
-
|
|
793
|
-
|
|
794
|
-
|
|
795
|
-
|
|
796
|
-
|
|
797
|
-
const app = express();
|
|
798
|
-
|
|
799
|
-
app.get("/", (req, res) => {
|
|
800
|
-
const html = renderToString(<HomePage />);
|
|
801
|
-
res.send(`<!DOCTYPE html>${html}`);
|
|
843
|
+
app.get("/sitemap.xml", (req, res) => {
|
|
844
|
+
const { generateSitemap } = require("react-ssr-seo-toolkit/sitemap");
|
|
845
|
+
res.type("application/xml").send(
|
|
846
|
+
generateSitemap({ baseUrl: "https://mysite.com", routes: appRoutes, exclude: ["/admin/*"] })
|
|
847
|
+
);
|
|
802
848
|
});
|
|
803
849
|
|
|
804
|
-
app.get("/
|
|
805
|
-
const
|
|
806
|
-
|
|
807
|
-
|
|
850
|
+
app.get("/robots.txt", (req, res) => {
|
|
851
|
+
const { generateRobots } = require("react-ssr-seo-toolkit/sitemap");
|
|
852
|
+
res.type("text/plain").send(
|
|
853
|
+
generateRobots({ rules: { disallow: ["/admin"] }, sitemap: "https://mysite.com/sitemap.xml" })
|
|
854
|
+
);
|
|
808
855
|
});
|
|
809
856
|
|
|
810
|
-
app.
|
|
811
|
-
|
|
812
|
-
|
|
813
|
-
|
|
814
|
-
// pages/ProductPage.tsx — no <html> tags, Document handles that
|
|
815
|
-
import { mergeSEOConfig, createProductSchema } from "react-ssr-seo-toolkit";
|
|
816
|
-
import { siteConfig } from "../config/seo";
|
|
817
|
-
import { Document } from "../components/Document";
|
|
818
|
-
|
|
819
|
-
export function ProductPage({ product }) {
|
|
820
|
-
const seo = mergeSEOConfig(siteConfig, {
|
|
821
|
-
title: product.name,
|
|
822
|
-
description: product.description,
|
|
823
|
-
canonical: product.url,
|
|
824
|
-
});
|
|
825
|
-
|
|
826
|
-
const schema = createProductSchema({
|
|
827
|
-
name: product.name,
|
|
828
|
-
url: product.url,
|
|
829
|
-
price: product.price,
|
|
830
|
-
});
|
|
831
|
-
|
|
832
|
-
return (
|
|
833
|
-
<Document seo={seo} schemas={[schema]}>
|
|
834
|
-
<h1>{product.name}</h1>
|
|
835
|
-
<p>${product.price}</p>
|
|
836
|
-
</Document>
|
|
857
|
+
app.get("/og/:title.svg", (req, res) => {
|
|
858
|
+
const { createOGImageSVG } = require("react-ssr-seo-toolkit/og");
|
|
859
|
+
res.type("image/svg+xml").send(
|
|
860
|
+
createOGImageSVG({ title: decodeURIComponent(req.params.title), brand: "MySite" })
|
|
837
861
|
);
|
|
838
|
-
}
|
|
862
|
+
});
|
|
839
863
|
```
|
|
840
864
|
|
|
841
865
|
<br />
|
|
@@ -851,7 +875,7 @@ export function ProductPage({ product }) {
|
|
|
851
875
|
| Function | What It Does |
|
|
852
876
|
|---|---|
|
|
853
877
|
| `createSEOConfig(config?)` | Create a normalized SEO config. Use for site-wide defaults. |
|
|
854
|
-
| `mergeSEOConfig(base, override)` | Deep-merge site config with page-level overrides.
|
|
878
|
+
| `mergeSEOConfig(base, override)` | Deep-merge site config with page-level overrides. |
|
|
855
879
|
| `normalizeSEOConfig(config)` | Trim strings, normalize URLs, clean up a config object. |
|
|
856
880
|
|
|
857
881
|
<br />
|
|
@@ -861,70 +885,101 @@ export function ProductPage({ product }) {
|
|
|
861
885
|
| Function | Example | Result |
|
|
862
886
|
|---|---|---|
|
|
863
887
|
| `buildTitle(title, template)` | `buildTitle("About", "%s \| MySite")` | `"About \| MySite"` |
|
|
864
|
-
| `buildDescription(desc, maxLen)` | `buildDescription("Long text...", 160)` | Truncated at 160 chars |
|
|
865
888
|
| `buildCanonicalUrl(base, path)` | `buildCanonicalUrl("https://x.com", "/about")` | `"https://x.com/about"` |
|
|
866
889
|
| `buildRobotsDirectives(config)` | `buildRobotsDirectives({ index: false })` | `"noindex, follow"` |
|
|
867
|
-
| `noIndex()` |
|
|
868
|
-
| `noIndexNoFollow()` |
|
|
869
|
-
| `buildOpenGraph(config)` | `buildOpenGraph({ title: "Hi" })` | `[{ property: "og:title", content: "Hi" }]` |
|
|
870
|
-
| `buildTwitterMetadata(config)` | `buildTwitterMetadata({ card: "summary" })` | `[{ name: "twitter:card", content: "summary" }]` |
|
|
871
|
-
| `buildAlternateLinks(alternates)` | `buildAlternateLinks([{ hreflang: "en", href: "..." }])` | `[{ rel: "alternate", hreflang: "en", href: "..." }]` |
|
|
890
|
+
| `noIndex()` | — | `{ index: false, follow: true }` |
|
|
891
|
+
| `noIndexNoFollow()` | — | `{ index: false, follow: false }` |
|
|
872
892
|
|
|
873
893
|
<br />
|
|
874
894
|
|
|
875
895
|
### JSON-LD Schema Generators
|
|
876
896
|
|
|
877
|
-
All return
|
|
897
|
+
All return `{ "@context": "https://schema.org", "@type": "...", ... }`.
|
|
878
898
|
|
|
879
899
|
| Function | Schema Type | Use Case |
|
|
880
900
|
|---|---|---|
|
|
881
901
|
| `createOrganizationSchema(input)` | Organization | Company info, logo, social links, contact |
|
|
882
902
|
| `createWebsiteSchema(input)` | WebSite | Site name, sitelinks searchbox |
|
|
883
|
-
| `createArticleSchema(input)` | Article | Blog posts, news articles
|
|
884
|
-
| `createProductSchema(input)` | Product | E-commerce: price, brand, SKU, ratings
|
|
903
|
+
| `createArticleSchema(input)` | Article | Blog posts, news articles |
|
|
904
|
+
| `createProductSchema(input)` | Product | E-commerce: price, brand, SKU, ratings |
|
|
885
905
|
| `createBreadcrumbSchema(items)` | BreadcrumbList | Navigation hierarchy |
|
|
886
|
-
| `createFAQSchema(items)` | FAQPage |
|
|
887
|
-
| `
|
|
906
|
+
| `createFAQSchema(items)` | FAQPage | Question + answer pairs |
|
|
907
|
+
| `createEventSchema(input)` | Event / SportsEvent | Concerts, conferences, sports — auto-switches to `SportsEvent` when `sport`/`homeTeam`/`awayTeam` is set |
|
|
908
|
+
| `createPersonSchema(input)` | Person | Author bios, team pages |
|
|
909
|
+
| `createRecipeSchema(input)` | Recipe | Food blogs, cooking sites |
|
|
910
|
+
| `createJobPostingSchema(input)` | JobPosting | Career pages, job boards |
|
|
911
|
+
| `composeSchemas(...schemas)` | @graph | Combine multiple schemas into one `<script>` |
|
|
888
912
|
|
|
889
913
|
<br />
|
|
890
914
|
|
|
891
|
-
###
|
|
915
|
+
### Sitemap & Robots — `react-ssr-seo-toolkit/sitemap`
|
|
892
916
|
|
|
893
917
|
| Function | What It Does |
|
|
894
918
|
|---|---|
|
|
895
|
-
| `
|
|
896
|
-
| `
|
|
897
|
-
| `
|
|
898
|
-
| `omitEmpty(obj)` | Remove keys with `undefined`, `null`, or empty string values |
|
|
899
|
-
| `deepMerge(base, override)` | Deep-merge two objects (arrays replaced, not concatenated) |
|
|
919
|
+
| `generateSitemap(options)` | Generates a valid `sitemap.xml` string from a list of routes |
|
|
920
|
+
| `generateRobots(options)` | Generates a `robots.txt` string with user-agent rules and sitemap links |
|
|
921
|
+
| `autoBreadcrumb(path, options?)` | Auto-generates `BreadcrumbItem[]` from a URL path like `/ticket/liverpool-vs-arsenal` |
|
|
900
922
|
|
|
901
|
-
|
|
923
|
+
**`generateSitemap` options:**
|
|
902
924
|
|
|
903
|
-
|
|
925
|
+
| Option | Type | Description |
|
|
926
|
+
|---|---|---|
|
|
927
|
+
| `routes` | `string[] \| SitemapRoute[]` | Route paths or route objects with `lastmod`, `changefreq`, `priority` |
|
|
928
|
+
| `baseUrl` | `string` | Base URL (e.g. `"https://mysite.com"`) |
|
|
929
|
+
| `exclude` | `string[]` | Glob patterns to exclude (supports `*` wildcard and `/*` suffix) |
|
|
930
|
+
| `defaultChangefreq` | `ChangeFreq` | Default change frequency for all routes |
|
|
931
|
+
| `defaultPriority` | `number` | Default priority (0.0–1.0) for all routes |
|
|
932
|
+
|
|
933
|
+
<br />
|
|
904
934
|
|
|
905
|
-
|
|
935
|
+
### SEO Validation & Scoring — `react-ssr-seo-toolkit/validation`
|
|
906
936
|
|
|
907
937
|
| Function | What It Does |
|
|
908
938
|
|---|---|
|
|
909
|
-
| `
|
|
910
|
-
| `
|
|
939
|
+
| `validateSEO(routes)` | Checks a list of routes for missing/invalid SEO fields, returns `SEOValidationIssue[]` |
|
|
940
|
+
| `printValidationReport(issues)` | Formats issues into a human-readable string with icons |
|
|
941
|
+
| `getSEOScore(config, pageName?)` | Scores a single page's SEO config out of 100 |
|
|
942
|
+
| `formatSEOScore(result)` | Formats the score result as a pretty-printed string |
|
|
943
|
+
|
|
944
|
+
**Scoring breakdown (100 points total):**
|
|
911
945
|
|
|
912
|
-
|
|
946
|
+
| Check | Points |
|
|
947
|
+
|---|---|
|
|
948
|
+
| Title (≤60 chars) | 20 |
|
|
949
|
+
| Description (≤160 chars) | 15 |
|
|
950
|
+
| Canonical URL | 10 |
|
|
951
|
+
| og:image | 15 |
|
|
952
|
+
| og:title | 5 |
|
|
953
|
+
| og:description | 5 |
|
|
954
|
+
| Twitter Card | 10 |
|
|
955
|
+
| Structured Data (JSON-LD) | 10 |
|
|
956
|
+
| Robots Directives | 5 |
|
|
957
|
+
| Hreflang | 5 |
|
|
913
958
|
|
|
914
959
|
<br />
|
|
915
960
|
|
|
916
|
-
|
|
961
|
+
### OG Image Generation — `react-ssr-seo-toolkit/og`
|
|
917
962
|
|
|
918
963
|
| Function | What It Does |
|
|
919
964
|
|---|---|
|
|
920
|
-
| `
|
|
921
|
-
| `toRouterLinks(config)` | Converts canonical + hreflang + additionalLinkTags → `LinkDescriptor[]`. Use as the return value of a route's `links()` export. |
|
|
965
|
+
| `createOGImageSVG(options)` | Returns a 1200×630 SVG string — serve as `image/svg+xml` or convert to PNG with `sharp` |
|
|
922
966
|
|
|
923
|
-
**
|
|
967
|
+
**Options:**
|
|
924
968
|
|
|
925
|
-
|
|
926
|
-
|
|
927
|
-
|
|
969
|
+
| Option | Type | Description |
|
|
970
|
+
|---|---|---|
|
|
971
|
+
| `title` | `string` | Main headline |
|
|
972
|
+
| `description` | `string?` | Subtitle / description |
|
|
973
|
+
| `template` | `"default" \| "article" \| "sports-event"` | Visual template |
|
|
974
|
+
| `brand` | `string?` | Brand name shown in corner |
|
|
975
|
+
| `backgroundColor` | `string?` | Override background color |
|
|
976
|
+
| `textColor` | `string?` | Override text color |
|
|
977
|
+
| `accentColor` | `string?` | Override accent / highlight color |
|
|
978
|
+
| `category` | `string?` | Category tag (article template) |
|
|
979
|
+
| `author` | `string?` | Author name (article template) |
|
|
980
|
+
| `dateString` | `string?` | Display date (article template) |
|
|
981
|
+
| `homeTeam` / `awayTeam` | `string?` | Team names (sports-event template) |
|
|
982
|
+
| `eventDate` | `string?` | Event date display (sports-event template) |
|
|
928
983
|
|
|
929
984
|
<br />
|
|
930
985
|
|
|
@@ -932,42 +987,17 @@ All return a plain object with `@context: "https://schema.org"` and `@type` set.
|
|
|
932
987
|
|
|
933
988
|
#### `<SEOHead>`
|
|
934
989
|
|
|
935
|
-
Renders all SEO tags
|
|
990
|
+
Renders all SEO tags inside `<head>`. Accepts the full `SEOConfig` plus an optional `nonce` for CSP.
|
|
936
991
|
|
|
937
992
|
```tsx
|
|
938
993
|
<SEOHead
|
|
939
994
|
title="My Page"
|
|
940
995
|
titleTemplate="%s | MySite"
|
|
941
|
-
description="Page description
|
|
996
|
+
description="Page description."
|
|
942
997
|
canonical="https://mysite.com/page"
|
|
943
998
|
robots={{ index: true, follow: true }}
|
|
944
|
-
openGraph={{
|
|
945
|
-
|
|
946
|
-
description: "For social sharing.",
|
|
947
|
-
type: "website",
|
|
948
|
-
url: "https://mysite.com/page",
|
|
949
|
-
siteName: "MySite",
|
|
950
|
-
locale: "en_US",
|
|
951
|
-
images: [{ url: "https://mysite.com/og.jpg", width: 1200, height: 630, alt: "Preview" }],
|
|
952
|
-
}}
|
|
953
|
-
twitter={{
|
|
954
|
-
card: "summary_large_image",
|
|
955
|
-
site: "@mysite",
|
|
956
|
-
creator: "@author",
|
|
957
|
-
title: "My Page",
|
|
958
|
-
image: "https://mysite.com/twitter.jpg",
|
|
959
|
-
}}
|
|
960
|
-
alternates={[
|
|
961
|
-
{ hreflang: "en", href: "https://mysite.com/en/page" },
|
|
962
|
-
{ hreflang: "es", href: "https://mysite.com/es/page" },
|
|
963
|
-
]}
|
|
964
|
-
additionalMetaTags={[
|
|
965
|
-
{ name: "author", content: "Jane Doe" },
|
|
966
|
-
]}
|
|
967
|
-
additionalLinkTags={[
|
|
968
|
-
{ rel: "icon", href: "/favicon.ico" },
|
|
969
|
-
]}
|
|
970
|
-
jsonLd={createArticleSchema({ headline: "...", url: "..." })}
|
|
999
|
+
openGraph={{ type: "website", images: [{ url: "...", width: 1200, height: 630 }] }}
|
|
1000
|
+
twitter={{ card: "summary_large_image", site: "@mysite" }}
|
|
971
1001
|
/>
|
|
972
1002
|
```
|
|
973
1003
|
|
|
@@ -979,6 +1009,53 @@ Standalone JSON-LD `<script>` tag renderer.
|
|
|
979
1009
|
<JsonLd data={createProductSchema({ name: "Widget", url: "...", price: 29.99 })} />
|
|
980
1010
|
```
|
|
981
1011
|
|
|
1012
|
+
#### `<SEOPreview>` — `react-ssr-seo-toolkit/components`
|
|
1013
|
+
|
|
1014
|
+
Renders a visual social card preview. Useful in dev mode or a CMS preview panel.
|
|
1015
|
+
|
|
1016
|
+
```tsx
|
|
1017
|
+
<SEOPreview config={seoConfig} platform="twitter" /> // Twitter card
|
|
1018
|
+
<SEOPreview config={seoConfig} platform="facebook" /> // Facebook / OG
|
|
1019
|
+
<SEOPreview config={seoConfig} platform="linkedin" /> // LinkedIn post
|
|
1020
|
+
<SEOPreview config={seoConfig} platform="google" /> // Google search result
|
|
1021
|
+
```
|
|
1022
|
+
|
|
1023
|
+
| Prop | Type | Default |
|
|
1024
|
+
|---|---|---|
|
|
1025
|
+
| `config` | `SEOConfig` | required |
|
|
1026
|
+
| `platform` | `"twitter" \| "facebook" \| "linkedin" \| "google"` | `"twitter"` |
|
|
1027
|
+
| `style` | `React.CSSProperties` | — |
|
|
1028
|
+
|
|
1029
|
+
<br />
|
|
1030
|
+
|
|
1031
|
+
### Framework Adapters
|
|
1032
|
+
|
|
1033
|
+
#### `react-ssr-seo-toolkit/adapters/nextjs`
|
|
1034
|
+
|
|
1035
|
+
| Function | What It Does |
|
|
1036
|
+
|---|---|
|
|
1037
|
+
| `toNextMetadata(config)` | Converts `SEOConfig` → Next.js App Router `Metadata` object |
|
|
1038
|
+
| `buildNextTitle(config)` | Returns the resolved title string |
|
|
1039
|
+
|
|
1040
|
+
#### `react-ssr-seo-toolkit/adapters/react-router`
|
|
1041
|
+
|
|
1042
|
+
| Function | What It Does |
|
|
1043
|
+
|---|---|
|
|
1044
|
+
| `toRouterMeta(config)` | Converts `SEOConfig` → `MetaDescriptor[]` for a route's `meta()` export |
|
|
1045
|
+
| `toRouterLinks(config)` | Converts canonical + hreflang → `LinkDescriptor[]` for a route's `links()` export |
|
|
1046
|
+
|
|
1047
|
+
<br />
|
|
1048
|
+
|
|
1049
|
+
### Utilities
|
|
1050
|
+
|
|
1051
|
+
| Function | What It Does |
|
|
1052
|
+
|---|---|
|
|
1053
|
+
| `safeJsonLdSerialize(data)` | Serialize JSON-LD safely — escapes `<`, `>`, `&` to prevent XSS |
|
|
1054
|
+
| `normalizeUrl(url)` | Trim whitespace, remove trailing slashes |
|
|
1055
|
+
| `buildFullUrl(base, path?)` | Combine base URL with path |
|
|
1056
|
+
| `omitEmpty(obj)` | Remove keys with `undefined`, `null`, or `""` values |
|
|
1057
|
+
| `deepMerge(base, override)` | Deep-merge two objects (arrays replaced, not concatenated) |
|
|
1058
|
+
|
|
982
1059
|
<br />
|
|
983
1060
|
|
|
984
1061
|
### TypeScript Types
|
|
@@ -986,22 +1063,28 @@ Standalone JSON-LD `<script>` tag renderer.
|
|
|
986
1063
|
```tsx
|
|
987
1064
|
import type {
|
|
988
1065
|
SEOConfig,
|
|
989
|
-
OpenGraphConfig,
|
|
990
|
-
|
|
991
|
-
OpenGraphType, // "website" | "article" | "product" | "profile" | ...
|
|
992
|
-
TwitterConfig,
|
|
993
|
-
TwitterCardType, // "summary" | "summary_large_image" | "app" | "player"
|
|
1066
|
+
OpenGraphConfig, OpenGraphImage, OpenGraphType,
|
|
1067
|
+
TwitterConfig, TwitterCardType,
|
|
994
1068
|
RobotsConfig,
|
|
995
1069
|
AlternateLink,
|
|
996
|
-
JSONLDBase,
|
|
997
|
-
|
|
998
|
-
|
|
999
|
-
WebsiteSchemaInput,
|
|
1000
|
-
ArticleSchemaInput,
|
|
1001
|
-
ProductSchemaInput,
|
|
1070
|
+
JSONLDBase, BreadcrumbItem,
|
|
1071
|
+
OrganizationSchemaInput, WebsiteSchemaInput,
|
|
1072
|
+
ArticleSchemaInput, ProductSchemaInput,
|
|
1002
1073
|
FAQItem,
|
|
1003
|
-
|
|
1004
|
-
|
|
1074
|
+
EventSchemaInput, EventStatus, EventAttendanceMode,
|
|
1075
|
+
PersonSchemaInput,
|
|
1076
|
+
RecipeSchemaInput, RecipeInstruction,
|
|
1077
|
+
JobPostingSchemaInput,
|
|
1078
|
+
// Sitemap
|
|
1079
|
+
SitemapRoute, GenerateSitemapOptions, ChangeFreq,
|
|
1080
|
+
RobotsRule, GenerateRobotsOptions,
|
|
1081
|
+
// Validation
|
|
1082
|
+
SEOValidationIssue, RouteWithSEO,
|
|
1083
|
+
SEOScoreResult, SEOScoreCheck,
|
|
1084
|
+
// OG Image
|
|
1085
|
+
OGImageOptions, OGImageTemplate,
|
|
1086
|
+
// Components
|
|
1087
|
+
SEOHeadProps, JsonLdProps, SEOPreviewProps,
|
|
1005
1088
|
} from "react-ssr-seo-toolkit";
|
|
1006
1089
|
```
|
|
1007
1090
|
|
|
@@ -1011,9 +1094,28 @@ import type {
|
|
|
1011
1094
|
|
|
1012
1095
|
<br />
|
|
1013
1096
|
|
|
1014
|
-
##
|
|
1097
|
+
## Entry Points
|
|
1098
|
+
|
|
1099
|
+
The package ships separate entry points so you only import what you need:
|
|
1015
1100
|
|
|
1016
|
-
|
|
1101
|
+
| Import | Contents |
|
|
1102
|
+
|---|---|
|
|
1103
|
+
| `react-ssr-seo-toolkit` | Everything — core, schema, utils, components |
|
|
1104
|
+
| `react-ssr-seo-toolkit/schema` | Schema generators only (no React) |
|
|
1105
|
+
| `react-ssr-seo-toolkit/sitemap` | `generateSitemap`, `generateRobots`, `autoBreadcrumb` |
|
|
1106
|
+
| `react-ssr-seo-toolkit/validation` | `validateSEO`, `getSEOScore`, `formatSEOScore` |
|
|
1107
|
+
| `react-ssr-seo-toolkit/og` | `createOGImageSVG` |
|
|
1108
|
+
| `react-ssr-seo-toolkit/components` | React components — `SEOHead`, `JsonLd`, `SEOPreview` |
|
|
1109
|
+
| `react-ssr-seo-toolkit/adapters/nextjs` | Next.js adapter |
|
|
1110
|
+
| `react-ssr-seo-toolkit/adapters/react-router` | React Router 7 adapter |
|
|
1111
|
+
|
|
1112
|
+
<br />
|
|
1113
|
+
|
|
1114
|
+
---
|
|
1115
|
+
|
|
1116
|
+
<br />
|
|
1117
|
+
|
|
1118
|
+
## Live Demo
|
|
1017
1119
|
|
|
1018
1120
|
```bash
|
|
1019
1121
|
git clone https://github.com/Tonmoy01/react-ssr-seo-toolkit.git
|
|
@@ -1022,19 +1124,7 @@ npm install
|
|
|
1022
1124
|
npm run demo
|
|
1023
1125
|
```
|
|
1024
1126
|
|
|
1025
|
-
Then visit [http://localhost:3000](http://localhost:3000)
|
|
1026
|
-
|
|
1027
|
-
| URL | Page | SEO Features |
|
|
1028
|
-
|---|---|---|
|
|
1029
|
-
| `/` | Home | Organization + Website schema, hreflang, OG images |
|
|
1030
|
-
| `/getting-started` | Getting Started | Installation guide with copy-paste examples |
|
|
1031
|
-
| `/article` | Article | Article schema, breadcrumbs, multiple authors, Twitter cards |
|
|
1032
|
-
| `/product` | Product | Product schema, pricing, ratings, availability |
|
|
1033
|
-
| `/faq` | FAQ | FAQPage schema with Q&A pairs |
|
|
1034
|
-
| `/noindex` | No-Index | Robots noindex directive |
|
|
1035
|
-
| `/api` | API Reference | Complete function and type documentation |
|
|
1036
|
-
|
|
1037
|
-
> **Tip:** Right-click any page and **View Page Source** to see all SEO tags in the raw HTML.
|
|
1127
|
+
Then visit [http://localhost:3000](http://localhost:3000).
|
|
1038
1128
|
|
|
1039
1129
|
<br />
|
|
1040
1130
|
|
|
@@ -1049,9 +1139,7 @@ npm install # install dependencies
|
|
|
1049
1139
|
npm run build # build the library
|
|
1050
1140
|
npm run dev # watch mode (auto-rebuild)
|
|
1051
1141
|
npm test # run tests
|
|
1052
|
-
npm run test:watch # tests in watch mode
|
|
1053
1142
|
npm run lint # type check
|
|
1054
|
-
npm run clean # clean build output
|
|
1055
1143
|
npm run demo # run demo server
|
|
1056
1144
|
```
|
|
1057
1145
|
|
|
@@ -1065,24 +1153,16 @@ npm run demo # run demo server
|
|
|
1065
1153
|
|
|
1066
1154
|
### "Cannot find module 'react-ssr-seo-toolkit'"
|
|
1067
1155
|
|
|
1068
|
-
Ensure the package is installed and your bundler supports the `exports` field in `package.json`.
|
|
1156
|
+
Ensure the package is installed and your bundler supports the `exports` field in `package.json`.
|
|
1069
1157
|
|
|
1070
1158
|
### Hydration mismatch warnings
|
|
1071
1159
|
|
|
1072
|
-
`<SEOHead>` produces deterministic output.
|
|
1160
|
+
`<SEOHead>` produces deterministic output. Ensure the same config object is used on both server and client. Avoid using `Date.now()` or random values in your SEO config.
|
|
1073
1161
|
|
|
1074
1162
|
### JSON-LD not appearing in page source
|
|
1075
1163
|
|
|
1076
1164
|
Make sure `<JsonLd>` is inside `<head>` and rendered during SSR — not in a client-only `useEffect`.
|
|
1077
1165
|
|
|
1078
|
-
### TypeScript errors
|
|
1079
|
-
|
|
1080
|
-
All types are exported. Import them directly:
|
|
1081
|
-
|
|
1082
|
-
```tsx
|
|
1083
|
-
import type { SEOConfig, OpenGraphConfig } from "react-ssr-seo-toolkit";
|
|
1084
|
-
```
|
|
1085
|
-
|
|
1086
1166
|
<br />
|
|
1087
1167
|
|
|
1088
1168
|
---
|