react-ssr-seo-toolkit 1.0.5 → 1.1.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 +117 -24
- package/dist/adapters/nextjs.cjs +2 -0
- package/dist/adapters/nextjs.cjs.map +1 -0
- package/dist/adapters/nextjs.d.cts +86 -0
- package/dist/adapters/nextjs.d.ts +86 -0
- package/dist/adapters/nextjs.js +2 -0
- package/dist/adapters/nextjs.js.map +1 -0
- package/dist/adapters/react-router.cjs +2 -0
- package/dist/adapters/react-router.cjs.map +1 -0
- package/dist/adapters/react-router.d.cts +71 -0
- package/dist/adapters/react-router.d.ts +71 -0
- package/dist/adapters/react-router.js +2 -0
- package/dist/adapters/react-router.js.map +1 -0
- package/dist/chunk-AYIAPQTP.js +2 -0
- package/dist/chunk-AYIAPQTP.js.map +1 -0
- package/dist/chunk-FKDMHECL.cjs +2 -0
- package/dist/chunk-FKDMHECL.cjs.map +1 -0
- package/dist/chunk-LP66SUGR.js +2 -0
- package/dist/chunk-LP66SUGR.js.map +1 -0
- package/dist/chunk-T7EB7Y2S.cjs +2 -0
- package/dist/chunk-T7EB7Y2S.cjs.map +1 -0
- package/dist/components.cjs +1 -1
- package/dist/components.js +1 -1
- package/dist/index.cjs +1 -1
- package/dist/index.js +1 -1
- package/package.json +21 -1
- package/dist/chunk-QBHCTDUJ.cjs +0 -2
- package/dist/chunk-QBHCTDUJ.cjs.map +0 -1
- package/dist/chunk-YMCW2G4X.js +0 -2
- package/dist/chunk-YMCW2G4X.js.map +0 -1
package/README.md
CHANGED
|
@@ -76,9 +76,9 @@ All in one package. Zero dependencies. Fully typed. SSR-safe.
|
|
|
76
76
|
|
|
77
77
|
| | Framework | Integration |
|
|
78
78
|
|:---:|---|---|
|
|
79
|
-
| <img src="https://img.shields.io/badge/-Next.js-000?style=flat-square&logo=nextdotjs" /> | **Next.js App Router** | `
|
|
79
|
+
| <img src="https://img.shields.io/badge/-Next.js-000?style=flat-square&logo=nextdotjs" /> | **Next.js App Router** | `toNextMetadata()` adapter → `generateMetadata()` |
|
|
80
80
|
| <img src="https://img.shields.io/badge/-Next.js-000?style=flat-square&logo=nextdotjs" /> | **Next.js Pages Router** | `<SEOHead>` inside `next/head` |
|
|
81
|
-
| <img src="https://img.shields.io/badge/-React_Router-CA4245?style=flat-square&logo=reactrouter&logoColor=white" /> | **React Router 7
|
|
81
|
+
| <img src="https://img.shields.io/badge/-React_Router-CA4245?style=flat-square&logo=reactrouter&logoColor=white" /> | **React Router 7** | `toRouterMeta()` adapter → `meta()` export |
|
|
82
82
|
| <img src="https://img.shields.io/badge/-Express-000?style=flat-square&logo=express" /> | **Express + React SSR** | `<SEOHead>` in `renderToString()` |
|
|
83
83
|
| <img src="https://img.shields.io/badge/-Remix-000?style=flat-square&logo=remix" /> | **Remix / Astro / Solid** | Pure utility functions (no React needed) |
|
|
84
84
|
|
|
@@ -588,32 +588,39 @@ const combined = composeSchemas(
|
|
|
588
588
|
|
|
589
589
|
### Next.js App Router
|
|
590
590
|
|
|
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
|
+
|
|
591
598
|
```tsx
|
|
592
599
|
// app/blog/[slug]/page.tsx
|
|
593
|
-
import {
|
|
594
|
-
|
|
595
|
-
|
|
596
|
-
} from "react-ssr-seo-toolkit";
|
|
600
|
+
import { toNextMetadata } from "react-ssr-seo-toolkit/adapters/nextjs";
|
|
601
|
+
import { createArticleSchema, safeJsonLdSerialize, buildCanonicalUrl } from "react-ssr-seo-toolkit";
|
|
602
|
+
import type { Metadata } from "next";
|
|
597
603
|
|
|
598
|
-
export async function generateMetadata({ params }) {
|
|
604
|
+
export async function generateMetadata({ params }): Promise<Metadata> {
|
|
599
605
|
const post = await getPost(params.slug);
|
|
600
606
|
|
|
601
|
-
return {
|
|
602
|
-
title:
|
|
603
|
-
|
|
604
|
-
|
|
605
|
-
|
|
606
|
-
},
|
|
607
|
+
return toNextMetadata({
|
|
608
|
+
title: post.title,
|
|
609
|
+
titleTemplate: "%s | My Blog",
|
|
610
|
+
description: post.excerpt,
|
|
611
|
+
canonical: buildCanonicalUrl("https://myblog.com", `/blog/${params.slug}`),
|
|
607
612
|
openGraph: {
|
|
608
613
|
title: post.title,
|
|
609
614
|
type: "article",
|
|
610
|
-
images: [{ url: post.image, width: 1200, height: 630 }],
|
|
615
|
+
images: [{ url: post.image, width: 1200, height: 630, alt: post.title }],
|
|
611
616
|
},
|
|
612
|
-
|
|
617
|
+
twitter: { card: "summary_large_image", creator: "@myblog" },
|
|
618
|
+
robots: { index: true, follow: true },
|
|
619
|
+
});
|
|
613
620
|
}
|
|
614
621
|
|
|
615
|
-
export default function BlogPost({ params }) {
|
|
616
|
-
const post = getPost(params.slug);
|
|
622
|
+
export default async function BlogPost({ params }) {
|
|
623
|
+
const post = await getPost(params.slug);
|
|
617
624
|
|
|
618
625
|
const schema = createArticleSchema({
|
|
619
626
|
headline: post.title,
|
|
@@ -669,7 +676,69 @@ export default function AboutPage() {
|
|
|
669
676
|
|
|
670
677
|
<br />
|
|
671
678
|
|
|
672
|
-
### React Router 7
|
|
679
|
+
### React Router 7
|
|
680
|
+
|
|
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
|
+
```tsx
|
|
690
|
+
// app/routes/about.tsx
|
|
691
|
+
import { toRouterMeta } from "react-ssr-seo-toolkit/adapters/react-router";
|
|
692
|
+
import { mergeSEOConfig, buildCanonicalUrl } from "react-ssr-seo-toolkit";
|
|
693
|
+
import { siteConfig, SITE_URL } from "../config/seo";
|
|
694
|
+
import type { Route } from "./+types/about";
|
|
695
|
+
|
|
696
|
+
export function meta({}: Route.MetaArgs) {
|
|
697
|
+
return toRouterMeta(
|
|
698
|
+
mergeSEOConfig(siteConfig, {
|
|
699
|
+
title: "About Us",
|
|
700
|
+
description: "Learn about our company.",
|
|
701
|
+
canonical: buildCanonicalUrl(SITE_URL, "/about"),
|
|
702
|
+
openGraph: { type: "website", title: "About Us" },
|
|
703
|
+
twitter: { card: "summary_large_image" },
|
|
704
|
+
})
|
|
705
|
+
);
|
|
706
|
+
}
|
|
707
|
+
|
|
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>`:**
|
|
673
742
|
|
|
674
743
|
```tsx
|
|
675
744
|
// app/root.tsx — only the root layout writes <html>
|
|
@@ -696,7 +765,7 @@ export default function Root() {
|
|
|
696
765
|
```
|
|
697
766
|
|
|
698
767
|
```tsx
|
|
699
|
-
// app/routes/about.tsx
|
|
768
|
+
// app/routes/about.tsx
|
|
700
769
|
import { mergeSEOConfig, buildCanonicalUrl } from "react-ssr-seo-toolkit";
|
|
701
770
|
import { siteConfig, SITE_URL } from "../config/seo";
|
|
702
771
|
|
|
@@ -710,11 +779,7 @@ export function loader() {
|
|
|
710
779
|
}
|
|
711
780
|
|
|
712
781
|
export default function AboutPage() {
|
|
713
|
-
return
|
|
714
|
-
<main>
|
|
715
|
-
<h1>About Us</h1>
|
|
716
|
-
</main>
|
|
717
|
-
);
|
|
782
|
+
return <main><h1>About Us</h1></main>;
|
|
718
783
|
}
|
|
719
784
|
```
|
|
720
785
|
|
|
@@ -835,6 +900,34 @@ All return a plain object with `@context: "https://schema.org"` and `@type` set.
|
|
|
835
900
|
|
|
836
901
|
<br />
|
|
837
902
|
|
|
903
|
+
### Framework Adapters
|
|
904
|
+
|
|
905
|
+
#### Next.js Adapter — `react-ssr-seo-toolkit/adapters/nextjs`
|
|
906
|
+
|
|
907
|
+
| Function | What It Does |
|
|
908
|
+
|---|---|
|
|
909
|
+
| `toNextMetadata(config)` | Converts `SEOConfig` → Next.js App Router `Metadata` object. Use inside `generateMetadata()` or `export const metadata`. |
|
|
910
|
+
| `buildNextTitle(config)` | Returns the resolved title string (applies `titleTemplate` if set). |
|
|
911
|
+
|
|
912
|
+
**Exported types:** `NextJSMetadata`, `NextJSMetadataTitle`, `NextJSMetadataImage`, `NextJSMetadataRobots`, `NextJSMetadataOpenGraph`, `NextJSMetadataTwitter`, `NextJSMetadataAlternates`
|
|
913
|
+
|
|
914
|
+
<br />
|
|
915
|
+
|
|
916
|
+
#### React Router 7 Adapter — `react-ssr-seo-toolkit/adapters/react-router`
|
|
917
|
+
|
|
918
|
+
| Function | What It Does |
|
|
919
|
+
|---|---|
|
|
920
|
+
| `toRouterMeta(config)` | Converts `SEOConfig` → `MetaDescriptor[]`. Use as the return value of a route's `meta()` export. Includes title, description, robots, OG, Twitter, hreflang, canonical, and custom tags. |
|
|
921
|
+
| `toRouterLinks(config)` | Converts canonical + hreflang + additionalLinkTags → `LinkDescriptor[]`. Use as the return value of a route's `links()` export. |
|
|
922
|
+
|
|
923
|
+
**Exported types:** `RouterMetaDescriptor`, `RouterTitleDescriptor`, `RouterNameMetaDescriptor`, `RouterPropertyMetaDescriptor`, `RouterLinkDescriptor`
|
|
924
|
+
|
|
925
|
+
<br />
|
|
926
|
+
|
|
927
|
+
---
|
|
928
|
+
|
|
929
|
+
<br />
|
|
930
|
+
|
|
838
931
|
### React Components
|
|
839
932
|
|
|
840
933
|
#### `<SEOHead>`
|
|
@@ -0,0 +1,2 @@
|
|
|
1
|
+
'use strict';var chunkFKDMHECL_cjs=require('../chunk-FKDMHECL.cjs');function d(a){let r={};a.title&&(a.titleTemplate?r.title={default:a.title,template:a.titleTemplate}:r.title=a.title),a.description?.trim()&&(r.description=a.description.trim());let o=!!a.canonical?.trim(),s=!!a.alternates?.length;if((o||s)&&(r.alternates={},o&&(r.alternates.canonical=a.canonical.trim()),s)){let t={};for(let e of a.alternates)t[e.hreflang]=e.href;r.alternates.languages=t;}if(a.robots){let t=a.robots,e={};t.index!==void 0&&(e.index=t.index),t.follow!==void 0&&(e.follow=t.follow),t.noarchive&&(e.noarchive=t.noarchive),t.nosnippet&&(e.nosnippet=t.nosnippet),t.noimageindex&&(e.noimageindex=t.noimageindex),t.notranslate&&(e.notranslate=t.notranslate),t.maxSnippet!==void 0&&(e["max-snippet"]=t.maxSnippet),t.maxImagePreview&&(e["max-image-preview"]=t.maxImagePreview),t.maxVideoPreview!==void 0&&(e["max-video-preview"]=t.maxVideoPreview),r.robots=e;}if(a.openGraph){let t=a.openGraph,e={};t.title&&(e.title=t.title),t.description&&(e.description=t.description),t.url&&(e.url=t.url),t.siteName&&(e.siteName=t.siteName),t.type&&(e.type=t.type),t.locale&&(e.locale=t.locale),t.images?.length&&(e.images=t.images.map(i=>{let n={url:i.url};return i.alt&&(n.alt=i.alt),i.width&&(n.width=i.width),i.height&&(n.height=i.height),i.type&&(n.type=i.type),n})),r.openGraph=e;}if(a.twitter){let t=a.twitter,e={};t.card&&(e.card=t.card),t.site&&(e.site=t.site),t.creator&&(e.creator=t.creator),t.title&&(e.title=t.title),t.description&&(e.description=t.description),t.image&&(e.images=[t.image]),r.twitter=e;}if(a.additionalMetaTags?.length){let t={};for(let e of a.additionalMetaTags){let i=e.name??e.property;i&&(t[i]=e.content);}Object.keys(t).length>0&&(r.other=t);}return r}function m(a){return chunkFKDMHECL_cjs.i(a.title,a.titleTemplate)}exports.buildNextTitle=m;exports.toNextMetadata=d;//# sourceMappingURL=nextjs.cjs.map
|
|
2
|
+
//# sourceMappingURL=nextjs.cjs.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../../src/adapters/nextjs/index.ts"],"names":["toNextMetadata","config","meta","hasCanonical","hasAlternates","languages","alt","r","robots","og","openGraph","img","image","tw","twitter","other","tag","key","buildNextTitle","buildTitle"],"mappings":"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","file":"nextjs.cjs","sourcesContent":["import type { SEOConfig, OpenGraphImage } from \"../../types/index.js\";\nimport { buildTitle } from \"../../core/index.js\";\n\n// ─── Next.js-compatible Metadata type ────────────────────────\n// Mirrors Next.js App Router's Metadata interface without importing from 'next'.\n// Safe to use even when 'next' is not installed.\n\nexport interface NextJSMetadataTitle {\n /** Page-level title, replaces %s in template */\n default?: string;\n /** Template applied to child pages, e.g. \"%s | MySite\" */\n template?: string;\n /** Absolute title, ignores template */\n absolute?: string;\n}\n\nexport interface NextJSMetadataImage {\n url: string;\n alt?: string;\n width?: number;\n height?: number;\n type?: string;\n}\n\nexport interface NextJSMetadataRobots {\n index?: boolean;\n follow?: boolean;\n noarchive?: boolean;\n nosnippet?: boolean;\n noimageindex?: boolean;\n notranslate?: boolean;\n \"max-snippet\"?: number;\n \"max-image-preview\"?: \"none\" | \"standard\" | \"large\";\n \"max-video-preview\"?: number;\n}\n\nexport interface NextJSMetadataOpenGraph {\n title?: string;\n description?: string;\n url?: string;\n siteName?: string;\n images?: NextJSMetadataImage[];\n locale?: string;\n type?: string;\n}\n\nexport interface NextJSMetadataTwitter {\n card?: \"summary\" | \"summary_large_image\" | \"app\" | \"player\";\n site?: string;\n creator?: string;\n title?: string;\n description?: string;\n images?: string[];\n}\n\nexport interface NextJSMetadataAlternates {\n canonical?: string;\n languages?: Record<string, string>;\n}\n\n/**\n * Minimal subset of Next.js App Router `Metadata` type.\n * Fully compatible — pass this to `generateMetadata()` or `export const metadata`.\n */\nexport interface NextJSMetadata {\n title?: string | NextJSMetadataTitle;\n description?: string;\n robots?: NextJSMetadataRobots;\n openGraph?: NextJSMetadataOpenGraph;\n twitter?: NextJSMetadataTwitter;\n alternates?: NextJSMetadataAlternates;\n other?: Record<string, string | number | Array<string | number>>;\n}\n\n// ─── Converter ────────────────────────────────────────────────\n\n/**\n * Converts a `SEOConfig` to a Next.js App Router compatible `Metadata` object.\n *\n * @example\n * // app/about/page.tsx\n * import { toNextMetadata } from 'react-ssr-seo-toolkit/adapters/nextjs';\n *\n * export function generateMetadata(): Metadata {\n * return toNextMetadata({\n * title: 'About Us',\n * titleTemplate: '%s | MySite',\n * description: 'Learn more about us.',\n * canonical: 'https://mysite.com/about',\n * });\n * }\n */\nexport function toNextMetadata(config: SEOConfig): NextJSMetadata {\n const meta: NextJSMetadata = {};\n\n // Title\n if (config.title) {\n if (config.titleTemplate) {\n meta.title = {\n default: config.title,\n template: config.titleTemplate,\n };\n } else {\n meta.title = config.title;\n }\n }\n\n // Description\n if (config.description?.trim()) {\n meta.description = config.description.trim();\n }\n\n // Canonical + hreflang → alternates\n const hasCanonical = !!config.canonical?.trim();\n const hasAlternates = !!config.alternates?.length;\n\n if (hasCanonical || hasAlternates) {\n meta.alternates = {};\n\n if (hasCanonical) {\n meta.alternates.canonical = config.canonical!.trim();\n }\n\n if (hasAlternates) {\n const languages: Record<string, string> = {};\n for (const alt of config.alternates!) {\n languages[alt.hreflang] = alt.href;\n }\n meta.alternates.languages = languages;\n }\n }\n\n // Robots\n if (config.robots) {\n const r = config.robots;\n const robots: NextJSMetadataRobots = {};\n\n if (r.index !== undefined) robots.index = r.index;\n if (r.follow !== undefined) robots.follow = r.follow;\n if (r.noarchive) robots.noarchive = r.noarchive;\n if (r.nosnippet) robots.nosnippet = r.nosnippet;\n if (r.noimageindex) robots.noimageindex = r.noimageindex;\n if (r.notranslate) robots.notranslate = r.notranslate;\n if (r.maxSnippet !== undefined) robots[\"max-snippet\"] = r.maxSnippet;\n if (r.maxImagePreview) robots[\"max-image-preview\"] = r.maxImagePreview;\n if (r.maxVideoPreview !== undefined)\n robots[\"max-video-preview\"] = r.maxVideoPreview;\n\n meta.robots = robots;\n }\n\n // Open Graph\n if (config.openGraph) {\n const og = config.openGraph;\n const openGraph: NextJSMetadataOpenGraph = {};\n\n if (og.title) openGraph.title = og.title;\n if (og.description) openGraph.description = og.description;\n if (og.url) openGraph.url = og.url;\n if (og.siteName) openGraph.siteName = og.siteName;\n if (og.type) openGraph.type = og.type;\n if (og.locale) openGraph.locale = og.locale;\n\n if (og.images?.length) {\n openGraph.images = og.images.map((img: OpenGraphImage) => {\n const image: NextJSMetadataImage = { url: img.url };\n if (img.alt) image.alt = img.alt;\n if (img.width) image.width = img.width;\n if (img.height) image.height = img.height;\n if (img.type) image.type = img.type;\n return image;\n });\n }\n\n meta.openGraph = openGraph;\n }\n\n // Twitter\n if (config.twitter) {\n const tw = config.twitter;\n const twitter: NextJSMetadataTwitter = {};\n\n if (tw.card) twitter.card = tw.card;\n if (tw.site) twitter.site = tw.site;\n if (tw.creator) twitter.creator = tw.creator;\n if (tw.title) twitter.title = tw.title;\n if (tw.description) twitter.description = tw.description;\n if (tw.image) twitter.images = [tw.image];\n\n meta.twitter = twitter;\n }\n\n // Additional meta tags → other (name-based only; property-based are not\n // expressible in Next.js Metadata.other and are skipped)\n if (config.additionalMetaTags?.length) {\n const other: Record<string, string> = {};\n for (const tag of config.additionalMetaTags) {\n const key = tag.name ?? tag.property;\n if (key) other[key] = tag.content;\n }\n if (Object.keys(other).length > 0) meta.other = other;\n }\n\n return meta;\n}\n\n/**\n * Builds the resolved title string exactly as Next.js would display it.\n * Useful for `<title>` in Pages Router or for og:title overrides.\n */\nexport function buildNextTitle(config: SEOConfig): string {\n return buildTitle(config.title, config.titleTemplate);\n}\n"]}
|
|
@@ -0,0 +1,86 @@
|
|
|
1
|
+
import { S as SEOConfig } from '../index-Dr2yktvz.cjs';
|
|
2
|
+
|
|
3
|
+
interface NextJSMetadataTitle {
|
|
4
|
+
/** Page-level title, replaces %s in template */
|
|
5
|
+
default?: string;
|
|
6
|
+
/** Template applied to child pages, e.g. "%s | MySite" */
|
|
7
|
+
template?: string;
|
|
8
|
+
/** Absolute title, ignores template */
|
|
9
|
+
absolute?: string;
|
|
10
|
+
}
|
|
11
|
+
interface NextJSMetadataImage {
|
|
12
|
+
url: string;
|
|
13
|
+
alt?: string;
|
|
14
|
+
width?: number;
|
|
15
|
+
height?: number;
|
|
16
|
+
type?: string;
|
|
17
|
+
}
|
|
18
|
+
interface NextJSMetadataRobots {
|
|
19
|
+
index?: boolean;
|
|
20
|
+
follow?: boolean;
|
|
21
|
+
noarchive?: boolean;
|
|
22
|
+
nosnippet?: boolean;
|
|
23
|
+
noimageindex?: boolean;
|
|
24
|
+
notranslate?: boolean;
|
|
25
|
+
"max-snippet"?: number;
|
|
26
|
+
"max-image-preview"?: "none" | "standard" | "large";
|
|
27
|
+
"max-video-preview"?: number;
|
|
28
|
+
}
|
|
29
|
+
interface NextJSMetadataOpenGraph {
|
|
30
|
+
title?: string;
|
|
31
|
+
description?: string;
|
|
32
|
+
url?: string;
|
|
33
|
+
siteName?: string;
|
|
34
|
+
images?: NextJSMetadataImage[];
|
|
35
|
+
locale?: string;
|
|
36
|
+
type?: string;
|
|
37
|
+
}
|
|
38
|
+
interface NextJSMetadataTwitter {
|
|
39
|
+
card?: "summary" | "summary_large_image" | "app" | "player";
|
|
40
|
+
site?: string;
|
|
41
|
+
creator?: string;
|
|
42
|
+
title?: string;
|
|
43
|
+
description?: string;
|
|
44
|
+
images?: string[];
|
|
45
|
+
}
|
|
46
|
+
interface NextJSMetadataAlternates {
|
|
47
|
+
canonical?: string;
|
|
48
|
+
languages?: Record<string, string>;
|
|
49
|
+
}
|
|
50
|
+
/**
|
|
51
|
+
* Minimal subset of Next.js App Router `Metadata` type.
|
|
52
|
+
* Fully compatible — pass this to `generateMetadata()` or `export const metadata`.
|
|
53
|
+
*/
|
|
54
|
+
interface NextJSMetadata {
|
|
55
|
+
title?: string | NextJSMetadataTitle;
|
|
56
|
+
description?: string;
|
|
57
|
+
robots?: NextJSMetadataRobots;
|
|
58
|
+
openGraph?: NextJSMetadataOpenGraph;
|
|
59
|
+
twitter?: NextJSMetadataTwitter;
|
|
60
|
+
alternates?: NextJSMetadataAlternates;
|
|
61
|
+
other?: Record<string, string | number | Array<string | number>>;
|
|
62
|
+
}
|
|
63
|
+
/**
|
|
64
|
+
* Converts a `SEOConfig` to a Next.js App Router compatible `Metadata` object.
|
|
65
|
+
*
|
|
66
|
+
* @example
|
|
67
|
+
* // app/about/page.tsx
|
|
68
|
+
* import { toNextMetadata } from 'react-ssr-seo-toolkit/adapters/nextjs';
|
|
69
|
+
*
|
|
70
|
+
* export function generateMetadata(): Metadata {
|
|
71
|
+
* return toNextMetadata({
|
|
72
|
+
* title: 'About Us',
|
|
73
|
+
* titleTemplate: '%s | MySite',
|
|
74
|
+
* description: 'Learn more about us.',
|
|
75
|
+
* canonical: 'https://mysite.com/about',
|
|
76
|
+
* });
|
|
77
|
+
* }
|
|
78
|
+
*/
|
|
79
|
+
declare function toNextMetadata(config: SEOConfig): NextJSMetadata;
|
|
80
|
+
/**
|
|
81
|
+
* Builds the resolved title string exactly as Next.js would display it.
|
|
82
|
+
* Useful for `<title>` in Pages Router or for og:title overrides.
|
|
83
|
+
*/
|
|
84
|
+
declare function buildNextTitle(config: SEOConfig): string;
|
|
85
|
+
|
|
86
|
+
export { type NextJSMetadata, type NextJSMetadataAlternates, type NextJSMetadataImage, type NextJSMetadataOpenGraph, type NextJSMetadataRobots, type NextJSMetadataTitle, type NextJSMetadataTwitter, buildNextTitle, toNextMetadata };
|
|
@@ -0,0 +1,86 @@
|
|
|
1
|
+
import { S as SEOConfig } from '../index-Dr2yktvz.js';
|
|
2
|
+
|
|
3
|
+
interface NextJSMetadataTitle {
|
|
4
|
+
/** Page-level title, replaces %s in template */
|
|
5
|
+
default?: string;
|
|
6
|
+
/** Template applied to child pages, e.g. "%s | MySite" */
|
|
7
|
+
template?: string;
|
|
8
|
+
/** Absolute title, ignores template */
|
|
9
|
+
absolute?: string;
|
|
10
|
+
}
|
|
11
|
+
interface NextJSMetadataImage {
|
|
12
|
+
url: string;
|
|
13
|
+
alt?: string;
|
|
14
|
+
width?: number;
|
|
15
|
+
height?: number;
|
|
16
|
+
type?: string;
|
|
17
|
+
}
|
|
18
|
+
interface NextJSMetadataRobots {
|
|
19
|
+
index?: boolean;
|
|
20
|
+
follow?: boolean;
|
|
21
|
+
noarchive?: boolean;
|
|
22
|
+
nosnippet?: boolean;
|
|
23
|
+
noimageindex?: boolean;
|
|
24
|
+
notranslate?: boolean;
|
|
25
|
+
"max-snippet"?: number;
|
|
26
|
+
"max-image-preview"?: "none" | "standard" | "large";
|
|
27
|
+
"max-video-preview"?: number;
|
|
28
|
+
}
|
|
29
|
+
interface NextJSMetadataOpenGraph {
|
|
30
|
+
title?: string;
|
|
31
|
+
description?: string;
|
|
32
|
+
url?: string;
|
|
33
|
+
siteName?: string;
|
|
34
|
+
images?: NextJSMetadataImage[];
|
|
35
|
+
locale?: string;
|
|
36
|
+
type?: string;
|
|
37
|
+
}
|
|
38
|
+
interface NextJSMetadataTwitter {
|
|
39
|
+
card?: "summary" | "summary_large_image" | "app" | "player";
|
|
40
|
+
site?: string;
|
|
41
|
+
creator?: string;
|
|
42
|
+
title?: string;
|
|
43
|
+
description?: string;
|
|
44
|
+
images?: string[];
|
|
45
|
+
}
|
|
46
|
+
interface NextJSMetadataAlternates {
|
|
47
|
+
canonical?: string;
|
|
48
|
+
languages?: Record<string, string>;
|
|
49
|
+
}
|
|
50
|
+
/**
|
|
51
|
+
* Minimal subset of Next.js App Router `Metadata` type.
|
|
52
|
+
* Fully compatible — pass this to `generateMetadata()` or `export const metadata`.
|
|
53
|
+
*/
|
|
54
|
+
interface NextJSMetadata {
|
|
55
|
+
title?: string | NextJSMetadataTitle;
|
|
56
|
+
description?: string;
|
|
57
|
+
robots?: NextJSMetadataRobots;
|
|
58
|
+
openGraph?: NextJSMetadataOpenGraph;
|
|
59
|
+
twitter?: NextJSMetadataTwitter;
|
|
60
|
+
alternates?: NextJSMetadataAlternates;
|
|
61
|
+
other?: Record<string, string | number | Array<string | number>>;
|
|
62
|
+
}
|
|
63
|
+
/**
|
|
64
|
+
* Converts a `SEOConfig` to a Next.js App Router compatible `Metadata` object.
|
|
65
|
+
*
|
|
66
|
+
* @example
|
|
67
|
+
* // app/about/page.tsx
|
|
68
|
+
* import { toNextMetadata } from 'react-ssr-seo-toolkit/adapters/nextjs';
|
|
69
|
+
*
|
|
70
|
+
* export function generateMetadata(): Metadata {
|
|
71
|
+
* return toNextMetadata({
|
|
72
|
+
* title: 'About Us',
|
|
73
|
+
* titleTemplate: '%s | MySite',
|
|
74
|
+
* description: 'Learn more about us.',
|
|
75
|
+
* canonical: 'https://mysite.com/about',
|
|
76
|
+
* });
|
|
77
|
+
* }
|
|
78
|
+
*/
|
|
79
|
+
declare function toNextMetadata(config: SEOConfig): NextJSMetadata;
|
|
80
|
+
/**
|
|
81
|
+
* Builds the resolved title string exactly as Next.js would display it.
|
|
82
|
+
* Useful for `<title>` in Pages Router or for og:title overrides.
|
|
83
|
+
*/
|
|
84
|
+
declare function buildNextTitle(config: SEOConfig): string;
|
|
85
|
+
|
|
86
|
+
export { type NextJSMetadata, type NextJSMetadataAlternates, type NextJSMetadataImage, type NextJSMetadataOpenGraph, type NextJSMetadataRobots, type NextJSMetadataTitle, type NextJSMetadataTwitter, buildNextTitle, toNextMetadata };
|
|
@@ -0,0 +1,2 @@
|
|
|
1
|
+
import {i}from'../chunk-LP66SUGR.js';function d(a){let r={};a.title&&(a.titleTemplate?r.title={default:a.title,template:a.titleTemplate}:r.title=a.title),a.description?.trim()&&(r.description=a.description.trim());let o=!!a.canonical?.trim(),s=!!a.alternates?.length;if((o||s)&&(r.alternates={},o&&(r.alternates.canonical=a.canonical.trim()),s)){let t={};for(let e of a.alternates)t[e.hreflang]=e.href;r.alternates.languages=t;}if(a.robots){let t=a.robots,e={};t.index!==void 0&&(e.index=t.index),t.follow!==void 0&&(e.follow=t.follow),t.noarchive&&(e.noarchive=t.noarchive),t.nosnippet&&(e.nosnippet=t.nosnippet),t.noimageindex&&(e.noimageindex=t.noimageindex),t.notranslate&&(e.notranslate=t.notranslate),t.maxSnippet!==void 0&&(e["max-snippet"]=t.maxSnippet),t.maxImagePreview&&(e["max-image-preview"]=t.maxImagePreview),t.maxVideoPreview!==void 0&&(e["max-video-preview"]=t.maxVideoPreview),r.robots=e;}if(a.openGraph){let t=a.openGraph,e={};t.title&&(e.title=t.title),t.description&&(e.description=t.description),t.url&&(e.url=t.url),t.siteName&&(e.siteName=t.siteName),t.type&&(e.type=t.type),t.locale&&(e.locale=t.locale),t.images?.length&&(e.images=t.images.map(i=>{let n={url:i.url};return i.alt&&(n.alt=i.alt),i.width&&(n.width=i.width),i.height&&(n.height=i.height),i.type&&(n.type=i.type),n})),r.openGraph=e;}if(a.twitter){let t=a.twitter,e={};t.card&&(e.card=t.card),t.site&&(e.site=t.site),t.creator&&(e.creator=t.creator),t.title&&(e.title=t.title),t.description&&(e.description=t.description),t.image&&(e.images=[t.image]),r.twitter=e;}if(a.additionalMetaTags?.length){let t={};for(let e of a.additionalMetaTags){let i=e.name??e.property;i&&(t[i]=e.content);}Object.keys(t).length>0&&(r.other=t);}return r}function m(a){return i(a.title,a.titleTemplate)}export{m as buildNextTitle,d as toNextMetadata};//# sourceMappingURL=nextjs.js.map
|
|
2
|
+
//# sourceMappingURL=nextjs.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../../src/adapters/nextjs/index.ts"],"names":["toNextMetadata","config","meta","hasCanonical","hasAlternates","languages","alt","r","robots","og","openGraph","img","image","tw","twitter","other","tag","key","buildNextTitle","buildTitle"],"mappings":"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","file":"nextjs.js","sourcesContent":["import type { SEOConfig, OpenGraphImage } from \"../../types/index.js\";\nimport { buildTitle } from \"../../core/index.js\";\n\n// ─── Next.js-compatible Metadata type ────────────────────────\n// Mirrors Next.js App Router's Metadata interface without importing from 'next'.\n// Safe to use even when 'next' is not installed.\n\nexport interface NextJSMetadataTitle {\n /** Page-level title, replaces %s in template */\n default?: string;\n /** Template applied to child pages, e.g. \"%s | MySite\" */\n template?: string;\n /** Absolute title, ignores template */\n absolute?: string;\n}\n\nexport interface NextJSMetadataImage {\n url: string;\n alt?: string;\n width?: number;\n height?: number;\n type?: string;\n}\n\nexport interface NextJSMetadataRobots {\n index?: boolean;\n follow?: boolean;\n noarchive?: boolean;\n nosnippet?: boolean;\n noimageindex?: boolean;\n notranslate?: boolean;\n \"max-snippet\"?: number;\n \"max-image-preview\"?: \"none\" | \"standard\" | \"large\";\n \"max-video-preview\"?: number;\n}\n\nexport interface NextJSMetadataOpenGraph {\n title?: string;\n description?: string;\n url?: string;\n siteName?: string;\n images?: NextJSMetadataImage[];\n locale?: string;\n type?: string;\n}\n\nexport interface NextJSMetadataTwitter {\n card?: \"summary\" | \"summary_large_image\" | \"app\" | \"player\";\n site?: string;\n creator?: string;\n title?: string;\n description?: string;\n images?: string[];\n}\n\nexport interface NextJSMetadataAlternates {\n canonical?: string;\n languages?: Record<string, string>;\n}\n\n/**\n * Minimal subset of Next.js App Router `Metadata` type.\n * Fully compatible — pass this to `generateMetadata()` or `export const metadata`.\n */\nexport interface NextJSMetadata {\n title?: string | NextJSMetadataTitle;\n description?: string;\n robots?: NextJSMetadataRobots;\n openGraph?: NextJSMetadataOpenGraph;\n twitter?: NextJSMetadataTwitter;\n alternates?: NextJSMetadataAlternates;\n other?: Record<string, string | number | Array<string | number>>;\n}\n\n// ─── Converter ────────────────────────────────────────────────\n\n/**\n * Converts a `SEOConfig` to a Next.js App Router compatible `Metadata` object.\n *\n * @example\n * // app/about/page.tsx\n * import { toNextMetadata } from 'react-ssr-seo-toolkit/adapters/nextjs';\n *\n * export function generateMetadata(): Metadata {\n * return toNextMetadata({\n * title: 'About Us',\n * titleTemplate: '%s | MySite',\n * description: 'Learn more about us.',\n * canonical: 'https://mysite.com/about',\n * });\n * }\n */\nexport function toNextMetadata(config: SEOConfig): NextJSMetadata {\n const meta: NextJSMetadata = {};\n\n // Title\n if (config.title) {\n if (config.titleTemplate) {\n meta.title = {\n default: config.title,\n template: config.titleTemplate,\n };\n } else {\n meta.title = config.title;\n }\n }\n\n // Description\n if (config.description?.trim()) {\n meta.description = config.description.trim();\n }\n\n // Canonical + hreflang → alternates\n const hasCanonical = !!config.canonical?.trim();\n const hasAlternates = !!config.alternates?.length;\n\n if (hasCanonical || hasAlternates) {\n meta.alternates = {};\n\n if (hasCanonical) {\n meta.alternates.canonical = config.canonical!.trim();\n }\n\n if (hasAlternates) {\n const languages: Record<string, string> = {};\n for (const alt of config.alternates!) {\n languages[alt.hreflang] = alt.href;\n }\n meta.alternates.languages = languages;\n }\n }\n\n // Robots\n if (config.robots) {\n const r = config.robots;\n const robots: NextJSMetadataRobots = {};\n\n if (r.index !== undefined) robots.index = r.index;\n if (r.follow !== undefined) robots.follow = r.follow;\n if (r.noarchive) robots.noarchive = r.noarchive;\n if (r.nosnippet) robots.nosnippet = r.nosnippet;\n if (r.noimageindex) robots.noimageindex = r.noimageindex;\n if (r.notranslate) robots.notranslate = r.notranslate;\n if (r.maxSnippet !== undefined) robots[\"max-snippet\"] = r.maxSnippet;\n if (r.maxImagePreview) robots[\"max-image-preview\"] = r.maxImagePreview;\n if (r.maxVideoPreview !== undefined)\n robots[\"max-video-preview\"] = r.maxVideoPreview;\n\n meta.robots = robots;\n }\n\n // Open Graph\n if (config.openGraph) {\n const og = config.openGraph;\n const openGraph: NextJSMetadataOpenGraph = {};\n\n if (og.title) openGraph.title = og.title;\n if (og.description) openGraph.description = og.description;\n if (og.url) openGraph.url = og.url;\n if (og.siteName) openGraph.siteName = og.siteName;\n if (og.type) openGraph.type = og.type;\n if (og.locale) openGraph.locale = og.locale;\n\n if (og.images?.length) {\n openGraph.images = og.images.map((img: OpenGraphImage) => {\n const image: NextJSMetadataImage = { url: img.url };\n if (img.alt) image.alt = img.alt;\n if (img.width) image.width = img.width;\n if (img.height) image.height = img.height;\n if (img.type) image.type = img.type;\n return image;\n });\n }\n\n meta.openGraph = openGraph;\n }\n\n // Twitter\n if (config.twitter) {\n const tw = config.twitter;\n const twitter: NextJSMetadataTwitter = {};\n\n if (tw.card) twitter.card = tw.card;\n if (tw.site) twitter.site = tw.site;\n if (tw.creator) twitter.creator = tw.creator;\n if (tw.title) twitter.title = tw.title;\n if (tw.description) twitter.description = tw.description;\n if (tw.image) twitter.images = [tw.image];\n\n meta.twitter = twitter;\n }\n\n // Additional meta tags → other (name-based only; property-based are not\n // expressible in Next.js Metadata.other and are skipped)\n if (config.additionalMetaTags?.length) {\n const other: Record<string, string> = {};\n for (const tag of config.additionalMetaTags) {\n const key = tag.name ?? tag.property;\n if (key) other[key] = tag.content;\n }\n if (Object.keys(other).length > 0) meta.other = other;\n }\n\n return meta;\n}\n\n/**\n * Builds the resolved title string exactly as Next.js would display it.\n * Useful for `<title>` in Pages Router or for og:title overrides.\n */\nexport function buildNextTitle(config: SEOConfig): string {\n return buildTitle(config.title, config.titleTemplate);\n}\n"]}
|
|
@@ -0,0 +1,2 @@
|
|
|
1
|
+
'use strict';var chunkFKDMHECL_cjs=require('../chunk-FKDMHECL.cjs');function h(e){let r=[],n=chunkFKDMHECL_cjs.i(e.title,e.titleTemplate);n&&r.push({title:n}),e.description?.trim()&&r.push({name:"description",content:e.description.trim()}),e.canonical?.trim()&&r.push({tagName:"link",rel:"canonical",href:e.canonical.trim()});let s=chunkFKDMHECL_cjs.l(e.robots);s&&r.push({name:"robots",content:s});for(let t of chunkFKDMHECL_cjs.o(e.openGraph))r.push({property:t.property,content:t.content});for(let t of chunkFKDMHECL_cjs.p(e.twitter))r.push({name:t.name,content:t.content});for(let t of chunkFKDMHECL_cjs.q(e.alternates))r.push({tagName:"link",rel:t.rel,href:t.href,hrefLang:t.hreflang});if(e.additionalMetaTags)for(let t of e.additionalMetaTags)t.name?r.push({name:t.name,content:t.content}):t.property&&r.push({property:t.property,content:t.content});if(e.additionalLinkTags)for(let t of e.additionalLinkTags)r.push({tagName:"link",rel:t.rel,href:t.href,...t.hreflang&&{hrefLang:t.hreflang},...t.type&&{type:t.type},...t.sizes&&{sizes:t.sizes}});return r}function c(e){let r=[];e.canonical?.trim()&&r.push({rel:"canonical",href:e.canonical.trim()});for(let n of chunkFKDMHECL_cjs.q(e.alternates))r.push({rel:n.rel,href:n.href,hrefLang:n.hreflang});if(e.additionalLinkTags)for(let n of e.additionalLinkTags)r.push({rel:n.rel,href:n.href,...n.hreflang&&{hrefLang:n.hreflang},...n.type&&{type:n.type},...n.sizes&&{sizes:n.sizes}});return r}exports.toRouterLinks=c;exports.toRouterMeta=h;//# sourceMappingURL=react-router.cjs.map
|
|
2
|
+
//# sourceMappingURL=react-router.cjs.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../../src/adapters/react-router/index.ts"],"names":["toRouterMeta","config","descriptors","resolvedTitle","buildTitle","robotsContent","buildRobotsDirectives","tag","buildOpenGraph","buildTwitterMetadata","link","buildAlternateLinks","toRouterLinks","links","alt"],"mappings":"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","file":"react-router.cjs","sourcesContent":["import type { SEOConfig } from \"../../types/index.js\";\nimport {\n buildTitle,\n buildRobotsDirectives,\n buildOpenGraph,\n buildTwitterMetadata,\n buildAlternateLinks,\n} from \"../../core/index.js\";\n\n// ─── Descriptor types ─────────────────────────────────────────\n// Mirrors React Router 7's MetaDescriptor and LinkDescriptor\n// without importing from 'react-router', so no peer dep is needed.\n\nexport type RouterTitleDescriptor = { title: string };\n\nexport type RouterNameMetaDescriptor = {\n name: string;\n content: string;\n};\n\nexport type RouterPropertyMetaDescriptor = {\n property: string;\n content: string;\n};\n\nexport type RouterLinkDescriptor = {\n tagName: \"link\";\n rel: string;\n href: string;\n hrefLang?: string;\n type?: string;\n sizes?: string;\n [key: string]: string | undefined;\n};\n\nexport type RouterMetaDescriptor =\n | RouterTitleDescriptor\n | RouterNameMetaDescriptor\n | RouterPropertyMetaDescriptor\n | RouterLinkDescriptor;\n\n// ─── toRouterMeta ─────────────────────────────────────────────\n\n/**\n * Converts a `SEOConfig` to a React Router 7 meta descriptor array.\n *\n * Use the return value inside your route's `meta()` export.\n * Includes title, description, robots, Open Graph, Twitter, hreflang,\n * canonical, and any additional meta/link tags.\n *\n * @example\n * // routes/about.tsx\n * import { toRouterMeta } from 'react-ssr-seo-toolkit/adapters/react-router';\n *\n * export function meta(): MetaDescriptor[] {\n * return toRouterMeta({\n * title: 'About Us',\n * titleTemplate: '%s | MySite',\n * description: 'Learn more about us.',\n * canonical: 'https://mysite.com/about',\n * openGraph: { type: 'website', title: 'About Us' },\n * });\n * }\n */\nexport function toRouterMeta(config: SEOConfig): RouterMetaDescriptor[] {\n const descriptors: RouterMetaDescriptor[] = [];\n\n // Title\n const resolvedTitle = buildTitle(config.title, config.titleTemplate);\n if (resolvedTitle) {\n descriptors.push({ title: resolvedTitle });\n }\n\n // Description\n if (config.description?.trim()) {\n descriptors.push({ name: \"description\", content: config.description.trim() });\n }\n\n // Canonical\n if (config.canonical?.trim()) {\n descriptors.push({\n tagName: \"link\",\n rel: \"canonical\",\n href: config.canonical.trim(),\n });\n }\n\n // Robots\n const robotsContent = buildRobotsDirectives(config.robots);\n if (robotsContent) {\n descriptors.push({ name: \"robots\", content: robotsContent });\n }\n\n // Open Graph\n for (const tag of buildOpenGraph(config.openGraph)) {\n descriptors.push({ property: tag.property, content: tag.content });\n }\n\n // Twitter\n for (const tag of buildTwitterMetadata(config.twitter)) {\n descriptors.push({ name: tag.name, content: tag.content });\n }\n\n // Hreflang alternates\n for (const link of buildAlternateLinks(config.alternates)) {\n descriptors.push({\n tagName: \"link\",\n rel: link.rel,\n href: link.href,\n hrefLang: link.hreflang,\n });\n }\n\n // Additional meta tags\n if (config.additionalMetaTags) {\n for (const tag of config.additionalMetaTags) {\n if (tag.name) {\n descriptors.push({ name: tag.name, content: tag.content });\n } else if (tag.property) {\n descriptors.push({ property: tag.property, content: tag.content });\n }\n }\n }\n\n // Additional link tags\n if (config.additionalLinkTags) {\n for (const link of config.additionalLinkTags) {\n descriptors.push({\n tagName: \"link\",\n rel: link.rel,\n href: link.href,\n ...(link.hreflang && { hrefLang: link.hreflang }),\n ...(link.type && { type: link.type }),\n ...(link.sizes && { sizes: link.sizes }),\n });\n }\n }\n\n return descriptors;\n}\n\n// ─── toRouterLinks ────────────────────────────────────────────\n\n/**\n * Converts link-type entries from `SEOConfig` to React Router 7 `LinkDescriptor[]`.\n *\n * Use the return value inside your route's `links()` export for canonical\n * and hreflang links. This is an alternative to including them in `meta()`.\n *\n * @example\n * // routes/about.tsx\n * import { toRouterLinks } from 'react-ssr-seo-toolkit/adapters/react-router';\n *\n * export function links() {\n * return toRouterLinks({\n * canonical: 'https://mysite.com/about',\n * alternates: [{ hreflang: 'en', href: 'https://mysite.com/about' }],\n * });\n * }\n */\nexport function toRouterLinks(\n config: Pick<SEOConfig, \"canonical\" | \"alternates\" | \"additionalLinkTags\">\n): Array<{ rel: string; href: string; hrefLang?: string; type?: string; sizes?: string }> {\n const links: Array<{\n rel: string;\n href: string;\n hrefLang?: string;\n type?: string;\n sizes?: string;\n }> = [];\n\n if (config.canonical?.trim()) {\n links.push({ rel: \"canonical\", href: config.canonical.trim() });\n }\n\n for (const alt of buildAlternateLinks(config.alternates)) {\n links.push({ rel: alt.rel, href: alt.href, hrefLang: alt.hreflang });\n }\n\n if (config.additionalLinkTags) {\n for (const link of config.additionalLinkTags) {\n links.push({\n rel: link.rel,\n href: link.href,\n ...(link.hreflang && { hrefLang: link.hreflang }),\n ...(link.type && { type: link.type }),\n ...(link.sizes && { sizes: link.sizes }),\n });\n }\n }\n\n return links;\n}\n"]}
|
|
@@ -0,0 +1,71 @@
|
|
|
1
|
+
import { S as SEOConfig } from '../index-Dr2yktvz.cjs';
|
|
2
|
+
|
|
3
|
+
type RouterTitleDescriptor = {
|
|
4
|
+
title: string;
|
|
5
|
+
};
|
|
6
|
+
type RouterNameMetaDescriptor = {
|
|
7
|
+
name: string;
|
|
8
|
+
content: string;
|
|
9
|
+
};
|
|
10
|
+
type RouterPropertyMetaDescriptor = {
|
|
11
|
+
property: string;
|
|
12
|
+
content: string;
|
|
13
|
+
};
|
|
14
|
+
type RouterLinkDescriptor = {
|
|
15
|
+
tagName: "link";
|
|
16
|
+
rel: string;
|
|
17
|
+
href: string;
|
|
18
|
+
hrefLang?: string;
|
|
19
|
+
type?: string;
|
|
20
|
+
sizes?: string;
|
|
21
|
+
[key: string]: string | undefined;
|
|
22
|
+
};
|
|
23
|
+
type RouterMetaDescriptor = RouterTitleDescriptor | RouterNameMetaDescriptor | RouterPropertyMetaDescriptor | RouterLinkDescriptor;
|
|
24
|
+
/**
|
|
25
|
+
* Converts a `SEOConfig` to a React Router 7 meta descriptor array.
|
|
26
|
+
*
|
|
27
|
+
* Use the return value inside your route's `meta()` export.
|
|
28
|
+
* Includes title, description, robots, Open Graph, Twitter, hreflang,
|
|
29
|
+
* canonical, and any additional meta/link tags.
|
|
30
|
+
*
|
|
31
|
+
* @example
|
|
32
|
+
* // routes/about.tsx
|
|
33
|
+
* import { toRouterMeta } from 'react-ssr-seo-toolkit/adapters/react-router';
|
|
34
|
+
*
|
|
35
|
+
* export function meta(): MetaDescriptor[] {
|
|
36
|
+
* return toRouterMeta({
|
|
37
|
+
* title: 'About Us',
|
|
38
|
+
* titleTemplate: '%s | MySite',
|
|
39
|
+
* description: 'Learn more about us.',
|
|
40
|
+
* canonical: 'https://mysite.com/about',
|
|
41
|
+
* openGraph: { type: 'website', title: 'About Us' },
|
|
42
|
+
* });
|
|
43
|
+
* }
|
|
44
|
+
*/
|
|
45
|
+
declare function toRouterMeta(config: SEOConfig): RouterMetaDescriptor[];
|
|
46
|
+
/**
|
|
47
|
+
* Converts link-type entries from `SEOConfig` to React Router 7 `LinkDescriptor[]`.
|
|
48
|
+
*
|
|
49
|
+
* Use the return value inside your route's `links()` export for canonical
|
|
50
|
+
* and hreflang links. This is an alternative to including them in `meta()`.
|
|
51
|
+
*
|
|
52
|
+
* @example
|
|
53
|
+
* // routes/about.tsx
|
|
54
|
+
* import { toRouterLinks } from 'react-ssr-seo-toolkit/adapters/react-router';
|
|
55
|
+
*
|
|
56
|
+
* export function links() {
|
|
57
|
+
* return toRouterLinks({
|
|
58
|
+
* canonical: 'https://mysite.com/about',
|
|
59
|
+
* alternates: [{ hreflang: 'en', href: 'https://mysite.com/about' }],
|
|
60
|
+
* });
|
|
61
|
+
* }
|
|
62
|
+
*/
|
|
63
|
+
declare function toRouterLinks(config: Pick<SEOConfig, "canonical" | "alternates" | "additionalLinkTags">): Array<{
|
|
64
|
+
rel: string;
|
|
65
|
+
href: string;
|
|
66
|
+
hrefLang?: string;
|
|
67
|
+
type?: string;
|
|
68
|
+
sizes?: string;
|
|
69
|
+
}>;
|
|
70
|
+
|
|
71
|
+
export { type RouterLinkDescriptor, type RouterMetaDescriptor, type RouterNameMetaDescriptor, type RouterPropertyMetaDescriptor, type RouterTitleDescriptor, toRouterLinks, toRouterMeta };
|
|
@@ -0,0 +1,71 @@
|
|
|
1
|
+
import { S as SEOConfig } from '../index-Dr2yktvz.js';
|
|
2
|
+
|
|
3
|
+
type RouterTitleDescriptor = {
|
|
4
|
+
title: string;
|
|
5
|
+
};
|
|
6
|
+
type RouterNameMetaDescriptor = {
|
|
7
|
+
name: string;
|
|
8
|
+
content: string;
|
|
9
|
+
};
|
|
10
|
+
type RouterPropertyMetaDescriptor = {
|
|
11
|
+
property: string;
|
|
12
|
+
content: string;
|
|
13
|
+
};
|
|
14
|
+
type RouterLinkDescriptor = {
|
|
15
|
+
tagName: "link";
|
|
16
|
+
rel: string;
|
|
17
|
+
href: string;
|
|
18
|
+
hrefLang?: string;
|
|
19
|
+
type?: string;
|
|
20
|
+
sizes?: string;
|
|
21
|
+
[key: string]: string | undefined;
|
|
22
|
+
};
|
|
23
|
+
type RouterMetaDescriptor = RouterTitleDescriptor | RouterNameMetaDescriptor | RouterPropertyMetaDescriptor | RouterLinkDescriptor;
|
|
24
|
+
/**
|
|
25
|
+
* Converts a `SEOConfig` to a React Router 7 meta descriptor array.
|
|
26
|
+
*
|
|
27
|
+
* Use the return value inside your route's `meta()` export.
|
|
28
|
+
* Includes title, description, robots, Open Graph, Twitter, hreflang,
|
|
29
|
+
* canonical, and any additional meta/link tags.
|
|
30
|
+
*
|
|
31
|
+
* @example
|
|
32
|
+
* // routes/about.tsx
|
|
33
|
+
* import { toRouterMeta } from 'react-ssr-seo-toolkit/adapters/react-router';
|
|
34
|
+
*
|
|
35
|
+
* export function meta(): MetaDescriptor[] {
|
|
36
|
+
* return toRouterMeta({
|
|
37
|
+
* title: 'About Us',
|
|
38
|
+
* titleTemplate: '%s | MySite',
|
|
39
|
+
* description: 'Learn more about us.',
|
|
40
|
+
* canonical: 'https://mysite.com/about',
|
|
41
|
+
* openGraph: { type: 'website', title: 'About Us' },
|
|
42
|
+
* });
|
|
43
|
+
* }
|
|
44
|
+
*/
|
|
45
|
+
declare function toRouterMeta(config: SEOConfig): RouterMetaDescriptor[];
|
|
46
|
+
/**
|
|
47
|
+
* Converts link-type entries from `SEOConfig` to React Router 7 `LinkDescriptor[]`.
|
|
48
|
+
*
|
|
49
|
+
* Use the return value inside your route's `links()` export for canonical
|
|
50
|
+
* and hreflang links. This is an alternative to including them in `meta()`.
|
|
51
|
+
*
|
|
52
|
+
* @example
|
|
53
|
+
* // routes/about.tsx
|
|
54
|
+
* import { toRouterLinks } from 'react-ssr-seo-toolkit/adapters/react-router';
|
|
55
|
+
*
|
|
56
|
+
* export function links() {
|
|
57
|
+
* return toRouterLinks({
|
|
58
|
+
* canonical: 'https://mysite.com/about',
|
|
59
|
+
* alternates: [{ hreflang: 'en', href: 'https://mysite.com/about' }],
|
|
60
|
+
* });
|
|
61
|
+
* }
|
|
62
|
+
*/
|
|
63
|
+
declare function toRouterLinks(config: Pick<SEOConfig, "canonical" | "alternates" | "additionalLinkTags">): Array<{
|
|
64
|
+
rel: string;
|
|
65
|
+
href: string;
|
|
66
|
+
hrefLang?: string;
|
|
67
|
+
type?: string;
|
|
68
|
+
sizes?: string;
|
|
69
|
+
}>;
|
|
70
|
+
|
|
71
|
+
export { type RouterLinkDescriptor, type RouterMetaDescriptor, type RouterNameMetaDescriptor, type RouterPropertyMetaDescriptor, type RouterTitleDescriptor, toRouterLinks, toRouterMeta };
|
|
@@ -0,0 +1,2 @@
|
|
|
1
|
+
import {i,l,o,p,q}from'../chunk-LP66SUGR.js';function h(e){let r=[],n=i(e.title,e.titleTemplate);n&&r.push({title:n}),e.description?.trim()&&r.push({name:"description",content:e.description.trim()}),e.canonical?.trim()&&r.push({tagName:"link",rel:"canonical",href:e.canonical.trim()});let s=l(e.robots);s&&r.push({name:"robots",content:s});for(let t of o(e.openGraph))r.push({property:t.property,content:t.content});for(let t of p(e.twitter))r.push({name:t.name,content:t.content});for(let t of q(e.alternates))r.push({tagName:"link",rel:t.rel,href:t.href,hrefLang:t.hreflang});if(e.additionalMetaTags)for(let t of e.additionalMetaTags)t.name?r.push({name:t.name,content:t.content}):t.property&&r.push({property:t.property,content:t.content});if(e.additionalLinkTags)for(let t of e.additionalLinkTags)r.push({tagName:"link",rel:t.rel,href:t.href,...t.hreflang&&{hrefLang:t.hreflang},...t.type&&{type:t.type},...t.sizes&&{sizes:t.sizes}});return r}function c(e){let r=[];e.canonical?.trim()&&r.push({rel:"canonical",href:e.canonical.trim()});for(let n of q(e.alternates))r.push({rel:n.rel,href:n.href,hrefLang:n.hreflang});if(e.additionalLinkTags)for(let n of e.additionalLinkTags)r.push({rel:n.rel,href:n.href,...n.hreflang&&{hrefLang:n.hreflang},...n.type&&{type:n.type},...n.sizes&&{sizes:n.sizes}});return r}export{c as toRouterLinks,h as toRouterMeta};//# sourceMappingURL=react-router.js.map
|
|
2
|
+
//# sourceMappingURL=react-router.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../../src/adapters/react-router/index.ts"],"names":["toRouterMeta","config","descriptors","resolvedTitle","buildTitle","robotsContent","buildRobotsDirectives","tag","buildOpenGraph","buildTwitterMetadata","link","buildAlternateLinks","toRouterLinks","links","alt"],"mappings":"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","file":"react-router.js","sourcesContent":["import type { SEOConfig } from \"../../types/index.js\";\nimport {\n buildTitle,\n buildRobotsDirectives,\n buildOpenGraph,\n buildTwitterMetadata,\n buildAlternateLinks,\n} from \"../../core/index.js\";\n\n// ─── Descriptor types ─────────────────────────────────────────\n// Mirrors React Router 7's MetaDescriptor and LinkDescriptor\n// without importing from 'react-router', so no peer dep is needed.\n\nexport type RouterTitleDescriptor = { title: string };\n\nexport type RouterNameMetaDescriptor = {\n name: string;\n content: string;\n};\n\nexport type RouterPropertyMetaDescriptor = {\n property: string;\n content: string;\n};\n\nexport type RouterLinkDescriptor = {\n tagName: \"link\";\n rel: string;\n href: string;\n hrefLang?: string;\n type?: string;\n sizes?: string;\n [key: string]: string | undefined;\n};\n\nexport type RouterMetaDescriptor =\n | RouterTitleDescriptor\n | RouterNameMetaDescriptor\n | RouterPropertyMetaDescriptor\n | RouterLinkDescriptor;\n\n// ─── toRouterMeta ─────────────────────────────────────────────\n\n/**\n * Converts a `SEOConfig` to a React Router 7 meta descriptor array.\n *\n * Use the return value inside your route's `meta()` export.\n * Includes title, description, robots, Open Graph, Twitter, hreflang,\n * canonical, and any additional meta/link tags.\n *\n * @example\n * // routes/about.tsx\n * import { toRouterMeta } from 'react-ssr-seo-toolkit/adapters/react-router';\n *\n * export function meta(): MetaDescriptor[] {\n * return toRouterMeta({\n * title: 'About Us',\n * titleTemplate: '%s | MySite',\n * description: 'Learn more about us.',\n * canonical: 'https://mysite.com/about',\n * openGraph: { type: 'website', title: 'About Us' },\n * });\n * }\n */\nexport function toRouterMeta(config: SEOConfig): RouterMetaDescriptor[] {\n const descriptors: RouterMetaDescriptor[] = [];\n\n // Title\n const resolvedTitle = buildTitle(config.title, config.titleTemplate);\n if (resolvedTitle) {\n descriptors.push({ title: resolvedTitle });\n }\n\n // Description\n if (config.description?.trim()) {\n descriptors.push({ name: \"description\", content: config.description.trim() });\n }\n\n // Canonical\n if (config.canonical?.trim()) {\n descriptors.push({\n tagName: \"link\",\n rel: \"canonical\",\n href: config.canonical.trim(),\n });\n }\n\n // Robots\n const robotsContent = buildRobotsDirectives(config.robots);\n if (robotsContent) {\n descriptors.push({ name: \"robots\", content: robotsContent });\n }\n\n // Open Graph\n for (const tag of buildOpenGraph(config.openGraph)) {\n descriptors.push({ property: tag.property, content: tag.content });\n }\n\n // Twitter\n for (const tag of buildTwitterMetadata(config.twitter)) {\n descriptors.push({ name: tag.name, content: tag.content });\n }\n\n // Hreflang alternates\n for (const link of buildAlternateLinks(config.alternates)) {\n descriptors.push({\n tagName: \"link\",\n rel: link.rel,\n href: link.href,\n hrefLang: link.hreflang,\n });\n }\n\n // Additional meta tags\n if (config.additionalMetaTags) {\n for (const tag of config.additionalMetaTags) {\n if (tag.name) {\n descriptors.push({ name: tag.name, content: tag.content });\n } else if (tag.property) {\n descriptors.push({ property: tag.property, content: tag.content });\n }\n }\n }\n\n // Additional link tags\n if (config.additionalLinkTags) {\n for (const link of config.additionalLinkTags) {\n descriptors.push({\n tagName: \"link\",\n rel: link.rel,\n href: link.href,\n ...(link.hreflang && { hrefLang: link.hreflang }),\n ...(link.type && { type: link.type }),\n ...(link.sizes && { sizes: link.sizes }),\n });\n }\n }\n\n return descriptors;\n}\n\n// ─── toRouterLinks ────────────────────────────────────────────\n\n/**\n * Converts link-type entries from `SEOConfig` to React Router 7 `LinkDescriptor[]`.\n *\n * Use the return value inside your route's `links()` export for canonical\n * and hreflang links. This is an alternative to including them in `meta()`.\n *\n * @example\n * // routes/about.tsx\n * import { toRouterLinks } from 'react-ssr-seo-toolkit/adapters/react-router';\n *\n * export function links() {\n * return toRouterLinks({\n * canonical: 'https://mysite.com/about',\n * alternates: [{ hreflang: 'en', href: 'https://mysite.com/about' }],\n * });\n * }\n */\nexport function toRouterLinks(\n config: Pick<SEOConfig, \"canonical\" | \"alternates\" | \"additionalLinkTags\">\n): Array<{ rel: string; href: string; hrefLang?: string; type?: string; sizes?: string }> {\n const links: Array<{\n rel: string;\n href: string;\n hrefLang?: string;\n type?: string;\n sizes?: string;\n }> = [];\n\n if (config.canonical?.trim()) {\n links.push({ rel: \"canonical\", href: config.canonical.trim() });\n }\n\n for (const alt of buildAlternateLinks(config.alternates)) {\n links.push({ rel: alt.rel, href: alt.href, hrefLang: alt.hreflang });\n }\n\n if (config.additionalLinkTags) {\n for (const link of config.additionalLinkTags) {\n links.push({\n rel: link.rel,\n href: link.href,\n ...(link.hreflang && { hrefLang: link.hreflang }),\n ...(link.type && { type: link.type }),\n ...(link.sizes && { sizes: link.sizes }),\n });\n }\n }\n\n return links;\n}\n"]}
|
|
@@ -0,0 +1,2 @@
|
|
|
1
|
+
import {i,l,o,p,q,a}from'./chunk-LP66SUGR.js';import t from'react';function C({title:c,titleTemplate:i$1,description:m,canonical:p$1,robots:R,openGraph:S,twitter:b,alternates:x,additionalMetaTags:l$1,additionalLinkTags:f,jsonLd:s,nonce:L}){let n=[],O=0,r=()=>`seo-${O++}`,y=i(c,i$1);y&&n.push(t.createElement("title",{key:r()},y)),m?.trim()&&n.push(t.createElement("meta",{key:r(),name:"description",content:m.trim()})),p$1?.trim()&&n.push(t.createElement("link",{key:r(),rel:"canonical",href:p$1.trim()}));let u=l(R);u&&n.push(t.createElement("meta",{key:r(),name:"robots",content:u}));let H=o(S);for(let e of H)n.push(t.createElement("meta",{key:r(),property:e.property,content:e.content}));let J=p(b);for(let e of J)n.push(t.createElement("meta",{key:r(),name:e.name,content:e.content}));let T=q(x);for(let e of T)n.push(t.createElement("link",{key:r(),rel:e.rel,hrefLang:e.hreflang,href:e.href}));if(l$1)for(let e of l$1){let o={content:e.content};e.name&&(o.name=e.name),e.property&&(o.property=e.property),n.push(t.createElement("meta",{key:r(),...o}));}if(f)for(let e of f)n.push(t.createElement("link",{key:r(),...e}));if(s){let e=Array.isArray(s)?s:[s];for(let o of e)n.push(t.createElement("script",{key:r(),type:"application/ld+json",nonce:L,dangerouslySetInnerHTML:{__html:a(o)}}));}return t.createElement(t.Fragment,null,...n)}function M({data:c,nonce:i}){return t.createElement("script",{type:"application/ld+json",nonce:i,dangerouslySetInnerHTML:{__html:a(c)}})}export{C as a,M as b};//# sourceMappingURL=chunk-AYIAPQTP.js.map
|
|
2
|
+
//# sourceMappingURL=chunk-AYIAPQTP.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../src/components/SEOHead.tsx","../src/components/JsonLd.tsx"],"names":["SEOHead","title","titleTemplate","description","canonical","robots","openGraph","twitter","alternates","additionalMetaTags","additionalLinkTags","jsonLd","nonce","elements","key","k","resolvedTitle","buildTitle","React","robotsContent","buildRobotsDirectives","ogTags","buildOpenGraph","tag","twitterTags","buildTwitterMetadata","altLinks","buildAlternateLinks","link","meta","props","schemas","schema","safeJsonLdSerialize","JsonLd","data"],"mappings":"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","file":"chunk-AYIAPQTP.js","sourcesContent":["import React from \"react\";\nimport type { SEOConfig } from \"../types/index.js\";\nimport {\n buildTitle,\n buildRobotsDirectives,\n buildOpenGraph,\n buildTwitterMetadata,\n buildAlternateLinks,\n} from \"../core/index.js\";\nimport { safeJsonLdSerialize } from \"../utils/index.js\";\n\nexport interface SEOHeadProps extends SEOConfig {\n /**\n * Optional nonce for CSP-compatible script tags.\n */\n nonce?: string;\n}\n\n/**\n * Generic SSR-safe React component that renders SEO-related tags.\n *\n * Renders: title, meta description, canonical link, robots meta,\n * Open Graph tags, Twitter tags, alternate/hreflang links,\n * additional meta/link tags, and JSON-LD scripts.\n *\n * Designed to be placed inside a <head> element in SSR apps.\n * Does NOT use any browser globals — fully SSR-compatible.\n */\nexport function SEOHead({\n title,\n titleTemplate,\n description,\n canonical,\n robots,\n openGraph,\n twitter,\n alternates,\n additionalMetaTags,\n additionalLinkTags,\n jsonLd,\n nonce,\n}: SEOHeadProps): React.ReactElement {\n const elements: React.ReactElement[] = [];\n let key = 0;\n const k = () => `seo-${key++}`;\n\n // Title\n const resolvedTitle = buildTitle(title, titleTemplate);\n if (resolvedTitle) {\n elements.push(React.createElement(\"title\", { key: k() }, resolvedTitle));\n }\n\n // Description\n if (description?.trim()) {\n elements.push(\n React.createElement(\"meta\", {\n key: k(),\n name: \"description\",\n content: description.trim(),\n })\n );\n }\n\n // Canonical\n if (canonical?.trim()) {\n elements.push(\n React.createElement(\"link\", {\n key: k(),\n rel: \"canonical\",\n href: canonical.trim(),\n })\n );\n }\n\n // Robots\n const robotsContent = buildRobotsDirectives(robots);\n if (robotsContent) {\n elements.push(\n React.createElement(\"meta\", {\n key: k(),\n name: \"robots\",\n content: robotsContent,\n })\n );\n }\n\n // Open Graph\n const ogTags = buildOpenGraph(openGraph);\n for (const tag of ogTags) {\n elements.push(\n React.createElement(\"meta\", {\n key: k(),\n property: tag.property,\n content: tag.content,\n })\n );\n }\n\n // Twitter\n const twitterTags = buildTwitterMetadata(twitter);\n for (const tag of twitterTags) {\n elements.push(\n React.createElement(\"meta\", {\n key: k(),\n name: tag.name,\n content: tag.content,\n })\n );\n }\n\n // Hreflang alternates\n const altLinks = buildAlternateLinks(alternates);\n for (const link of altLinks) {\n elements.push(\n React.createElement(\"link\", {\n key: k(),\n rel: link.rel,\n hrefLang: link.hreflang,\n href: link.href,\n })\n );\n }\n\n // Additional meta tags\n if (additionalMetaTags) {\n for (const meta of additionalMetaTags) {\n const props: Record<string, string> = { content: meta.content };\n if (meta.name) props.name = meta.name;\n if (meta.property) props.property = meta.property;\n elements.push(React.createElement(\"meta\", { key: k(), ...props }));\n }\n }\n\n // Additional link tags\n if (additionalLinkTags) {\n for (const link of additionalLinkTags) {\n elements.push(React.createElement(\"link\", { key: k(), ...link }));\n }\n }\n\n // JSON-LD\n if (jsonLd) {\n const schemas = Array.isArray(jsonLd) ? jsonLd : [jsonLd];\n for (const schema of schemas) {\n elements.push(\n React.createElement(\"script\", {\n key: k(),\n type: \"application/ld+json\",\n nonce,\n dangerouslySetInnerHTML: {\n __html: safeJsonLdSerialize(schema),\n },\n })\n );\n }\n }\n\n return React.createElement(React.Fragment, null, ...elements);\n}\n","import React from \"react\";\nimport { safeJsonLdSerialize } from \"../utils/index.js\";\n\nexport interface JsonLdProps {\n data: Record<string, unknown> | Array<Record<string, unknown>>;\n nonce?: string;\n}\n\n/**\n * Renders a <script type=\"application/ld+json\"> tag with safely serialized JSON-LD.\n * SSR-safe: no browser globals used.\n */\nexport function JsonLd({ data, nonce }: JsonLdProps): React.ReactElement {\n return React.createElement(\"script\", {\n type: \"application/ld+json\",\n nonce,\n dangerouslySetInnerHTML: {\n __html: safeJsonLdSerialize(data),\n },\n });\n}\n"]}
|
|
@@ -0,0 +1,2 @@
|
|
|
1
|
+
'use strict';function f(e){let t=JSON.stringify(e,(n,r)=>{if(r!=null)return r});return t?t.replace(/</g,"\\u003c").replace(/>/g,"\\u003e").replace(/&/g,"\\u0026").replace(/\u2028/g,"\\u2028").replace(/\u2029/g,"\\u2029"):"{}"}function d(e){let t={};for(let n of Object.keys(e)){let r=e[n];r!=null&&r!==""&&(t[n]=r);}return t}function l(e,t){let n={...e},r=t,u=e;for(let i of Object.keys(r)){let s=r[i],a=u[i];s!==void 0&&(s!==null&&typeof s=="object"&&!Array.isArray(s)&&a!==null&&typeof a=="object"&&!Array.isArray(a)?n[i]=l(a,s):n[i]=s);}return n}function o(e){let t=e.trim();return t==="/"||t===""?t:t.replace(/\/+$/,"")}function g(e,t){let n=o(e);if(!t||t==="/")return n;let r=t.startsWith("/")?t:`/${t}`;return n+o(r)}function h(e={}){return p(e)}function y(e,t){let n=l(e,t);return t.alternates!==void 0&&(n.alternates=t.alternates),t.additionalMetaTags!==void 0&&(n.additionalMetaTags=t.additionalMetaTags),t.additionalLinkTags!==void 0&&(n.additionalLinkTags=t.additionalLinkTags),t.jsonLd!==void 0&&(n.jsonLd=t.jsonLd),p(n)}function p(e){let t={...e};return t.title&&(t.title=t.title.trim()),t.description&&(t.description=t.description.trim()),t.canonical&&(t.canonical=o(t.canonical)),t}function x(e,t){if(!e)return "";let n=e.trim();return t?t.replace(/%s/g,n):n}function w(e,t){if(!e)return "";let n=e.trim();return !t||n.length<=t?n:n.slice(0,t).trimEnd()+"\u2026"}function C(e,t){let n=o(e);if(!t||t==="/")return n;let r=t.startsWith("/")?t:`/${t}`;return n+o(r)}function k(e){if(!e)return "";let t=[];return e.index===false?t.push("noindex"):e.index===true&&t.push("index"),e.follow===false?t.push("nofollow"):e.follow===true&&t.push("follow"),e.noarchive&&t.push("noarchive"),e.nosnippet&&t.push("nosnippet"),e.noimageindex&&t.push("noimageindex"),e.notranslate&&t.push("notranslate"),e.maxSnippet!==void 0&&t.push(`max-snippet:${e.maxSnippet}`),e.maxImagePreview&&t.push(`max-image-preview:${e.maxImagePreview}`),e.maxVideoPreview!==void 0&&t.push(`max-video-preview:${e.maxVideoPreview}`),t.join(", ")}function O(){return {index:false,follow:false}}function b(){return {index:false,follow:true}}function T(e){if(!e)return [];let t=[],n=[["title","og:title"],["description","og:description"],["url","og:url"],["siteName","og:site_name"],["type","og:type"],["locale","og:locale"]];for(let[r,u]of n){let i=e[r];typeof i=="string"&&i.trim()&&t.push({property:u,content:i.trim()});}if(e.images)for(let r of e.images)r.url&&(t.push({property:"og:image",content:r.url}),r.alt&&t.push({property:"og:image:alt",content:r.alt}),r.width&&t.push({property:"og:image:width",content:String(r.width)}),r.height&&t.push({property:"og:image:height",content:String(r.height)}),r.type&&t.push({property:"og:image:type",content:r.type}));return t}function S(e){if(!e)return [];let t=[];return e.card&&t.push({name:"twitter:card",content:e.card}),e.site&&t.push({name:"twitter:site",content:e.site}),e.creator&&t.push({name:"twitter:creator",content:e.creator}),e.title&&t.push({name:"twitter:title",content:e.title}),e.description&&t.push({name:"twitter:description",content:e.description}),e.image&&t.push({name:"twitter:image",content:e.image}),e.imageAlt&&t.push({name:"twitter:image:alt",content:e.imageAlt}),t}function A(e){return !e||e.length===0?[]:e.map(t=>({rel:"alternate",hreflang:t.hreflang,href:o(t.href)}))}exports.a=f;exports.b=d;exports.c=l;exports.d=o;exports.e=g;exports.f=h;exports.g=y;exports.h=p;exports.i=x;exports.j=w;exports.k=C;exports.l=k;exports.m=O;exports.n=b;exports.o=T;exports.p=S;exports.q=A;//# sourceMappingURL=chunk-FKDMHECL.cjs.map
|
|
2
|
+
//# sourceMappingURL=chunk-FKDMHECL.cjs.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../src/utils/index.ts","../src/core/index.ts"],"names":["safeJsonLdSerialize","data","json","_key","value","omitEmpty","obj","result","key","val","deepMerge","base","override","overrideObj","baseObj","overrideVal","baseVal","normalizeUrl","url","trimmed","buildFullUrl","path","normalizedBase","normalizedPath","createSEOConfig","config","normalizeSEOConfig","mergeSEOConfig","merged","normalized","buildTitle","title","template","buildDescription","description","maxLength","buildCanonicalUrl","baseUrl","buildRobotsDirectives","directives","noIndexNoFollow","noIndex","buildOpenGraph","tags","simple","property","image","buildTwitterMetadata","buildAlternateLinks","alternates","alt"],"mappings":"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","file":"chunk-FKDMHECL.cjs","sourcesContent":["/**\n * Safely serialize a value to JSON for use in a <script> tag.\n * Escapes characters that could break out of script context.\n */\nexport function safeJsonLdSerialize(data: unknown): string {\n const json = JSON.stringify(data, (_key, value) => {\n if (value === undefined || value === null) return undefined;\n return value;\n });\n\n if (!json) return \"{}\";\n\n return json\n .replace(/</g, \"\\\\u003c\")\n .replace(/>/g, \"\\\\u003e\")\n .replace(/&/g, \"\\\\u0026\")\n .replace(/\\u2028/g, \"\\\\u2028\")\n .replace(/\\u2029/g, \"\\\\u2029\");\n}\n\n/**\n * Strip undefined/null values from an object (shallow).\n */\nexport function omitEmpty<T extends Record<string, unknown>>(\n obj: T\n): Partial<T> {\n const result: Partial<T> = {};\n for (const key of Object.keys(obj) as Array<keyof T>) {\n const val = obj[key];\n if (val !== undefined && val !== null && val !== \"\") {\n result[key] = val;\n }\n }\n return result;\n}\n\n/**\n * Deep-merge two objects. Arrays are replaced, not concatenated.\n */\nexport function deepMerge<T>(\n base: T,\n override: Partial<T>\n): T {\n const result = { ...base } as Record<string, unknown>;\n const overrideObj = override as Record<string, unknown>;\n const baseObj = base as Record<string, unknown>;\n\n for (const key of Object.keys(overrideObj)) {\n const overrideVal = overrideObj[key];\n const baseVal = baseObj[key];\n\n if (overrideVal === undefined) continue;\n\n if (\n overrideVal !== null &&\n typeof overrideVal === \"object\" &&\n !Array.isArray(overrideVal) &&\n baseVal !== null &&\n typeof baseVal === \"object\" &&\n !Array.isArray(baseVal)\n ) {\n result[key] = deepMerge(\n baseVal as Record<string, unknown>,\n overrideVal as Record<string, unknown>\n );\n } else {\n result[key] = overrideVal;\n }\n }\n\n return result as T;\n}\n\n/**\n * Normalize a URL by trimming whitespace and removing trailing slashes\n * (except for root \"/\").\n */\nexport function normalizeUrl(url: string): string {\n const trimmed = url.trim();\n if (trimmed === \"/\" || trimmed === \"\") return trimmed;\n return trimmed.replace(/\\/+$/, \"\");\n}\n\n/**\n * Build a full canonical URL from a base and a path.\n */\nexport function buildFullUrl(base: string, path?: string): string {\n const normalizedBase = normalizeUrl(base);\n if (!path || path === \"/\") return normalizedBase;\n const normalizedPath = path.startsWith(\"/\") ? path : `/${path}`;\n return normalizedBase + normalizeUrl(normalizedPath);\n}\n","import type {\n SEOConfig,\n OpenGraphConfig,\n TwitterConfig,\n RobotsConfig,\n AlternateLink,\n} from \"../types/index.js\";\nimport { deepMerge, normalizeUrl } from \"../utils/index.js\";\n\n// ─── Config builder ───────────────────────────────────────────\n\n/**\n * Create an SEO config with sensible defaults.\n */\nexport function createSEOConfig(config: SEOConfig = {}): SEOConfig {\n return normalizeSEOConfig(config);\n}\n\n/**\n * Deep-merge a base SEO config with page-level overrides.\n * Useful for combining a global site config with per-page config.\n */\nexport function mergeSEOConfig(\n base: SEOConfig,\n override: SEOConfig\n): SEOConfig {\n const merged = deepMerge(base, override);\n\n // Arrays should be replaced, not deep-merged\n if (override.alternates !== undefined) {\n merged.alternates = override.alternates;\n }\n if (override.additionalMetaTags !== undefined) {\n merged.additionalMetaTags = override.additionalMetaTags;\n }\n if (override.additionalLinkTags !== undefined) {\n merged.additionalLinkTags = override.additionalLinkTags;\n }\n if (override.jsonLd !== undefined) {\n merged.jsonLd = override.jsonLd;\n }\n\n return normalizeSEOConfig(merged);\n}\n\n/**\n * Normalize an SEO config: trim strings, normalize URLs, remove empties.\n */\nexport function normalizeSEOConfig(config: SEOConfig): SEOConfig {\n const normalized: SEOConfig = { ...config };\n\n if (normalized.title) {\n normalized.title = normalized.title.trim();\n }\n if (normalized.description) {\n normalized.description = normalized.description.trim();\n }\n if (normalized.canonical) {\n normalized.canonical = normalizeUrl(normalized.canonical);\n }\n\n return normalized;\n}\n\n// ─── Title ────────────────────────────────────────────────────\n\n/**\n * Build a title string, optionally applying a template.\n * Template uses `%s` as the placeholder for the page title.\n *\n * @example\n * buildTitle(\"About\", \"%s | MySite\") // \"About | MySite\"\n * buildTitle(\"Home\") // \"Home\"\n */\nexport function buildTitle(title?: string, template?: string): string {\n if (!title) return \"\";\n const trimmed = title.trim();\n if (!template) return trimmed;\n return template.replace(/%s/g, trimmed);\n}\n\n/**\n * Build a description, truncating at a max length if specified.\n */\nexport function buildDescription(\n description?: string,\n maxLength?: number\n): string {\n if (!description) return \"\";\n const trimmed = description.trim();\n if (!maxLength || trimmed.length <= maxLength) return trimmed;\n return trimmed.slice(0, maxLength).trimEnd() + \"…\";\n}\n\n// ─── Canonical URL ────────────────────────────────────────────\n\n/**\n * Build a canonical URL from a base URL and optional path.\n */\nexport function buildCanonicalUrl(\n baseUrl: string,\n path?: string\n): string {\n const normalizedBase = normalizeUrl(baseUrl);\n if (!path || path === \"/\") return normalizedBase;\n const normalizedPath = path.startsWith(\"/\") ? path : `/${path}`;\n return normalizedBase + normalizeUrl(normalizedPath);\n}\n\n// ─── Robots ───────────────────────────────────────────────────\n\n/**\n * Build a robots meta content string from a config.\n *\n * @example\n * buildRobotsDirectives({ index: true, follow: true }) // \"index, follow\"\n * buildRobotsDirectives({ index: false, follow: false, noarchive: true })\n * // \"noindex, nofollow, noarchive\"\n */\nexport function buildRobotsDirectives(config?: RobotsConfig): string {\n if (!config) return \"\";\n\n const directives: string[] = [];\n\n if (config.index === false) directives.push(\"noindex\");\n else if (config.index === true) directives.push(\"index\");\n\n if (config.follow === false) directives.push(\"nofollow\");\n else if (config.follow === true) directives.push(\"follow\");\n\n if (config.noarchive) directives.push(\"noarchive\");\n if (config.nosnippet) directives.push(\"nosnippet\");\n if (config.noimageindex) directives.push(\"noimageindex\");\n if (config.notranslate) directives.push(\"notranslate\");\n\n if (config.maxSnippet !== undefined)\n directives.push(`max-snippet:${config.maxSnippet}`);\n if (config.maxImagePreview)\n directives.push(`max-image-preview:${config.maxImagePreview}`);\n if (config.maxVideoPreview !== undefined)\n directives.push(`max-video-preview:${config.maxVideoPreview}`);\n\n return directives.join(\", \");\n}\n\n// ─── Convenience helpers ──────────────────────────────────────\n\n/**\n * Create a noindex/nofollow robots config.\n */\nexport function noIndexNoFollow(): RobotsConfig {\n return { index: false, follow: false };\n}\n\n/**\n * Create a noindex robots config that still allows following links.\n */\nexport function noIndex(): RobotsConfig {\n return { index: false, follow: true };\n}\n\n// ─── Open Graph ───────────────────────────────────────────────\n\n/**\n * Build Open Graph meta tag entries from config.\n * Returns an array of { property, content } pairs.\n */\nexport function buildOpenGraph(\n config?: OpenGraphConfig\n): Array<{ property: string; content: string }> {\n if (!config) return [];\n\n const tags: Array<{ property: string; content: string }> = [];\n\n const simple: Array<[keyof OpenGraphConfig, string]> = [\n [\"title\", \"og:title\"],\n [\"description\", \"og:description\"],\n [\"url\", \"og:url\"],\n [\"siteName\", \"og:site_name\"],\n [\"type\", \"og:type\"],\n [\"locale\", \"og:locale\"],\n ];\n\n for (const [key, property] of simple) {\n const value = config[key];\n if (typeof value === \"string\" && value.trim()) {\n tags.push({ property, content: value.trim() });\n }\n }\n\n if (config.images) {\n for (const image of config.images) {\n if (!image.url) continue;\n tags.push({ property: \"og:image\", content: image.url });\n if (image.alt) tags.push({ property: \"og:image:alt\", content: image.alt });\n if (image.width)\n tags.push({\n property: \"og:image:width\",\n content: String(image.width),\n });\n if (image.height)\n tags.push({\n property: \"og:image:height\",\n content: String(image.height),\n });\n if (image.type)\n tags.push({ property: \"og:image:type\", content: image.type });\n }\n }\n\n return tags;\n}\n\n// ─── Twitter ──────────────────────────────────────────────────\n\n/**\n * Build Twitter card meta tag entries from config.\n * Returns an array of { name, content } pairs.\n */\nexport function buildTwitterMetadata(\n config?: TwitterConfig\n): Array<{ name: string; content: string }> {\n if (!config) return [];\n\n const tags: Array<{ name: string; content: string }> = [];\n\n if (config.card) tags.push({ name: \"twitter:card\", content: config.card });\n if (config.site) tags.push({ name: \"twitter:site\", content: config.site });\n if (config.creator)\n tags.push({ name: \"twitter:creator\", content: config.creator });\n if (config.title)\n tags.push({ name: \"twitter:title\", content: config.title });\n if (config.description)\n tags.push({ name: \"twitter:description\", content: config.description });\n if (config.image)\n tags.push({ name: \"twitter:image\", content: config.image });\n if (config.imageAlt)\n tags.push({ name: \"twitter:image:alt\", content: config.imageAlt });\n\n return tags;\n}\n\n// ─── Hreflang ─────────────────────────────────────────────────\n\n/**\n * Build alternate link entries for hreflang from an array of alternates.\n * Returns an array of { rel, hreflang, href } suitable for <link> tags.\n */\nexport function buildAlternateLinks(\n alternates?: AlternateLink[]\n): Array<{ rel: string; hreflang: string; href: string }> {\n if (!alternates || alternates.length === 0) return [];\n return alternates.map((alt) => ({\n rel: \"alternate\",\n hreflang: alt.hreflang,\n href: normalizeUrl(alt.href),\n }));\n}\n"]}
|
|
@@ -0,0 +1,2 @@
|
|
|
1
|
+
function f(e){let t=JSON.stringify(e,(n,r)=>{if(r!=null)return r});return t?t.replace(/</g,"\\u003c").replace(/>/g,"\\u003e").replace(/&/g,"\\u0026").replace(/\u2028/g,"\\u2028").replace(/\u2029/g,"\\u2029"):"{}"}function d(e){let t={};for(let n of Object.keys(e)){let r=e[n];r!=null&&r!==""&&(t[n]=r);}return t}function l(e,t){let n={...e},r=t,u=e;for(let i of Object.keys(r)){let s=r[i],a=u[i];s!==void 0&&(s!==null&&typeof s=="object"&&!Array.isArray(s)&&a!==null&&typeof a=="object"&&!Array.isArray(a)?n[i]=l(a,s):n[i]=s);}return n}function o(e){let t=e.trim();return t==="/"||t===""?t:t.replace(/\/+$/,"")}function g(e,t){let n=o(e);if(!t||t==="/")return n;let r=t.startsWith("/")?t:`/${t}`;return n+o(r)}function h(e={}){return p(e)}function y(e,t){let n=l(e,t);return t.alternates!==void 0&&(n.alternates=t.alternates),t.additionalMetaTags!==void 0&&(n.additionalMetaTags=t.additionalMetaTags),t.additionalLinkTags!==void 0&&(n.additionalLinkTags=t.additionalLinkTags),t.jsonLd!==void 0&&(n.jsonLd=t.jsonLd),p(n)}function p(e){let t={...e};return t.title&&(t.title=t.title.trim()),t.description&&(t.description=t.description.trim()),t.canonical&&(t.canonical=o(t.canonical)),t}function x(e,t){if(!e)return "";let n=e.trim();return t?t.replace(/%s/g,n):n}function w(e,t){if(!e)return "";let n=e.trim();return !t||n.length<=t?n:n.slice(0,t).trimEnd()+"\u2026"}function C(e,t){let n=o(e);if(!t||t==="/")return n;let r=t.startsWith("/")?t:`/${t}`;return n+o(r)}function k(e){if(!e)return "";let t=[];return e.index===false?t.push("noindex"):e.index===true&&t.push("index"),e.follow===false?t.push("nofollow"):e.follow===true&&t.push("follow"),e.noarchive&&t.push("noarchive"),e.nosnippet&&t.push("nosnippet"),e.noimageindex&&t.push("noimageindex"),e.notranslate&&t.push("notranslate"),e.maxSnippet!==void 0&&t.push(`max-snippet:${e.maxSnippet}`),e.maxImagePreview&&t.push(`max-image-preview:${e.maxImagePreview}`),e.maxVideoPreview!==void 0&&t.push(`max-video-preview:${e.maxVideoPreview}`),t.join(", ")}function O(){return {index:false,follow:false}}function b(){return {index:false,follow:true}}function T(e){if(!e)return [];let t=[],n=[["title","og:title"],["description","og:description"],["url","og:url"],["siteName","og:site_name"],["type","og:type"],["locale","og:locale"]];for(let[r,u]of n){let i=e[r];typeof i=="string"&&i.trim()&&t.push({property:u,content:i.trim()});}if(e.images)for(let r of e.images)r.url&&(t.push({property:"og:image",content:r.url}),r.alt&&t.push({property:"og:image:alt",content:r.alt}),r.width&&t.push({property:"og:image:width",content:String(r.width)}),r.height&&t.push({property:"og:image:height",content:String(r.height)}),r.type&&t.push({property:"og:image:type",content:r.type}));return t}function S(e){if(!e)return [];let t=[];return e.card&&t.push({name:"twitter:card",content:e.card}),e.site&&t.push({name:"twitter:site",content:e.site}),e.creator&&t.push({name:"twitter:creator",content:e.creator}),e.title&&t.push({name:"twitter:title",content:e.title}),e.description&&t.push({name:"twitter:description",content:e.description}),e.image&&t.push({name:"twitter:image",content:e.image}),e.imageAlt&&t.push({name:"twitter:image:alt",content:e.imageAlt}),t}function A(e){return !e||e.length===0?[]:e.map(t=>({rel:"alternate",hreflang:t.hreflang,href:o(t.href)}))}export{f as a,d as b,l as c,o as d,g as e,h as f,y as g,p as h,x as i,w as j,C as k,k as l,O as m,b as n,T as o,S as p,A as q};//# sourceMappingURL=chunk-LP66SUGR.js.map
|
|
2
|
+
//# sourceMappingURL=chunk-LP66SUGR.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../src/utils/index.ts","../src/core/index.ts"],"names":["safeJsonLdSerialize","data","json","_key","value","omitEmpty","obj","result","key","val","deepMerge","base","override","overrideObj","baseObj","overrideVal","baseVal","normalizeUrl","url","trimmed","buildFullUrl","path","normalizedBase","normalizedPath","createSEOConfig","config","normalizeSEOConfig","mergeSEOConfig","merged","normalized","buildTitle","title","template","buildDescription","description","maxLength","buildCanonicalUrl","baseUrl","buildRobotsDirectives","directives","noIndexNoFollow","noIndex","buildOpenGraph","tags","simple","property","image","buildTwitterMetadata","buildAlternateLinks","alternates","alt"],"mappings":"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","file":"chunk-LP66SUGR.js","sourcesContent":["/**\n * Safely serialize a value to JSON for use in a <script> tag.\n * Escapes characters that could break out of script context.\n */\nexport function safeJsonLdSerialize(data: unknown): string {\n const json = JSON.stringify(data, (_key, value) => {\n if (value === undefined || value === null) return undefined;\n return value;\n });\n\n if (!json) return \"{}\";\n\n return json\n .replace(/</g, \"\\\\u003c\")\n .replace(/>/g, \"\\\\u003e\")\n .replace(/&/g, \"\\\\u0026\")\n .replace(/\\u2028/g, \"\\\\u2028\")\n .replace(/\\u2029/g, \"\\\\u2029\");\n}\n\n/**\n * Strip undefined/null values from an object (shallow).\n */\nexport function omitEmpty<T extends Record<string, unknown>>(\n obj: T\n): Partial<T> {\n const result: Partial<T> = {};\n for (const key of Object.keys(obj) as Array<keyof T>) {\n const val = obj[key];\n if (val !== undefined && val !== null && val !== \"\") {\n result[key] = val;\n }\n }\n return result;\n}\n\n/**\n * Deep-merge two objects. Arrays are replaced, not concatenated.\n */\nexport function deepMerge<T>(\n base: T,\n override: Partial<T>\n): T {\n const result = { ...base } as Record<string, unknown>;\n const overrideObj = override as Record<string, unknown>;\n const baseObj = base as Record<string, unknown>;\n\n for (const key of Object.keys(overrideObj)) {\n const overrideVal = overrideObj[key];\n const baseVal = baseObj[key];\n\n if (overrideVal === undefined) continue;\n\n if (\n overrideVal !== null &&\n typeof overrideVal === \"object\" &&\n !Array.isArray(overrideVal) &&\n baseVal !== null &&\n typeof baseVal === \"object\" &&\n !Array.isArray(baseVal)\n ) {\n result[key] = deepMerge(\n baseVal as Record<string, unknown>,\n overrideVal as Record<string, unknown>\n );\n } else {\n result[key] = overrideVal;\n }\n }\n\n return result as T;\n}\n\n/**\n * Normalize a URL by trimming whitespace and removing trailing slashes\n * (except for root \"/\").\n */\nexport function normalizeUrl(url: string): string {\n const trimmed = url.trim();\n if (trimmed === \"/\" || trimmed === \"\") return trimmed;\n return trimmed.replace(/\\/+$/, \"\");\n}\n\n/**\n * Build a full canonical URL from a base and a path.\n */\nexport function buildFullUrl(base: string, path?: string): string {\n const normalizedBase = normalizeUrl(base);\n if (!path || path === \"/\") return normalizedBase;\n const normalizedPath = path.startsWith(\"/\") ? path : `/${path}`;\n return normalizedBase + normalizeUrl(normalizedPath);\n}\n","import type {\n SEOConfig,\n OpenGraphConfig,\n TwitterConfig,\n RobotsConfig,\n AlternateLink,\n} from \"../types/index.js\";\nimport { deepMerge, normalizeUrl } from \"../utils/index.js\";\n\n// ─── Config builder ───────────────────────────────────────────\n\n/**\n * Create an SEO config with sensible defaults.\n */\nexport function createSEOConfig(config: SEOConfig = {}): SEOConfig {\n return normalizeSEOConfig(config);\n}\n\n/**\n * Deep-merge a base SEO config with page-level overrides.\n * Useful for combining a global site config with per-page config.\n */\nexport function mergeSEOConfig(\n base: SEOConfig,\n override: SEOConfig\n): SEOConfig {\n const merged = deepMerge(base, override);\n\n // Arrays should be replaced, not deep-merged\n if (override.alternates !== undefined) {\n merged.alternates = override.alternates;\n }\n if (override.additionalMetaTags !== undefined) {\n merged.additionalMetaTags = override.additionalMetaTags;\n }\n if (override.additionalLinkTags !== undefined) {\n merged.additionalLinkTags = override.additionalLinkTags;\n }\n if (override.jsonLd !== undefined) {\n merged.jsonLd = override.jsonLd;\n }\n\n return normalizeSEOConfig(merged);\n}\n\n/**\n * Normalize an SEO config: trim strings, normalize URLs, remove empties.\n */\nexport function normalizeSEOConfig(config: SEOConfig): SEOConfig {\n const normalized: SEOConfig = { ...config };\n\n if (normalized.title) {\n normalized.title = normalized.title.trim();\n }\n if (normalized.description) {\n normalized.description = normalized.description.trim();\n }\n if (normalized.canonical) {\n normalized.canonical = normalizeUrl(normalized.canonical);\n }\n\n return normalized;\n}\n\n// ─── Title ────────────────────────────────────────────────────\n\n/**\n * Build a title string, optionally applying a template.\n * Template uses `%s` as the placeholder for the page title.\n *\n * @example\n * buildTitle(\"About\", \"%s | MySite\") // \"About | MySite\"\n * buildTitle(\"Home\") // \"Home\"\n */\nexport function buildTitle(title?: string, template?: string): string {\n if (!title) return \"\";\n const trimmed = title.trim();\n if (!template) return trimmed;\n return template.replace(/%s/g, trimmed);\n}\n\n/**\n * Build a description, truncating at a max length if specified.\n */\nexport function buildDescription(\n description?: string,\n maxLength?: number\n): string {\n if (!description) return \"\";\n const trimmed = description.trim();\n if (!maxLength || trimmed.length <= maxLength) return trimmed;\n return trimmed.slice(0, maxLength).trimEnd() + \"…\";\n}\n\n// ─── Canonical URL ────────────────────────────────────────────\n\n/**\n * Build a canonical URL from a base URL and optional path.\n */\nexport function buildCanonicalUrl(\n baseUrl: string,\n path?: string\n): string {\n const normalizedBase = normalizeUrl(baseUrl);\n if (!path || path === \"/\") return normalizedBase;\n const normalizedPath = path.startsWith(\"/\") ? path : `/${path}`;\n return normalizedBase + normalizeUrl(normalizedPath);\n}\n\n// ─── Robots ───────────────────────────────────────────────────\n\n/**\n * Build a robots meta content string from a config.\n *\n * @example\n * buildRobotsDirectives({ index: true, follow: true }) // \"index, follow\"\n * buildRobotsDirectives({ index: false, follow: false, noarchive: true })\n * // \"noindex, nofollow, noarchive\"\n */\nexport function buildRobotsDirectives(config?: RobotsConfig): string {\n if (!config) return \"\";\n\n const directives: string[] = [];\n\n if (config.index === false) directives.push(\"noindex\");\n else if (config.index === true) directives.push(\"index\");\n\n if (config.follow === false) directives.push(\"nofollow\");\n else if (config.follow === true) directives.push(\"follow\");\n\n if (config.noarchive) directives.push(\"noarchive\");\n if (config.nosnippet) directives.push(\"nosnippet\");\n if (config.noimageindex) directives.push(\"noimageindex\");\n if (config.notranslate) directives.push(\"notranslate\");\n\n if (config.maxSnippet !== undefined)\n directives.push(`max-snippet:${config.maxSnippet}`);\n if (config.maxImagePreview)\n directives.push(`max-image-preview:${config.maxImagePreview}`);\n if (config.maxVideoPreview !== undefined)\n directives.push(`max-video-preview:${config.maxVideoPreview}`);\n\n return directives.join(\", \");\n}\n\n// ─── Convenience helpers ──────────────────────────────────────\n\n/**\n * Create a noindex/nofollow robots config.\n */\nexport function noIndexNoFollow(): RobotsConfig {\n return { index: false, follow: false };\n}\n\n/**\n * Create a noindex robots config that still allows following links.\n */\nexport function noIndex(): RobotsConfig {\n return { index: false, follow: true };\n}\n\n// ─── Open Graph ───────────────────────────────────────────────\n\n/**\n * Build Open Graph meta tag entries from config.\n * Returns an array of { property, content } pairs.\n */\nexport function buildOpenGraph(\n config?: OpenGraphConfig\n): Array<{ property: string; content: string }> {\n if (!config) return [];\n\n const tags: Array<{ property: string; content: string }> = [];\n\n const simple: Array<[keyof OpenGraphConfig, string]> = [\n [\"title\", \"og:title\"],\n [\"description\", \"og:description\"],\n [\"url\", \"og:url\"],\n [\"siteName\", \"og:site_name\"],\n [\"type\", \"og:type\"],\n [\"locale\", \"og:locale\"],\n ];\n\n for (const [key, property] of simple) {\n const value = config[key];\n if (typeof value === \"string\" && value.trim()) {\n tags.push({ property, content: value.trim() });\n }\n }\n\n if (config.images) {\n for (const image of config.images) {\n if (!image.url) continue;\n tags.push({ property: \"og:image\", content: image.url });\n if (image.alt) tags.push({ property: \"og:image:alt\", content: image.alt });\n if (image.width)\n tags.push({\n property: \"og:image:width\",\n content: String(image.width),\n });\n if (image.height)\n tags.push({\n property: \"og:image:height\",\n content: String(image.height),\n });\n if (image.type)\n tags.push({ property: \"og:image:type\", content: image.type });\n }\n }\n\n return tags;\n}\n\n// ─── Twitter ──────────────────────────────────────────────────\n\n/**\n * Build Twitter card meta tag entries from config.\n * Returns an array of { name, content } pairs.\n */\nexport function buildTwitterMetadata(\n config?: TwitterConfig\n): Array<{ name: string; content: string }> {\n if (!config) return [];\n\n const tags: Array<{ name: string; content: string }> = [];\n\n if (config.card) tags.push({ name: \"twitter:card\", content: config.card });\n if (config.site) tags.push({ name: \"twitter:site\", content: config.site });\n if (config.creator)\n tags.push({ name: \"twitter:creator\", content: config.creator });\n if (config.title)\n tags.push({ name: \"twitter:title\", content: config.title });\n if (config.description)\n tags.push({ name: \"twitter:description\", content: config.description });\n if (config.image)\n tags.push({ name: \"twitter:image\", content: config.image });\n if (config.imageAlt)\n tags.push({ name: \"twitter:image:alt\", content: config.imageAlt });\n\n return tags;\n}\n\n// ─── Hreflang ─────────────────────────────────────────────────\n\n/**\n * Build alternate link entries for hreflang from an array of alternates.\n * Returns an array of { rel, hreflang, href } suitable for <link> tags.\n */\nexport function buildAlternateLinks(\n alternates?: AlternateLink[]\n): Array<{ rel: string; hreflang: string; href: string }> {\n if (!alternates || alternates.length === 0) return [];\n return alternates.map((alt) => ({\n rel: \"alternate\",\n hreflang: alt.hreflang,\n href: normalizeUrl(alt.href),\n }));\n}\n"]}
|
|
@@ -0,0 +1,2 @@
|
|
|
1
|
+
'use strict';var chunkFKDMHECL_cjs=require('./chunk-FKDMHECL.cjs'),t=require('react');function _interopDefault(e){return e&&e.__esModule?e:{default:e}}var t__default=/*#__PURE__*/_interopDefault(t);function C({title:c,titleTemplate:i,description:m,canonical:p,robots:R,openGraph:S,twitter:b,alternates:x,additionalMetaTags:l,additionalLinkTags:f,jsonLd:s,nonce:L}){let n=[],O=0,r=()=>`seo-${O++}`,y=chunkFKDMHECL_cjs.i(c,i);y&&n.push(t__default.default.createElement("title",{key:r()},y)),m?.trim()&&n.push(t__default.default.createElement("meta",{key:r(),name:"description",content:m.trim()})),p?.trim()&&n.push(t__default.default.createElement("link",{key:r(),rel:"canonical",href:p.trim()}));let u=chunkFKDMHECL_cjs.l(R);u&&n.push(t__default.default.createElement("meta",{key:r(),name:"robots",content:u}));let H=chunkFKDMHECL_cjs.o(S);for(let e of H)n.push(t__default.default.createElement("meta",{key:r(),property:e.property,content:e.content}));let J=chunkFKDMHECL_cjs.p(b);for(let e of J)n.push(t__default.default.createElement("meta",{key:r(),name:e.name,content:e.content}));let T=chunkFKDMHECL_cjs.q(x);for(let e of T)n.push(t__default.default.createElement("link",{key:r(),rel:e.rel,hrefLang:e.hreflang,href:e.href}));if(l)for(let e of l){let o={content:e.content};e.name&&(o.name=e.name),e.property&&(o.property=e.property),n.push(t__default.default.createElement("meta",{key:r(),...o}));}if(f)for(let e of f)n.push(t__default.default.createElement("link",{key:r(),...e}));if(s){let e=Array.isArray(s)?s:[s];for(let o of e)n.push(t__default.default.createElement("script",{key:r(),type:"application/ld+json",nonce:L,dangerouslySetInnerHTML:{__html:chunkFKDMHECL_cjs.a(o)}}));}return t__default.default.createElement(t__default.default.Fragment,null,...n)}function M({data:c,nonce:i}){return t__default.default.createElement("script",{type:"application/ld+json",nonce:i,dangerouslySetInnerHTML:{__html:chunkFKDMHECL_cjs.a(c)}})}exports.a=C;exports.b=M;//# sourceMappingURL=chunk-T7EB7Y2S.cjs.map
|
|
2
|
+
//# sourceMappingURL=chunk-T7EB7Y2S.cjs.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../src/components/SEOHead.tsx","../src/components/JsonLd.tsx"],"names":["SEOHead","title","titleTemplate","description","canonical","robots","openGraph","twitter","alternates","additionalMetaTags","additionalLinkTags","jsonLd","nonce","elements","key","k","resolvedTitle","buildTitle","React","robotsContent","buildRobotsDirectives","ogTags","buildOpenGraph","tag","twitterTags","buildTwitterMetadata","altLinks","buildAlternateLinks","link","meta","props","schemas","schema","safeJsonLdSerialize","JsonLd","data"],"mappings":"sMA4BO,SAASA,CAAAA,CAAQ,CACtB,MAAAC,CAAAA,CACA,aAAA,CAAAC,CAAAA,CACA,WAAA,CAAAC,EACA,SAAA,CAAAC,CAAAA,CACA,OAAAC,CAAAA,CACA,SAAA,CAAAC,EACA,OAAA,CAAAC,CAAAA,CACA,UAAA,CAAAC,CAAAA,CACA,mBAAAC,CAAAA,CACA,kBAAA,CAAAC,EACA,MAAA,CAAAC,CAAAA,CACA,MAAAC,CACF,CAAA,CAAqC,CACnC,IAAMC,EAAiC,EAAC,CACpCC,EAAM,CAAA,CACJC,CAAAA,CAAI,IAAM,CAAA,IAAA,EAAOD,CAAAA,EAAK,CAAA,CAAA,CAGtBE,CAAAA,CAAgBC,oBAAWhB,CAAAA,CAAOC,CAAa,EACjDc,CAAAA,EACFH,CAAAA,CAAS,KAAKK,kBAAAA,CAAM,aAAA,CAAc,OAAA,CAAS,CAAE,IAAKH,CAAAA,EAAI,EAAGC,CAAa,CAAC,EAIrEb,CAAAA,EAAa,IAAA,EAAK,EACpBU,CAAAA,CAAS,KACPK,kBAAAA,CAAM,aAAA,CAAc,OAAQ,CAC1B,GAAA,CAAKH,GAAE,CACP,IAAA,CAAM,aAAA,CACN,OAAA,CAASZ,EAAY,IAAA,EACvB,CAAC,CACH,CAAA,CAIEC,GAAW,IAAA,EAAK,EAClBS,CAAAA,CAAS,IAAA,CACPK,mBAAM,aAAA,CAAc,MAAA,CAAQ,CAC1B,GAAA,CAAKH,GAAE,CACP,GAAA,CAAK,WAAA,CACL,IAAA,CAAMX,EAAU,IAAA,EAClB,CAAC,CACH,CAAA,CAIF,IAAMe,CAAAA,CAAgBC,mBAAAA,CAAsBf,CAAM,CAAA,CAC9Cc,GACFN,CAAAA,CAAS,IAAA,CACPK,mBAAM,aAAA,CAAc,MAAA,CAAQ,CAC1B,GAAA,CAAKH,CAAAA,EAAE,CACP,IAAA,CAAM,SACN,OAAA,CAASI,CACX,CAAC,CACH,CAAA,CAIF,IAAME,CAAAA,CAASC,mBAAAA,CAAehB,CAAS,CAAA,CACvC,QAAWiB,CAAAA,IAAOF,CAAAA,CAChBR,EAAS,IAAA,CACPK,kBAAAA,CAAM,cAAc,MAAA,CAAQ,CAC1B,GAAA,CAAKH,CAAAA,GACL,QAAA,CAAUQ,CAAAA,CAAI,SACd,OAAA,CAASA,CAAAA,CAAI,OACf,CAAC,CACH,CAAA,CAIF,IAAMC,EAAcC,mBAAAA,CAAqBlB,CAAO,EAChD,IAAA,IAAWgB,CAAAA,IAAOC,EAChBX,CAAAA,CAAS,IAAA,CACPK,kBAAAA,CAAM,aAAA,CAAc,OAAQ,CAC1B,GAAA,CAAKH,GAAE,CACP,IAAA,CAAMQ,EAAI,IAAA,CACV,OAAA,CAASA,CAAAA,CAAI,OACf,CAAC,CACH,CAAA,CAIF,IAAMG,CAAAA,CAAWC,oBAAoBnB,CAAU,CAAA,CAC/C,IAAA,IAAWoB,CAAAA,IAAQF,EACjBb,CAAAA,CAAS,IAAA,CACPK,mBAAM,aAAA,CAAc,MAAA,CAAQ,CAC1B,GAAA,CAAKH,CAAAA,EAAE,CACP,GAAA,CAAKa,EAAK,GAAA,CACV,QAAA,CAAUA,EAAK,QAAA,CACf,IAAA,CAAMA,EAAK,IACb,CAAC,CACH,CAAA,CAIF,GAAInB,CAAAA,CACF,IAAA,IAAWoB,KAAQpB,CAAAA,CAAoB,CACrC,IAAMqB,CAAAA,CAAgC,CAAE,OAAA,CAASD,CAAAA,CAAK,OAAQ,CAAA,CAC1DA,CAAAA,CAAK,OAAMC,CAAAA,CAAM,IAAA,CAAOD,EAAK,IAAA,CAAA,CAC7BA,CAAAA,CAAK,QAAA,GAAUC,CAAAA,CAAM,SAAWD,CAAAA,CAAK,QAAA,CAAA,CACzChB,EAAS,IAAA,CAAKK,kBAAAA,CAAM,cAAc,MAAA,CAAQ,CAAE,GAAA,CAAKH,CAAAA,GAAK,GAAGe,CAAM,CAAC,CAAC,EACnE,CAIF,GAAIpB,CAAAA,CACF,IAAA,IAAWkB,CAAAA,IAAQlB,EACjBG,CAAAA,CAAS,IAAA,CAAKK,mBAAM,aAAA,CAAc,MAAA,CAAQ,CAAE,GAAA,CAAKH,CAAAA,EAAE,CAAG,GAAGa,CAAK,CAAC,CAAC,CAAA,CAKpE,GAAIjB,EAAQ,CACV,IAAMoB,CAAAA,CAAU,KAAA,CAAM,QAAQpB,CAAM,CAAA,CAAIA,EAAS,CAACA,CAAM,EACxD,IAAA,IAAWqB,CAAAA,IAAUD,CAAAA,CACnBlB,CAAAA,CAAS,KACPK,kBAAAA,CAAM,aAAA,CAAc,SAAU,CAC5B,GAAA,CAAKH,GAAE,CACP,IAAA,CAAM,qBAAA,CACN,KAAA,CAAAH,EACA,uBAAA,CAAyB,CACvB,OAAQqB,mBAAAA,CAAoBD,CAAM,CACpC,CACF,CAAC,CACH,EAEJ,CAEA,OAAOd,kBAAAA,CAAM,cAAcA,kBAAAA,CAAM,QAAA,CAAU,KAAM,GAAGL,CAAQ,CAC9D,CClJO,SAASqB,CAAAA,CAAO,CAAE,KAAAC,CAAAA,CAAM,KAAA,CAAAvB,CAAM,CAAA,CAAoC,CACvE,OAAOM,kBAAAA,CAAM,cAAc,QAAA,CAAU,CACnC,KAAM,qBAAA,CACN,KAAA,CAAAN,CAAAA,CACA,uBAAA,CAAyB,CACvB,MAAA,CAAQqB,mBAAAA,CAAoBE,CAAI,CAClC,CACF,CAAC,CACH","file":"chunk-T7EB7Y2S.cjs","sourcesContent":["import React from \"react\";\nimport type { SEOConfig } from \"../types/index.js\";\nimport {\n buildTitle,\n buildRobotsDirectives,\n buildOpenGraph,\n buildTwitterMetadata,\n buildAlternateLinks,\n} from \"../core/index.js\";\nimport { safeJsonLdSerialize } from \"../utils/index.js\";\n\nexport interface SEOHeadProps extends SEOConfig {\n /**\n * Optional nonce for CSP-compatible script tags.\n */\n nonce?: string;\n}\n\n/**\n * Generic SSR-safe React component that renders SEO-related tags.\n *\n * Renders: title, meta description, canonical link, robots meta,\n * Open Graph tags, Twitter tags, alternate/hreflang links,\n * additional meta/link tags, and JSON-LD scripts.\n *\n * Designed to be placed inside a <head> element in SSR apps.\n * Does NOT use any browser globals — fully SSR-compatible.\n */\nexport function SEOHead({\n title,\n titleTemplate,\n description,\n canonical,\n robots,\n openGraph,\n twitter,\n alternates,\n additionalMetaTags,\n additionalLinkTags,\n jsonLd,\n nonce,\n}: SEOHeadProps): React.ReactElement {\n const elements: React.ReactElement[] = [];\n let key = 0;\n const k = () => `seo-${key++}`;\n\n // Title\n const resolvedTitle = buildTitle(title, titleTemplate);\n if (resolvedTitle) {\n elements.push(React.createElement(\"title\", { key: k() }, resolvedTitle));\n }\n\n // Description\n if (description?.trim()) {\n elements.push(\n React.createElement(\"meta\", {\n key: k(),\n name: \"description\",\n content: description.trim(),\n })\n );\n }\n\n // Canonical\n if (canonical?.trim()) {\n elements.push(\n React.createElement(\"link\", {\n key: k(),\n rel: \"canonical\",\n href: canonical.trim(),\n })\n );\n }\n\n // Robots\n const robotsContent = buildRobotsDirectives(robots);\n if (robotsContent) {\n elements.push(\n React.createElement(\"meta\", {\n key: k(),\n name: \"robots\",\n content: robotsContent,\n })\n );\n }\n\n // Open Graph\n const ogTags = buildOpenGraph(openGraph);\n for (const tag of ogTags) {\n elements.push(\n React.createElement(\"meta\", {\n key: k(),\n property: tag.property,\n content: tag.content,\n })\n );\n }\n\n // Twitter\n const twitterTags = buildTwitterMetadata(twitter);\n for (const tag of twitterTags) {\n elements.push(\n React.createElement(\"meta\", {\n key: k(),\n name: tag.name,\n content: tag.content,\n })\n );\n }\n\n // Hreflang alternates\n const altLinks = buildAlternateLinks(alternates);\n for (const link of altLinks) {\n elements.push(\n React.createElement(\"link\", {\n key: k(),\n rel: link.rel,\n hrefLang: link.hreflang,\n href: link.href,\n })\n );\n }\n\n // Additional meta tags\n if (additionalMetaTags) {\n for (const meta of additionalMetaTags) {\n const props: Record<string, string> = { content: meta.content };\n if (meta.name) props.name = meta.name;\n if (meta.property) props.property = meta.property;\n elements.push(React.createElement(\"meta\", { key: k(), ...props }));\n }\n }\n\n // Additional link tags\n if (additionalLinkTags) {\n for (const link of additionalLinkTags) {\n elements.push(React.createElement(\"link\", { key: k(), ...link }));\n }\n }\n\n // JSON-LD\n if (jsonLd) {\n const schemas = Array.isArray(jsonLd) ? jsonLd : [jsonLd];\n for (const schema of schemas) {\n elements.push(\n React.createElement(\"script\", {\n key: k(),\n type: \"application/ld+json\",\n nonce,\n dangerouslySetInnerHTML: {\n __html: safeJsonLdSerialize(schema),\n },\n })\n );\n }\n }\n\n return React.createElement(React.Fragment, null, ...elements);\n}\n","import React from \"react\";\nimport { safeJsonLdSerialize } from \"../utils/index.js\";\n\nexport interface JsonLdProps {\n data: Record<string, unknown> | Array<Record<string, unknown>>;\n nonce?: string;\n}\n\n/**\n * Renders a <script type=\"application/ld+json\"> tag with safely serialized JSON-LD.\n * SSR-safe: no browser globals used.\n */\nexport function JsonLd({ data, nonce }: JsonLdProps): React.ReactElement {\n return React.createElement(\"script\", {\n type: \"application/ld+json\",\n nonce,\n dangerouslySetInnerHTML: {\n __html: safeJsonLdSerialize(data),\n },\n });\n}\n"]}
|
package/dist/components.cjs
CHANGED
|
@@ -1,2 +1,2 @@
|
|
|
1
|
-
'use strict';var
|
|
1
|
+
'use strict';var chunkT7EB7Y2S_cjs=require('./chunk-T7EB7Y2S.cjs');require('./chunk-FKDMHECL.cjs');Object.defineProperty(exports,"JsonLd",{enumerable:true,get:function(){return chunkT7EB7Y2S_cjs.b}});Object.defineProperty(exports,"SEOHead",{enumerable:true,get:function(){return chunkT7EB7Y2S_cjs.a}});//# sourceMappingURL=components.cjs.map
|
|
2
2
|
//# sourceMappingURL=components.cjs.map
|
package/dist/components.js
CHANGED
|
@@ -1,2 +1,2 @@
|
|
|
1
|
-
export{
|
|
1
|
+
export{b as JsonLd,a as SEOHead}from'./chunk-AYIAPQTP.js';import'./chunk-LP66SUGR.js';//# sourceMappingURL=components.js.map
|
|
2
2
|
//# sourceMappingURL=components.js.map
|
package/dist/index.cjs
CHANGED
|
@@ -1,2 +1,2 @@
|
|
|
1
|
-
'use strict';var chunk63ETSZTD_cjs=require('./chunk-63ETSZTD.cjs'),
|
|
1
|
+
'use strict';var chunk63ETSZTD_cjs=require('./chunk-63ETSZTD.cjs'),chunkT7EB7Y2S_cjs=require('./chunk-T7EB7Y2S.cjs'),chunkFKDMHECL_cjs=require('./chunk-FKDMHECL.cjs');Object.defineProperty(exports,"composeSchemas",{enumerable:true,get:function(){return chunk63ETSZTD_cjs.g}});Object.defineProperty(exports,"createArticleSchema",{enumerable:true,get:function(){return chunk63ETSZTD_cjs.d}});Object.defineProperty(exports,"createBreadcrumbSchema",{enumerable:true,get:function(){return chunk63ETSZTD_cjs.c}});Object.defineProperty(exports,"createFAQSchema",{enumerable:true,get:function(){return chunk63ETSZTD_cjs.f}});Object.defineProperty(exports,"createOrganizationSchema",{enumerable:true,get:function(){return chunk63ETSZTD_cjs.a}});Object.defineProperty(exports,"createProductSchema",{enumerable:true,get:function(){return chunk63ETSZTD_cjs.e}});Object.defineProperty(exports,"createWebsiteSchema",{enumerable:true,get:function(){return chunk63ETSZTD_cjs.b}});Object.defineProperty(exports,"JsonLd",{enumerable:true,get:function(){return chunkT7EB7Y2S_cjs.b}});Object.defineProperty(exports,"SEOHead",{enumerable:true,get:function(){return chunkT7EB7Y2S_cjs.a}});Object.defineProperty(exports,"buildAlternateLinks",{enumerable:true,get:function(){return chunkFKDMHECL_cjs.q}});Object.defineProperty(exports,"buildCanonicalUrl",{enumerable:true,get:function(){return chunkFKDMHECL_cjs.k}});Object.defineProperty(exports,"buildDescription",{enumerable:true,get:function(){return chunkFKDMHECL_cjs.j}});Object.defineProperty(exports,"buildFullUrl",{enumerable:true,get:function(){return chunkFKDMHECL_cjs.e}});Object.defineProperty(exports,"buildOpenGraph",{enumerable:true,get:function(){return chunkFKDMHECL_cjs.o}});Object.defineProperty(exports,"buildRobotsDirectives",{enumerable:true,get:function(){return chunkFKDMHECL_cjs.l}});Object.defineProperty(exports,"buildTitle",{enumerable:true,get:function(){return chunkFKDMHECL_cjs.i}});Object.defineProperty(exports,"buildTwitterMetadata",{enumerable:true,get:function(){return chunkFKDMHECL_cjs.p}});Object.defineProperty(exports,"createSEOConfig",{enumerable:true,get:function(){return chunkFKDMHECL_cjs.f}});Object.defineProperty(exports,"deepMerge",{enumerable:true,get:function(){return chunkFKDMHECL_cjs.c}});Object.defineProperty(exports,"mergeSEOConfig",{enumerable:true,get:function(){return chunkFKDMHECL_cjs.g}});Object.defineProperty(exports,"noIndex",{enumerable:true,get:function(){return chunkFKDMHECL_cjs.n}});Object.defineProperty(exports,"noIndexNoFollow",{enumerable:true,get:function(){return chunkFKDMHECL_cjs.m}});Object.defineProperty(exports,"normalizeSEOConfig",{enumerable:true,get:function(){return chunkFKDMHECL_cjs.h}});Object.defineProperty(exports,"normalizeUrl",{enumerable:true,get:function(){return chunkFKDMHECL_cjs.d}});Object.defineProperty(exports,"omitEmpty",{enumerable:true,get:function(){return chunkFKDMHECL_cjs.b}});Object.defineProperty(exports,"safeJsonLdSerialize",{enumerable:true,get:function(){return chunkFKDMHECL_cjs.a}});//# sourceMappingURL=index.cjs.map
|
|
2
2
|
//# sourceMappingURL=index.cjs.map
|
package/dist/index.js
CHANGED
|
@@ -1,2 +1,2 @@
|
|
|
1
|
-
export{g as composeSchemas,d as createArticleSchema,c as createBreadcrumbSchema,f as createFAQSchema,a as createOrganizationSchema,e as createProductSchema,b as createWebsiteSchema}from'./chunk-ES4OXVOR.js';export{
|
|
1
|
+
export{g as composeSchemas,d as createArticleSchema,c as createBreadcrumbSchema,f as createFAQSchema,a as createOrganizationSchema,e as createProductSchema,b as createWebsiteSchema}from'./chunk-ES4OXVOR.js';export{b as JsonLd,a as SEOHead}from'./chunk-AYIAPQTP.js';export{q as buildAlternateLinks,k as buildCanonicalUrl,j as buildDescription,e as buildFullUrl,o as buildOpenGraph,l as buildRobotsDirectives,i as buildTitle,p as buildTwitterMetadata,f as createSEOConfig,c as deepMerge,g as mergeSEOConfig,n as noIndex,m as noIndexNoFollow,h as normalizeSEOConfig,d as normalizeUrl,b as omitEmpty,a as safeJsonLdSerialize}from'./chunk-LP66SUGR.js';//# sourceMappingURL=index.js.map
|
|
2
2
|
//# sourceMappingURL=index.js.map
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "react-ssr-seo-toolkit",
|
|
3
|
-
"version": "1.0
|
|
3
|
+
"version": "1.1.0",
|
|
4
4
|
"description": "Framework-agnostic SEO utilities, metadata builders, structured data helpers, and React components for SSR applications",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"seo",
|
|
@@ -64,6 +64,26 @@
|
|
|
64
64
|
"types": "./dist/components.d.cts",
|
|
65
65
|
"default": "./dist/components.cjs"
|
|
66
66
|
}
|
|
67
|
+
},
|
|
68
|
+
"./adapters/nextjs": {
|
|
69
|
+
"import": {
|
|
70
|
+
"types": "./dist/adapters/nextjs.d.ts",
|
|
71
|
+
"default": "./dist/adapters/nextjs.js"
|
|
72
|
+
},
|
|
73
|
+
"require": {
|
|
74
|
+
"types": "./dist/adapters/nextjs.d.cts",
|
|
75
|
+
"default": "./dist/adapters/nextjs.cjs"
|
|
76
|
+
}
|
|
77
|
+
},
|
|
78
|
+
"./adapters/react-router": {
|
|
79
|
+
"import": {
|
|
80
|
+
"types": "./dist/adapters/react-router.d.ts",
|
|
81
|
+
"default": "./dist/adapters/react-router.js"
|
|
82
|
+
},
|
|
83
|
+
"require": {
|
|
84
|
+
"types": "./dist/adapters/react-router.d.cts",
|
|
85
|
+
"default": "./dist/adapters/react-router.cjs"
|
|
86
|
+
}
|
|
67
87
|
}
|
|
68
88
|
},
|
|
69
89
|
"files": [
|
package/dist/chunk-QBHCTDUJ.cjs
DELETED
|
@@ -1,2 +0,0 @@
|
|
|
1
|
-
'use strict';var o=require('react');function _interopDefault(e){return e&&e.__esModule?e:{default:e}}var o__default=/*#__PURE__*/_interopDefault(o);function g(t){let e=JSON.stringify(t,(n,r)=>{if(r!=null)return r});return e?e.replace(/</g,"\\u003c").replace(/>/g,"\\u003e").replace(/&/g,"\\u0026").replace(/\u2028/g,"\\u2028").replace(/\u2029/g,"\\u2029"):"{}"}function M(t){let e={};for(let n of Object.keys(t)){let r=t[n];r!=null&&r!==""&&(e[n]=r);}return e}function y(t,e){let n={...t},r=e,c=t;for(let a of Object.keys(r)){let p=r[a],f=c[a];p!==void 0&&(p!==null&&typeof p=="object"&&!Array.isArray(p)&&f!==null&&typeof f=="object"&&!Array.isArray(f)?n[a]=y(f,p):n[a]=p);}return n}function u(t){let e=t.trim();return e==="/"||e===""?e:e.replace(/\/+$/,"")}function v(t,e){let n=u(t);if(!e||e==="/")return n;let r=e.startsWith("/")?e:`/${e}`;return n+u(r)}function I(t={}){return E(t)}function _(t,e){let n=y(t,e);return e.alternates!==void 0&&(n.alternates=e.alternates),e.additionalMetaTags!==void 0&&(n.additionalMetaTags=e.additionalMetaTags),e.additionalLinkTags!==void 0&&(n.additionalLinkTags=e.additionalLinkTags),e.jsonLd!==void 0&&(n.jsonLd=e.jsonLd),E(n)}function E(t){let e={...t};return e.title&&(e.title=e.title.trim()),e.description&&(e.description=e.description.trim()),e.canonical&&(e.canonical=u(e.canonical)),e}function b(t,e){if(!t)return "";let n=t.trim();return e?e.replace(/%s/g,n):n}function G(t,e){if(!t)return "";let n=t.trim();return !e||n.length<=e?n:n.slice(0,e).trimEnd()+"\u2026"}function H(t,e){let n=u(t);if(!e||e==="/")return n;let r=e.startsWith("/")?e:`/${e}`;return n+u(r)}function O(t){if(!t)return "";let e=[];return t.index===false?e.push("noindex"):t.index===true&&e.push("index"),t.follow===false?e.push("nofollow"):t.follow===true&&e.push("follow"),t.noarchive&&e.push("noarchive"),t.nosnippet&&e.push("nosnippet"),t.noimageindex&&e.push("noimageindex"),t.notranslate&&e.push("notranslate"),t.maxSnippet!==void 0&&e.push(`max-snippet:${t.maxSnippet}`),t.maxImagePreview&&e.push(`max-image-preview:${t.maxImagePreview}`),t.maxVideoPreview!==void 0&&e.push(`max-video-preview:${t.maxVideoPreview}`),e.join(", ")}function U(){return {index:false,follow:false}}function V(){return {index:false,follow:true}}function S(t){if(!t)return [];let e=[],n=[["title","og:title"],["description","og:description"],["url","og:url"],["siteName","og:site_name"],["type","og:type"],["locale","og:locale"]];for(let[r,c]of n){let a=t[r];typeof a=="string"&&a.trim()&&e.push({property:c,content:a.trim()});}if(t.images)for(let r of t.images)r.url&&(e.push({property:"og:image",content:r.url}),r.alt&&e.push({property:"og:image:alt",content:r.alt}),r.width&&e.push({property:"og:image:width",content:String(r.width)}),r.height&&e.push({property:"og:image:height",content:String(r.height)}),r.type&&e.push({property:"og:image:type",content:r.type}));return e}function C(t){if(!t)return [];let e=[];return t.card&&e.push({name:"twitter:card",content:t.card}),t.site&&e.push({name:"twitter:site",content:t.site}),t.creator&&e.push({name:"twitter:creator",content:t.creator}),t.title&&e.push({name:"twitter:title",content:t.title}),t.description&&e.push({name:"twitter:description",content:t.description}),t.image&&e.push({name:"twitter:image",content:t.image}),t.imageAlt&&e.push({name:"twitter:image:alt",content:t.imageAlt}),e}function T(t){return !t||t.length===0?[]:t.map(e=>({rel:"alternate",hreflang:e.hreflang,href:u(e.href)}))}function W({title:t,titleTemplate:e,description:n,canonical:r,robots:c,openGraph:a,twitter:p,alternates:f,additionalMetaTags:h,additionalLinkTags:x,jsonLd:d,nonce:R}){let s=[],A=0,l=()=>`seo-${A++}`,k=b(t,e);k&&s.push(o__default.default.createElement("title",{key:l()},k)),n?.trim()&&s.push(o__default.default.createElement("meta",{key:l(),name:"description",content:n.trim()})),r?.trim()&&s.push(o__default.default.createElement("link",{key:l(),rel:"canonical",href:r.trim()}));let w=O(c);w&&s.push(o__default.default.createElement("meta",{key:l(),name:"robots",content:w}));let L=S(a);for(let i of L)s.push(o__default.default.createElement("meta",{key:l(),property:i.property,content:i.content}));let P=C(p);for(let i of P)s.push(o__default.default.createElement("meta",{key:l(),name:i.name,content:i.content}));let j=T(f);for(let i of j)s.push(o__default.default.createElement("link",{key:l(),rel:i.rel,hrefLang:i.hreflang,href:i.href}));if(h)for(let i of h){let m={content:i.content};i.name&&(m.name=i.name),i.property&&(m.property=i.property),s.push(o__default.default.createElement("meta",{key:l(),...m}));}if(x)for(let i of x)s.push(o__default.default.createElement("link",{key:l(),...i}));if(d){let i=Array.isArray(d)?d:[d];for(let m of i)s.push(o__default.default.createElement("script",{key:l(),type:"application/ld+json",nonce:R,dangerouslySetInnerHTML:{__html:g(m)}}));}return o__default.default.createElement(o__default.default.Fragment,null,...s)}function X({data:t,nonce:e}){return o__default.default.createElement("script",{type:"application/ld+json",nonce:e,dangerouslySetInnerHTML:{__html:g(t)}})}exports.a=g;exports.b=M;exports.c=y;exports.d=u;exports.e=v;exports.f=I;exports.g=_;exports.h=E;exports.i=b;exports.j=G;exports.k=H;exports.l=O;exports.m=U;exports.n=V;exports.o=S;exports.p=C;exports.q=T;exports.r=W;exports.s=X;//# sourceMappingURL=chunk-QBHCTDUJ.cjs.map
|
|
2
|
-
//# sourceMappingURL=chunk-QBHCTDUJ.cjs.map
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"sources":["../src/utils/index.ts","../src/core/index.ts","../src/components/SEOHead.tsx","../src/components/JsonLd.tsx"],"names":["safeJsonLdSerialize","data","json","_key","value","omitEmpty","obj","result","key","val","deepMerge","base","override","overrideObj","baseObj","overrideVal","baseVal","normalizeUrl","url","trimmed","buildFullUrl","path","normalizedBase","normalizedPath","createSEOConfig","config","normalizeSEOConfig","mergeSEOConfig","merged","normalized","buildTitle","title","template","buildDescription","description","maxLength","buildCanonicalUrl","baseUrl","buildRobotsDirectives","directives","noIndexNoFollow","noIndex","buildOpenGraph","tags","simple","property","image","buildTwitterMetadata","buildAlternateLinks","alternates","alt","SEOHead","titleTemplate","canonical","robots","openGraph","twitter","additionalMetaTags","additionalLinkTags","jsonLd","nonce","elements","k","resolvedTitle","React","robotsContent","ogTags","tag","twitterTags","altLinks","link","meta","props","schemas","schema","JsonLd"],"mappings":"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","file":"chunk-QBHCTDUJ.cjs","sourcesContent":["/**\n * Safely serialize a value to JSON for use in a <script> tag.\n * Escapes characters that could break out of script context.\n */\nexport function safeJsonLdSerialize(data: unknown): string {\n const json = JSON.stringify(data, (_key, value) => {\n if (value === undefined || value === null) return undefined;\n return value;\n });\n\n if (!json) return \"{}\";\n\n return json\n .replace(/</g, \"\\\\u003c\")\n .replace(/>/g, \"\\\\u003e\")\n .replace(/&/g, \"\\\\u0026\")\n .replace(/\\u2028/g, \"\\\\u2028\")\n .replace(/\\u2029/g, \"\\\\u2029\");\n}\n\n/**\n * Strip undefined/null values from an object (shallow).\n */\nexport function omitEmpty<T extends Record<string, unknown>>(\n obj: T\n): Partial<T> {\n const result: Partial<T> = {};\n for (const key of Object.keys(obj) as Array<keyof T>) {\n const val = obj[key];\n if (val !== undefined && val !== null && val !== \"\") {\n result[key] = val;\n }\n }\n return result;\n}\n\n/**\n * Deep-merge two objects. Arrays are replaced, not concatenated.\n */\nexport function deepMerge<T>(\n base: T,\n override: Partial<T>\n): T {\n const result = { ...base } as Record<string, unknown>;\n const overrideObj = override as Record<string, unknown>;\n const baseObj = base as Record<string, unknown>;\n\n for (const key of Object.keys(overrideObj)) {\n const overrideVal = overrideObj[key];\n const baseVal = baseObj[key];\n\n if (overrideVal === undefined) continue;\n\n if (\n overrideVal !== null &&\n typeof overrideVal === \"object\" &&\n !Array.isArray(overrideVal) &&\n baseVal !== null &&\n typeof baseVal === \"object\" &&\n !Array.isArray(baseVal)\n ) {\n result[key] = deepMerge(\n baseVal as Record<string, unknown>,\n overrideVal as Record<string, unknown>\n );\n } else {\n result[key] = overrideVal;\n }\n }\n\n return result as T;\n}\n\n/**\n * Normalize a URL by trimming whitespace and removing trailing slashes\n * (except for root \"/\").\n */\nexport function normalizeUrl(url: string): string {\n const trimmed = url.trim();\n if (trimmed === \"/\" || trimmed === \"\") return trimmed;\n return trimmed.replace(/\\/+$/, \"\");\n}\n\n/**\n * Build a full canonical URL from a base and a path.\n */\nexport function buildFullUrl(base: string, path?: string): string {\n const normalizedBase = normalizeUrl(base);\n if (!path || path === \"/\") return normalizedBase;\n const normalizedPath = path.startsWith(\"/\") ? path : `/${path}`;\n return normalizedBase + normalizeUrl(normalizedPath);\n}\n","import type {\n SEOConfig,\n OpenGraphConfig,\n TwitterConfig,\n RobotsConfig,\n AlternateLink,\n} from \"../types/index.js\";\nimport { deepMerge, normalizeUrl } from \"../utils/index.js\";\n\n// ─── Config builder ───────────────────────────────────────────\n\n/**\n * Create an SEO config with sensible defaults.\n */\nexport function createSEOConfig(config: SEOConfig = {}): SEOConfig {\n return normalizeSEOConfig(config);\n}\n\n/**\n * Deep-merge a base SEO config with page-level overrides.\n * Useful for combining a global site config with per-page config.\n */\nexport function mergeSEOConfig(\n base: SEOConfig,\n override: SEOConfig\n): SEOConfig {\n const merged = deepMerge(base, override);\n\n // Arrays should be replaced, not deep-merged\n if (override.alternates !== undefined) {\n merged.alternates = override.alternates;\n }\n if (override.additionalMetaTags !== undefined) {\n merged.additionalMetaTags = override.additionalMetaTags;\n }\n if (override.additionalLinkTags !== undefined) {\n merged.additionalLinkTags = override.additionalLinkTags;\n }\n if (override.jsonLd !== undefined) {\n merged.jsonLd = override.jsonLd;\n }\n\n return normalizeSEOConfig(merged);\n}\n\n/**\n * Normalize an SEO config: trim strings, normalize URLs, remove empties.\n */\nexport function normalizeSEOConfig(config: SEOConfig): SEOConfig {\n const normalized: SEOConfig = { ...config };\n\n if (normalized.title) {\n normalized.title = normalized.title.trim();\n }\n if (normalized.description) {\n normalized.description = normalized.description.trim();\n }\n if (normalized.canonical) {\n normalized.canonical = normalizeUrl(normalized.canonical);\n }\n\n return normalized;\n}\n\n// ─── Title ────────────────────────────────────────────────────\n\n/**\n * Build a title string, optionally applying a template.\n * Template uses `%s` as the placeholder for the page title.\n *\n * @example\n * buildTitle(\"About\", \"%s | MySite\") // \"About | MySite\"\n * buildTitle(\"Home\") // \"Home\"\n */\nexport function buildTitle(title?: string, template?: string): string {\n if (!title) return \"\";\n const trimmed = title.trim();\n if (!template) return trimmed;\n return template.replace(/%s/g, trimmed);\n}\n\n/**\n * Build a description, truncating at a max length if specified.\n */\nexport function buildDescription(\n description?: string,\n maxLength?: number\n): string {\n if (!description) return \"\";\n const trimmed = description.trim();\n if (!maxLength || trimmed.length <= maxLength) return trimmed;\n return trimmed.slice(0, maxLength).trimEnd() + \"…\";\n}\n\n// ─── Canonical URL ────────────────────────────────────────────\n\n/**\n * Build a canonical URL from a base URL and optional path.\n */\nexport function buildCanonicalUrl(\n baseUrl: string,\n path?: string\n): string {\n const normalizedBase = normalizeUrl(baseUrl);\n if (!path || path === \"/\") return normalizedBase;\n const normalizedPath = path.startsWith(\"/\") ? path : `/${path}`;\n return normalizedBase + normalizeUrl(normalizedPath);\n}\n\n// ─── Robots ───────────────────────────────────────────────────\n\n/**\n * Build a robots meta content string from a config.\n *\n * @example\n * buildRobotsDirectives({ index: true, follow: true }) // \"index, follow\"\n * buildRobotsDirectives({ index: false, follow: false, noarchive: true })\n * // \"noindex, nofollow, noarchive\"\n */\nexport function buildRobotsDirectives(config?: RobotsConfig): string {\n if (!config) return \"\";\n\n const directives: string[] = [];\n\n if (config.index === false) directives.push(\"noindex\");\n else if (config.index === true) directives.push(\"index\");\n\n if (config.follow === false) directives.push(\"nofollow\");\n else if (config.follow === true) directives.push(\"follow\");\n\n if (config.noarchive) directives.push(\"noarchive\");\n if (config.nosnippet) directives.push(\"nosnippet\");\n if (config.noimageindex) directives.push(\"noimageindex\");\n if (config.notranslate) directives.push(\"notranslate\");\n\n if (config.maxSnippet !== undefined)\n directives.push(`max-snippet:${config.maxSnippet}`);\n if (config.maxImagePreview)\n directives.push(`max-image-preview:${config.maxImagePreview}`);\n if (config.maxVideoPreview !== undefined)\n directives.push(`max-video-preview:${config.maxVideoPreview}`);\n\n return directives.join(\", \");\n}\n\n// ─── Convenience helpers ──────────────────────────────────────\n\n/**\n * Create a noindex/nofollow robots config.\n */\nexport function noIndexNoFollow(): RobotsConfig {\n return { index: false, follow: false };\n}\n\n/**\n * Create a noindex robots config that still allows following links.\n */\nexport function noIndex(): RobotsConfig {\n return { index: false, follow: true };\n}\n\n// ─── Open Graph ───────────────────────────────────────────────\n\n/**\n * Build Open Graph meta tag entries from config.\n * Returns an array of { property, content } pairs.\n */\nexport function buildOpenGraph(\n config?: OpenGraphConfig\n): Array<{ property: string; content: string }> {\n if (!config) return [];\n\n const tags: Array<{ property: string; content: string }> = [];\n\n const simple: Array<[keyof OpenGraphConfig, string]> = [\n [\"title\", \"og:title\"],\n [\"description\", \"og:description\"],\n [\"url\", \"og:url\"],\n [\"siteName\", \"og:site_name\"],\n [\"type\", \"og:type\"],\n [\"locale\", \"og:locale\"],\n ];\n\n for (const [key, property] of simple) {\n const value = config[key];\n if (typeof value === \"string\" && value.trim()) {\n tags.push({ property, content: value.trim() });\n }\n }\n\n if (config.images) {\n for (const image of config.images) {\n if (!image.url) continue;\n tags.push({ property: \"og:image\", content: image.url });\n if (image.alt) tags.push({ property: \"og:image:alt\", content: image.alt });\n if (image.width)\n tags.push({\n property: \"og:image:width\",\n content: String(image.width),\n });\n if (image.height)\n tags.push({\n property: \"og:image:height\",\n content: String(image.height),\n });\n if (image.type)\n tags.push({ property: \"og:image:type\", content: image.type });\n }\n }\n\n return tags;\n}\n\n// ─── Twitter ──────────────────────────────────────────────────\n\n/**\n * Build Twitter card meta tag entries from config.\n * Returns an array of { name, content } pairs.\n */\nexport function buildTwitterMetadata(\n config?: TwitterConfig\n): Array<{ name: string; content: string }> {\n if (!config) return [];\n\n const tags: Array<{ name: string; content: string }> = [];\n\n if (config.card) tags.push({ name: \"twitter:card\", content: config.card });\n if (config.site) tags.push({ name: \"twitter:site\", content: config.site });\n if (config.creator)\n tags.push({ name: \"twitter:creator\", content: config.creator });\n if (config.title)\n tags.push({ name: \"twitter:title\", content: config.title });\n if (config.description)\n tags.push({ name: \"twitter:description\", content: config.description });\n if (config.image)\n tags.push({ name: \"twitter:image\", content: config.image });\n if (config.imageAlt)\n tags.push({ name: \"twitter:image:alt\", content: config.imageAlt });\n\n return tags;\n}\n\n// ─── Hreflang ─────────────────────────────────────────────────\n\n/**\n * Build alternate link entries for hreflang from an array of alternates.\n * Returns an array of { rel, hreflang, href } suitable for <link> tags.\n */\nexport function buildAlternateLinks(\n alternates?: AlternateLink[]\n): Array<{ rel: string; hreflang: string; href: string }> {\n if (!alternates || alternates.length === 0) return [];\n return alternates.map((alt) => ({\n rel: \"alternate\",\n hreflang: alt.hreflang,\n href: normalizeUrl(alt.href),\n }));\n}\n","import React from \"react\";\nimport type { SEOConfig } from \"../types/index.js\";\nimport {\n buildTitle,\n buildRobotsDirectives,\n buildOpenGraph,\n buildTwitterMetadata,\n buildAlternateLinks,\n} from \"../core/index.js\";\nimport { safeJsonLdSerialize } from \"../utils/index.js\";\n\nexport interface SEOHeadProps extends SEOConfig {\n /**\n * Optional nonce for CSP-compatible script tags.\n */\n nonce?: string;\n}\n\n/**\n * Generic SSR-safe React component that renders SEO-related tags.\n *\n * Renders: title, meta description, canonical link, robots meta,\n * Open Graph tags, Twitter tags, alternate/hreflang links,\n * additional meta/link tags, and JSON-LD scripts.\n *\n * Designed to be placed inside a <head> element in SSR apps.\n * Does NOT use any browser globals — fully SSR-compatible.\n */\nexport function SEOHead({\n title,\n titleTemplate,\n description,\n canonical,\n robots,\n openGraph,\n twitter,\n alternates,\n additionalMetaTags,\n additionalLinkTags,\n jsonLd,\n nonce,\n}: SEOHeadProps): React.ReactElement {\n const elements: React.ReactElement[] = [];\n let key = 0;\n const k = () => `seo-${key++}`;\n\n // Title\n const resolvedTitle = buildTitle(title, titleTemplate);\n if (resolvedTitle) {\n elements.push(React.createElement(\"title\", { key: k() }, resolvedTitle));\n }\n\n // Description\n if (description?.trim()) {\n elements.push(\n React.createElement(\"meta\", {\n key: k(),\n name: \"description\",\n content: description.trim(),\n })\n );\n }\n\n // Canonical\n if (canonical?.trim()) {\n elements.push(\n React.createElement(\"link\", {\n key: k(),\n rel: \"canonical\",\n href: canonical.trim(),\n })\n );\n }\n\n // Robots\n const robotsContent = buildRobotsDirectives(robots);\n if (robotsContent) {\n elements.push(\n React.createElement(\"meta\", {\n key: k(),\n name: \"robots\",\n content: robotsContent,\n })\n );\n }\n\n // Open Graph\n const ogTags = buildOpenGraph(openGraph);\n for (const tag of ogTags) {\n elements.push(\n React.createElement(\"meta\", {\n key: k(),\n property: tag.property,\n content: tag.content,\n })\n );\n }\n\n // Twitter\n const twitterTags = buildTwitterMetadata(twitter);\n for (const tag of twitterTags) {\n elements.push(\n React.createElement(\"meta\", {\n key: k(),\n name: tag.name,\n content: tag.content,\n })\n );\n }\n\n // Hreflang alternates\n const altLinks = buildAlternateLinks(alternates);\n for (const link of altLinks) {\n elements.push(\n React.createElement(\"link\", {\n key: k(),\n rel: link.rel,\n hrefLang: link.hreflang,\n href: link.href,\n })\n );\n }\n\n // Additional meta tags\n if (additionalMetaTags) {\n for (const meta of additionalMetaTags) {\n const props: Record<string, string> = { content: meta.content };\n if (meta.name) props.name = meta.name;\n if (meta.property) props.property = meta.property;\n elements.push(React.createElement(\"meta\", { key: k(), ...props }));\n }\n }\n\n // Additional link tags\n if (additionalLinkTags) {\n for (const link of additionalLinkTags) {\n elements.push(React.createElement(\"link\", { key: k(), ...link }));\n }\n }\n\n // JSON-LD\n if (jsonLd) {\n const schemas = Array.isArray(jsonLd) ? jsonLd : [jsonLd];\n for (const schema of schemas) {\n elements.push(\n React.createElement(\"script\", {\n key: k(),\n type: \"application/ld+json\",\n nonce,\n dangerouslySetInnerHTML: {\n __html: safeJsonLdSerialize(schema),\n },\n })\n );\n }\n }\n\n return React.createElement(React.Fragment, null, ...elements);\n}\n","import React from \"react\";\nimport { safeJsonLdSerialize } from \"../utils/index.js\";\n\nexport interface JsonLdProps {\n data: Record<string, unknown> | Array<Record<string, unknown>>;\n nonce?: string;\n}\n\n/**\n * Renders a <script type=\"application/ld+json\"> tag with safely serialized JSON-LD.\n * SSR-safe: no browser globals used.\n */\nexport function JsonLd({ data, nonce }: JsonLdProps): React.ReactElement {\n return React.createElement(\"script\", {\n type: \"application/ld+json\",\n nonce,\n dangerouslySetInnerHTML: {\n __html: safeJsonLdSerialize(data),\n },\n });\n}\n"]}
|
package/dist/chunk-YMCW2G4X.js
DELETED
|
@@ -1,2 +0,0 @@
|
|
|
1
|
-
import o from'react';function g(t){let e=JSON.stringify(t,(n,r)=>{if(r!=null)return r});return e?e.replace(/</g,"\\u003c").replace(/>/g,"\\u003e").replace(/&/g,"\\u0026").replace(/\u2028/g,"\\u2028").replace(/\u2029/g,"\\u2029"):"{}"}function M(t){let e={};for(let n of Object.keys(t)){let r=t[n];r!=null&&r!==""&&(e[n]=r);}return e}function y(t,e){let n={...t},r=e,c=t;for(let a of Object.keys(r)){let p=r[a],f=c[a];p!==void 0&&(p!==null&&typeof p=="object"&&!Array.isArray(p)&&f!==null&&typeof f=="object"&&!Array.isArray(f)?n[a]=y(f,p):n[a]=p);}return n}function u(t){let e=t.trim();return e==="/"||e===""?e:e.replace(/\/+$/,"")}function v(t,e){let n=u(t);if(!e||e==="/")return n;let r=e.startsWith("/")?e:`/${e}`;return n+u(r)}function I(t={}){return E(t)}function _(t,e){let n=y(t,e);return e.alternates!==void 0&&(n.alternates=e.alternates),e.additionalMetaTags!==void 0&&(n.additionalMetaTags=e.additionalMetaTags),e.additionalLinkTags!==void 0&&(n.additionalLinkTags=e.additionalLinkTags),e.jsonLd!==void 0&&(n.jsonLd=e.jsonLd),E(n)}function E(t){let e={...t};return e.title&&(e.title=e.title.trim()),e.description&&(e.description=e.description.trim()),e.canonical&&(e.canonical=u(e.canonical)),e}function b(t,e){if(!t)return "";let n=t.trim();return e?e.replace(/%s/g,n):n}function G(t,e){if(!t)return "";let n=t.trim();return !e||n.length<=e?n:n.slice(0,e).trimEnd()+"\u2026"}function H(t,e){let n=u(t);if(!e||e==="/")return n;let r=e.startsWith("/")?e:`/${e}`;return n+u(r)}function O(t){if(!t)return "";let e=[];return t.index===false?e.push("noindex"):t.index===true&&e.push("index"),t.follow===false?e.push("nofollow"):t.follow===true&&e.push("follow"),t.noarchive&&e.push("noarchive"),t.nosnippet&&e.push("nosnippet"),t.noimageindex&&e.push("noimageindex"),t.notranslate&&e.push("notranslate"),t.maxSnippet!==void 0&&e.push(`max-snippet:${t.maxSnippet}`),t.maxImagePreview&&e.push(`max-image-preview:${t.maxImagePreview}`),t.maxVideoPreview!==void 0&&e.push(`max-video-preview:${t.maxVideoPreview}`),e.join(", ")}function U(){return {index:false,follow:false}}function V(){return {index:false,follow:true}}function S(t){if(!t)return [];let e=[],n=[["title","og:title"],["description","og:description"],["url","og:url"],["siteName","og:site_name"],["type","og:type"],["locale","og:locale"]];for(let[r,c]of n){let a=t[r];typeof a=="string"&&a.trim()&&e.push({property:c,content:a.trim()});}if(t.images)for(let r of t.images)r.url&&(e.push({property:"og:image",content:r.url}),r.alt&&e.push({property:"og:image:alt",content:r.alt}),r.width&&e.push({property:"og:image:width",content:String(r.width)}),r.height&&e.push({property:"og:image:height",content:String(r.height)}),r.type&&e.push({property:"og:image:type",content:r.type}));return e}function C(t){if(!t)return [];let e=[];return t.card&&e.push({name:"twitter:card",content:t.card}),t.site&&e.push({name:"twitter:site",content:t.site}),t.creator&&e.push({name:"twitter:creator",content:t.creator}),t.title&&e.push({name:"twitter:title",content:t.title}),t.description&&e.push({name:"twitter:description",content:t.description}),t.image&&e.push({name:"twitter:image",content:t.image}),t.imageAlt&&e.push({name:"twitter:image:alt",content:t.imageAlt}),e}function T(t){return !t||t.length===0?[]:t.map(e=>({rel:"alternate",hreflang:e.hreflang,href:u(e.href)}))}function W({title:t,titleTemplate:e,description:n,canonical:r,robots:c,openGraph:a,twitter:p,alternates:f,additionalMetaTags:h,additionalLinkTags:x,jsonLd:d,nonce:R}){let s=[],A=0,l=()=>`seo-${A++}`,k=b(t,e);k&&s.push(o.createElement("title",{key:l()},k)),n?.trim()&&s.push(o.createElement("meta",{key:l(),name:"description",content:n.trim()})),r?.trim()&&s.push(o.createElement("link",{key:l(),rel:"canonical",href:r.trim()}));let w=O(c);w&&s.push(o.createElement("meta",{key:l(),name:"robots",content:w}));let L=S(a);for(let i of L)s.push(o.createElement("meta",{key:l(),property:i.property,content:i.content}));let P=C(p);for(let i of P)s.push(o.createElement("meta",{key:l(),name:i.name,content:i.content}));let j=T(f);for(let i of j)s.push(o.createElement("link",{key:l(),rel:i.rel,hrefLang:i.hreflang,href:i.href}));if(h)for(let i of h){let m={content:i.content};i.name&&(m.name=i.name),i.property&&(m.property=i.property),s.push(o.createElement("meta",{key:l(),...m}));}if(x)for(let i of x)s.push(o.createElement("link",{key:l(),...i}));if(d){let i=Array.isArray(d)?d:[d];for(let m of i)s.push(o.createElement("script",{key:l(),type:"application/ld+json",nonce:R,dangerouslySetInnerHTML:{__html:g(m)}}));}return o.createElement(o.Fragment,null,...s)}function X({data:t,nonce:e}){return o.createElement("script",{type:"application/ld+json",nonce:e,dangerouslySetInnerHTML:{__html:g(t)}})}export{g as a,M as b,y as c,u as d,v as e,I as f,_ as g,E as h,b as i,G as j,H as k,O as l,U as m,V as n,S as o,C as p,T as q,W as r,X as s};//# sourceMappingURL=chunk-YMCW2G4X.js.map
|
|
2
|
-
//# sourceMappingURL=chunk-YMCW2G4X.js.map
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"sources":["../src/utils/index.ts","../src/core/index.ts","../src/components/SEOHead.tsx","../src/components/JsonLd.tsx"],"names":["safeJsonLdSerialize","data","json","_key","value","omitEmpty","obj","result","key","val","deepMerge","base","override","overrideObj","baseObj","overrideVal","baseVal","normalizeUrl","url","trimmed","buildFullUrl","path","normalizedBase","normalizedPath","createSEOConfig","config","normalizeSEOConfig","mergeSEOConfig","merged","normalized","buildTitle","title","template","buildDescription","description","maxLength","buildCanonicalUrl","baseUrl","buildRobotsDirectives","directives","noIndexNoFollow","noIndex","buildOpenGraph","tags","simple","property","image","buildTwitterMetadata","buildAlternateLinks","alternates","alt","SEOHead","titleTemplate","canonical","robots","openGraph","twitter","additionalMetaTags","additionalLinkTags","jsonLd","nonce","elements","k","resolvedTitle","React","robotsContent","ogTags","tag","twitterTags","altLinks","link","meta","props","schemas","schema","JsonLd"],"mappings":"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","file":"chunk-YMCW2G4X.js","sourcesContent":["/**\n * Safely serialize a value to JSON for use in a <script> tag.\n * Escapes characters that could break out of script context.\n */\nexport function safeJsonLdSerialize(data: unknown): string {\n const json = JSON.stringify(data, (_key, value) => {\n if (value === undefined || value === null) return undefined;\n return value;\n });\n\n if (!json) return \"{}\";\n\n return json\n .replace(/</g, \"\\\\u003c\")\n .replace(/>/g, \"\\\\u003e\")\n .replace(/&/g, \"\\\\u0026\")\n .replace(/\\u2028/g, \"\\\\u2028\")\n .replace(/\\u2029/g, \"\\\\u2029\");\n}\n\n/**\n * Strip undefined/null values from an object (shallow).\n */\nexport function omitEmpty<T extends Record<string, unknown>>(\n obj: T\n): Partial<T> {\n const result: Partial<T> = {};\n for (const key of Object.keys(obj) as Array<keyof T>) {\n const val = obj[key];\n if (val !== undefined && val !== null && val !== \"\") {\n result[key] = val;\n }\n }\n return result;\n}\n\n/**\n * Deep-merge two objects. Arrays are replaced, not concatenated.\n */\nexport function deepMerge<T>(\n base: T,\n override: Partial<T>\n): T {\n const result = { ...base } as Record<string, unknown>;\n const overrideObj = override as Record<string, unknown>;\n const baseObj = base as Record<string, unknown>;\n\n for (const key of Object.keys(overrideObj)) {\n const overrideVal = overrideObj[key];\n const baseVal = baseObj[key];\n\n if (overrideVal === undefined) continue;\n\n if (\n overrideVal !== null &&\n typeof overrideVal === \"object\" &&\n !Array.isArray(overrideVal) &&\n baseVal !== null &&\n typeof baseVal === \"object\" &&\n !Array.isArray(baseVal)\n ) {\n result[key] = deepMerge(\n baseVal as Record<string, unknown>,\n overrideVal as Record<string, unknown>\n );\n } else {\n result[key] = overrideVal;\n }\n }\n\n return result as T;\n}\n\n/**\n * Normalize a URL by trimming whitespace and removing trailing slashes\n * (except for root \"/\").\n */\nexport function normalizeUrl(url: string): string {\n const trimmed = url.trim();\n if (trimmed === \"/\" || trimmed === \"\") return trimmed;\n return trimmed.replace(/\\/+$/, \"\");\n}\n\n/**\n * Build a full canonical URL from a base and a path.\n */\nexport function buildFullUrl(base: string, path?: string): string {\n const normalizedBase = normalizeUrl(base);\n if (!path || path === \"/\") return normalizedBase;\n const normalizedPath = path.startsWith(\"/\") ? path : `/${path}`;\n return normalizedBase + normalizeUrl(normalizedPath);\n}\n","import type {\n SEOConfig,\n OpenGraphConfig,\n TwitterConfig,\n RobotsConfig,\n AlternateLink,\n} from \"../types/index.js\";\nimport { deepMerge, normalizeUrl } from \"../utils/index.js\";\n\n// ─── Config builder ───────────────────────────────────────────\n\n/**\n * Create an SEO config with sensible defaults.\n */\nexport function createSEOConfig(config: SEOConfig = {}): SEOConfig {\n return normalizeSEOConfig(config);\n}\n\n/**\n * Deep-merge a base SEO config with page-level overrides.\n * Useful for combining a global site config with per-page config.\n */\nexport function mergeSEOConfig(\n base: SEOConfig,\n override: SEOConfig\n): SEOConfig {\n const merged = deepMerge(base, override);\n\n // Arrays should be replaced, not deep-merged\n if (override.alternates !== undefined) {\n merged.alternates = override.alternates;\n }\n if (override.additionalMetaTags !== undefined) {\n merged.additionalMetaTags = override.additionalMetaTags;\n }\n if (override.additionalLinkTags !== undefined) {\n merged.additionalLinkTags = override.additionalLinkTags;\n }\n if (override.jsonLd !== undefined) {\n merged.jsonLd = override.jsonLd;\n }\n\n return normalizeSEOConfig(merged);\n}\n\n/**\n * Normalize an SEO config: trim strings, normalize URLs, remove empties.\n */\nexport function normalizeSEOConfig(config: SEOConfig): SEOConfig {\n const normalized: SEOConfig = { ...config };\n\n if (normalized.title) {\n normalized.title = normalized.title.trim();\n }\n if (normalized.description) {\n normalized.description = normalized.description.trim();\n }\n if (normalized.canonical) {\n normalized.canonical = normalizeUrl(normalized.canonical);\n }\n\n return normalized;\n}\n\n// ─── Title ────────────────────────────────────────────────────\n\n/**\n * Build a title string, optionally applying a template.\n * Template uses `%s` as the placeholder for the page title.\n *\n * @example\n * buildTitle(\"About\", \"%s | MySite\") // \"About | MySite\"\n * buildTitle(\"Home\") // \"Home\"\n */\nexport function buildTitle(title?: string, template?: string): string {\n if (!title) return \"\";\n const trimmed = title.trim();\n if (!template) return trimmed;\n return template.replace(/%s/g, trimmed);\n}\n\n/**\n * Build a description, truncating at a max length if specified.\n */\nexport function buildDescription(\n description?: string,\n maxLength?: number\n): string {\n if (!description) return \"\";\n const trimmed = description.trim();\n if (!maxLength || trimmed.length <= maxLength) return trimmed;\n return trimmed.slice(0, maxLength).trimEnd() + \"…\";\n}\n\n// ─── Canonical URL ────────────────────────────────────────────\n\n/**\n * Build a canonical URL from a base URL and optional path.\n */\nexport function buildCanonicalUrl(\n baseUrl: string,\n path?: string\n): string {\n const normalizedBase = normalizeUrl(baseUrl);\n if (!path || path === \"/\") return normalizedBase;\n const normalizedPath = path.startsWith(\"/\") ? path : `/${path}`;\n return normalizedBase + normalizeUrl(normalizedPath);\n}\n\n// ─── Robots ───────────────────────────────────────────────────\n\n/**\n * Build a robots meta content string from a config.\n *\n * @example\n * buildRobotsDirectives({ index: true, follow: true }) // \"index, follow\"\n * buildRobotsDirectives({ index: false, follow: false, noarchive: true })\n * // \"noindex, nofollow, noarchive\"\n */\nexport function buildRobotsDirectives(config?: RobotsConfig): string {\n if (!config) return \"\";\n\n const directives: string[] = [];\n\n if (config.index === false) directives.push(\"noindex\");\n else if (config.index === true) directives.push(\"index\");\n\n if (config.follow === false) directives.push(\"nofollow\");\n else if (config.follow === true) directives.push(\"follow\");\n\n if (config.noarchive) directives.push(\"noarchive\");\n if (config.nosnippet) directives.push(\"nosnippet\");\n if (config.noimageindex) directives.push(\"noimageindex\");\n if (config.notranslate) directives.push(\"notranslate\");\n\n if (config.maxSnippet !== undefined)\n directives.push(`max-snippet:${config.maxSnippet}`);\n if (config.maxImagePreview)\n directives.push(`max-image-preview:${config.maxImagePreview}`);\n if (config.maxVideoPreview !== undefined)\n directives.push(`max-video-preview:${config.maxVideoPreview}`);\n\n return directives.join(\", \");\n}\n\n// ─── Convenience helpers ──────────────────────────────────────\n\n/**\n * Create a noindex/nofollow robots config.\n */\nexport function noIndexNoFollow(): RobotsConfig {\n return { index: false, follow: false };\n}\n\n/**\n * Create a noindex robots config that still allows following links.\n */\nexport function noIndex(): RobotsConfig {\n return { index: false, follow: true };\n}\n\n// ─── Open Graph ───────────────────────────────────────────────\n\n/**\n * Build Open Graph meta tag entries from config.\n * Returns an array of { property, content } pairs.\n */\nexport function buildOpenGraph(\n config?: OpenGraphConfig\n): Array<{ property: string; content: string }> {\n if (!config) return [];\n\n const tags: Array<{ property: string; content: string }> = [];\n\n const simple: Array<[keyof OpenGraphConfig, string]> = [\n [\"title\", \"og:title\"],\n [\"description\", \"og:description\"],\n [\"url\", \"og:url\"],\n [\"siteName\", \"og:site_name\"],\n [\"type\", \"og:type\"],\n [\"locale\", \"og:locale\"],\n ];\n\n for (const [key, property] of simple) {\n const value = config[key];\n if (typeof value === \"string\" && value.trim()) {\n tags.push({ property, content: value.trim() });\n }\n }\n\n if (config.images) {\n for (const image of config.images) {\n if (!image.url) continue;\n tags.push({ property: \"og:image\", content: image.url });\n if (image.alt) tags.push({ property: \"og:image:alt\", content: image.alt });\n if (image.width)\n tags.push({\n property: \"og:image:width\",\n content: String(image.width),\n });\n if (image.height)\n tags.push({\n property: \"og:image:height\",\n content: String(image.height),\n });\n if (image.type)\n tags.push({ property: \"og:image:type\", content: image.type });\n }\n }\n\n return tags;\n}\n\n// ─── Twitter ──────────────────────────────────────────────────\n\n/**\n * Build Twitter card meta tag entries from config.\n * Returns an array of { name, content } pairs.\n */\nexport function buildTwitterMetadata(\n config?: TwitterConfig\n): Array<{ name: string; content: string }> {\n if (!config) return [];\n\n const tags: Array<{ name: string; content: string }> = [];\n\n if (config.card) tags.push({ name: \"twitter:card\", content: config.card });\n if (config.site) tags.push({ name: \"twitter:site\", content: config.site });\n if (config.creator)\n tags.push({ name: \"twitter:creator\", content: config.creator });\n if (config.title)\n tags.push({ name: \"twitter:title\", content: config.title });\n if (config.description)\n tags.push({ name: \"twitter:description\", content: config.description });\n if (config.image)\n tags.push({ name: \"twitter:image\", content: config.image });\n if (config.imageAlt)\n tags.push({ name: \"twitter:image:alt\", content: config.imageAlt });\n\n return tags;\n}\n\n// ─── Hreflang ─────────────────────────────────────────────────\n\n/**\n * Build alternate link entries for hreflang from an array of alternates.\n * Returns an array of { rel, hreflang, href } suitable for <link> tags.\n */\nexport function buildAlternateLinks(\n alternates?: AlternateLink[]\n): Array<{ rel: string; hreflang: string; href: string }> {\n if (!alternates || alternates.length === 0) return [];\n return alternates.map((alt) => ({\n rel: \"alternate\",\n hreflang: alt.hreflang,\n href: normalizeUrl(alt.href),\n }));\n}\n","import React from \"react\";\nimport type { SEOConfig } from \"../types/index.js\";\nimport {\n buildTitle,\n buildRobotsDirectives,\n buildOpenGraph,\n buildTwitterMetadata,\n buildAlternateLinks,\n} from \"../core/index.js\";\nimport { safeJsonLdSerialize } from \"../utils/index.js\";\n\nexport interface SEOHeadProps extends SEOConfig {\n /**\n * Optional nonce for CSP-compatible script tags.\n */\n nonce?: string;\n}\n\n/**\n * Generic SSR-safe React component that renders SEO-related tags.\n *\n * Renders: title, meta description, canonical link, robots meta,\n * Open Graph tags, Twitter tags, alternate/hreflang links,\n * additional meta/link tags, and JSON-LD scripts.\n *\n * Designed to be placed inside a <head> element in SSR apps.\n * Does NOT use any browser globals — fully SSR-compatible.\n */\nexport function SEOHead({\n title,\n titleTemplate,\n description,\n canonical,\n robots,\n openGraph,\n twitter,\n alternates,\n additionalMetaTags,\n additionalLinkTags,\n jsonLd,\n nonce,\n}: SEOHeadProps): React.ReactElement {\n const elements: React.ReactElement[] = [];\n let key = 0;\n const k = () => `seo-${key++}`;\n\n // Title\n const resolvedTitle = buildTitle(title, titleTemplate);\n if (resolvedTitle) {\n elements.push(React.createElement(\"title\", { key: k() }, resolvedTitle));\n }\n\n // Description\n if (description?.trim()) {\n elements.push(\n React.createElement(\"meta\", {\n key: k(),\n name: \"description\",\n content: description.trim(),\n })\n );\n }\n\n // Canonical\n if (canonical?.trim()) {\n elements.push(\n React.createElement(\"link\", {\n key: k(),\n rel: \"canonical\",\n href: canonical.trim(),\n })\n );\n }\n\n // Robots\n const robotsContent = buildRobotsDirectives(robots);\n if (robotsContent) {\n elements.push(\n React.createElement(\"meta\", {\n key: k(),\n name: \"robots\",\n content: robotsContent,\n })\n );\n }\n\n // Open Graph\n const ogTags = buildOpenGraph(openGraph);\n for (const tag of ogTags) {\n elements.push(\n React.createElement(\"meta\", {\n key: k(),\n property: tag.property,\n content: tag.content,\n })\n );\n }\n\n // Twitter\n const twitterTags = buildTwitterMetadata(twitter);\n for (const tag of twitterTags) {\n elements.push(\n React.createElement(\"meta\", {\n key: k(),\n name: tag.name,\n content: tag.content,\n })\n );\n }\n\n // Hreflang alternates\n const altLinks = buildAlternateLinks(alternates);\n for (const link of altLinks) {\n elements.push(\n React.createElement(\"link\", {\n key: k(),\n rel: link.rel,\n hrefLang: link.hreflang,\n href: link.href,\n })\n );\n }\n\n // Additional meta tags\n if (additionalMetaTags) {\n for (const meta of additionalMetaTags) {\n const props: Record<string, string> = { content: meta.content };\n if (meta.name) props.name = meta.name;\n if (meta.property) props.property = meta.property;\n elements.push(React.createElement(\"meta\", { key: k(), ...props }));\n }\n }\n\n // Additional link tags\n if (additionalLinkTags) {\n for (const link of additionalLinkTags) {\n elements.push(React.createElement(\"link\", { key: k(), ...link }));\n }\n }\n\n // JSON-LD\n if (jsonLd) {\n const schemas = Array.isArray(jsonLd) ? jsonLd : [jsonLd];\n for (const schema of schemas) {\n elements.push(\n React.createElement(\"script\", {\n key: k(),\n type: \"application/ld+json\",\n nonce,\n dangerouslySetInnerHTML: {\n __html: safeJsonLdSerialize(schema),\n },\n })\n );\n }\n }\n\n return React.createElement(React.Fragment, null, ...elements);\n}\n","import React from \"react\";\nimport { safeJsonLdSerialize } from \"../utils/index.js\";\n\nexport interface JsonLdProps {\n data: Record<string, unknown> | Array<Record<string, unknown>>;\n nonce?: string;\n}\n\n/**\n * Renders a <script type=\"application/ld+json\"> tag with safely serialized JSON-LD.\n * SSR-safe: no browser globals used.\n */\nexport function JsonLd({ data, nonce }: JsonLdProps): React.ReactElement {\n return React.createElement(\"script\", {\n type: \"application/ld+json\",\n nonce,\n dangerouslySetInnerHTML: {\n __html: safeJsonLdSerialize(data),\n },\n });\n}\n"]}
|