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 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** | `generateMetadata()` + `safeJsonLdSerialize()` |
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 SSR** | `<SEOHead>` in root component |
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
- buildTitle, buildDescription, buildCanonicalUrl,
595
- createArticleSchema, safeJsonLdSerialize,
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: buildTitle(post.title, "%s | My Blog"),
603
- description: buildDescription(post.excerpt, 160),
604
- alternates: {
605
- canonical: buildCanonicalUrl("https://myblog.com", `/blog/${params.slug}`),
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 SSR
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 — page just provides SEO data + content
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":"qCA4FO,SAASA,EAAeC,CAAAA,CAAmC,CAChE,IAAMC,CAAAA,CAAuB,GAGzBD,CAAAA,CAAO,KAAA,GACLA,CAAAA,CAAO,aAAA,CACTC,EAAK,KAAA,CAAQ,CACX,QAASD,CAAAA,CAAO,KAAA,CAChB,SAAUA,CAAAA,CAAO,aACnB,CAAA,CAEAC,CAAAA,CAAK,MAAQD,CAAAA,CAAO,KAAA,CAAA,CAKpBA,EAAO,WAAA,EAAa,IAAA,KACtBC,CAAAA,CAAK,WAAA,CAAcD,EAAO,WAAA,CAAY,IAAA,IAIxC,IAAME,CAAAA,CAAe,CAAC,CAACF,CAAAA,CAAO,WAAW,IAAA,EAAK,CACxCG,CAAAA,CAAgB,CAAC,CAACH,CAAAA,CAAO,UAAA,EAAY,OAE3C,GAAA,CAAIE,CAAAA,EAAgBC,KAClBF,CAAAA,CAAK,UAAA,CAAa,EAAC,CAEfC,CAAAA,GACFD,EAAK,UAAA,CAAW,SAAA,CAAYD,EAAO,SAAA,CAAW,IAAA,IAG5CG,CAAAA,CAAAA,CAAe,CACjB,IAAMC,CAAAA,CAAoC,EAAC,CAC3C,IAAA,IAAWC,KAAOL,CAAAA,CAAO,UAAA,CACvBI,EAAUC,CAAAA,CAAI,QAAQ,EAAIA,CAAAA,CAAI,IAAA,CAEhCJ,EAAK,UAAA,CAAW,SAAA,CAAYG,EAC9B,CAIF,GAAIJ,EAAO,MAAA,CAAQ,CACjB,IAAMM,CAAAA,CAAIN,EAAO,MAAA,CACXO,CAAAA,CAA+B,EAAC,CAElCD,CAAAA,CAAE,QAAU,MAAA,GAAWC,CAAAA,CAAO,KAAA,CAAQD,CAAAA,CAAE,OACxCA,CAAAA,CAAE,MAAA,GAAW,SAAWC,CAAAA,CAAO,MAAA,CAASD,EAAE,MAAA,CAAA,CAC1CA,CAAAA,CAAE,SAAA,GAAWC,CAAAA,CAAO,UAAYD,CAAAA,CAAE,SAAA,CAAA,CAClCA,EAAE,SAAA,GAAWC,CAAAA,CAAO,UAAYD,CAAAA,CAAE,SAAA,CAAA,CAClCA,EAAE,YAAA,GAAcC,CAAAA,CAAO,aAAeD,CAAAA,CAAE,YAAA,CAAA,CACxCA,EAAE,WAAA,GAAaC,CAAAA,CAAO,YAAcD,CAAAA,CAAE,WAAA,CAAA,CACtCA,CAAAA,CAAE,UAAA,GAAe,SAAWC,CAAAA,CAAO,aAAa,EAAID,CAAAA,CAAE,UAAA,CAAA,CACtDA,EAAE,eAAA,GAAiBC,CAAAA,CAAO,mBAAmB,CAAA,CAAID,CAAAA,CAAE,iBACnDA,CAAAA,CAAE,eAAA,GAAoB,SACxBC,CAAAA,CAAO,mBAAmB,EAAID,CAAAA,CAAE,eAAA,CAAA,CAElCL,CAAAA,CAAK,MAAA,CAASM,EAChB,CAGA,GAAIP,EAAO,SAAA,CAAW,CACpB,IAAMQ,CAAAA,CAAKR,CAAAA,CAAO,UACZS,CAAAA,CAAqC,GAEvCD,CAAAA,CAAG,KAAA,GAAOC,EAAU,KAAA,CAAQD,CAAAA,CAAG,OAC/BA,CAAAA,CAAG,WAAA,GAAaC,CAAAA,CAAU,WAAA,CAAcD,EAAG,WAAA,CAAA,CAC3CA,CAAAA,CAAG,MAAKC,CAAAA,CAAU,GAAA,CAAMD,EAAG,GAAA,CAAA,CAC3BA,CAAAA,CAAG,QAAA,GAAUC,CAAAA,CAAU,SAAWD,CAAAA,CAAG,QAAA,CAAA,CACrCA,EAAG,IAAA,GAAMC,CAAAA,CAAU,KAAOD,CAAAA,CAAG,IAAA,CAAA,CAC7BA,CAAAA,CAAG,MAAA,GAAQC,EAAU,MAAA,CAASD,CAAAA,CAAG,QAEjCA,CAAAA,CAAG,MAAA,EAAQ,SACbC,CAAAA,CAAU,MAAA,CAASD,EAAG,MAAA,CAAO,GAAA,CAAKE,GAAwB,CACxD,IAAMC,EAA6B,CAAE,GAAA,CAAKD,EAAI,GAAI,CAAA,CAClD,OAAIA,CAAAA,CAAI,MAAKC,CAAAA,CAAM,GAAA,CAAMD,EAAI,GAAA,CAAA,CACzBA,CAAAA,CAAI,QAAOC,CAAAA,CAAM,KAAA,CAAQD,EAAI,KAAA,CAAA,CAC7BA,CAAAA,CAAI,SAAQC,CAAAA,CAAM,MAAA,CAASD,EAAI,MAAA,CAAA,CAC/BA,CAAAA,CAAI,OAAMC,CAAAA,CAAM,IAAA,CAAOD,CAAAA,CAAI,IAAA,CAAA,CACxBC,CACT,CAAC,CAAA,CAAA,CAGHV,EAAK,SAAA,CAAYQ,EACnB,CAGA,GAAIT,CAAAA,CAAO,QAAS,CAClB,IAAMY,EAAKZ,CAAAA,CAAO,OAAA,CACZa,EAAiC,EAAC,CAEpCD,EAAG,IAAA,GAAMC,CAAAA,CAAQ,IAAA,CAAOD,CAAAA,CAAG,MAC3BA,CAAAA,CAAG,IAAA,GAAMC,EAAQ,IAAA,CAAOD,CAAAA,CAAG,MAC3BA,CAAAA,CAAG,OAAA,GAASC,CAAAA,CAAQ,OAAA,CAAUD,EAAG,OAAA,CAAA,CACjCA,CAAAA,CAAG,QAAOC,CAAAA,CAAQ,KAAA,CAAQD,EAAG,KAAA,CAAA,CAC7BA,CAAAA,CAAG,WAAA,GAAaC,CAAAA,CAAQ,YAAcD,CAAAA,CAAG,WAAA,CAAA,CACzCA,EAAG,KAAA,GAAOC,CAAAA,CAAQ,OAAS,CAACD,CAAAA,CAAG,KAAK,CAAA,CAAA,CAExCX,CAAAA,CAAK,QAAUY,EACjB,CAIA,GAAIb,CAAAA,CAAO,kBAAA,EAAoB,OAAQ,CACrC,IAAMc,CAAAA,CAAgC,GACtC,IAAA,IAAWC,CAAAA,IAAOf,EAAO,kBAAA,CAAoB,CAC3C,IAAMgB,CAAAA,CAAMD,CAAAA,CAAI,MAAQA,CAAAA,CAAI,QAAA,CACxBC,IAAKF,CAAAA,CAAME,CAAG,EAAID,CAAAA,CAAI,OAAA,EAC5B,CACI,MAAA,CAAO,IAAA,CAAKD,CAAK,CAAA,CAAE,OAAS,CAAA,GAAGb,CAAAA,CAAK,MAAQa,CAAAA,EAClD,CAEA,OAAOb,CACT,CAMO,SAASgB,CAAAA,CAAejB,CAAAA,CAA2B,CACxD,OAAOkB,CAAAA,CAAWlB,EAAO,KAAA,CAAOA,CAAAA,CAAO,aAAa,CACtD","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":"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","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"]}
@@ -1,2 +1,2 @@
1
- 'use strict';var chunkQBHCTDUJ_cjs=require('./chunk-QBHCTDUJ.cjs');Object.defineProperty(exports,"JsonLd",{enumerable:true,get:function(){return chunkQBHCTDUJ_cjs.s}});Object.defineProperty(exports,"SEOHead",{enumerable:true,get:function(){return chunkQBHCTDUJ_cjs.r}});//# sourceMappingURL=components.cjs.map
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
@@ -1,2 +1,2 @@
1
- export{s as JsonLd,r as SEOHead}from'./chunk-YMCW2G4X.js';//# sourceMappingURL=components.js.map
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'),chunkQBHCTDUJ_cjs=require('./chunk-QBHCTDUJ.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 chunkQBHCTDUJ_cjs.s}});Object.defineProperty(exports,"SEOHead",{enumerable:true,get:function(){return chunkQBHCTDUJ_cjs.r}});Object.defineProperty(exports,"buildAlternateLinks",{enumerable:true,get:function(){return chunkQBHCTDUJ_cjs.q}});Object.defineProperty(exports,"buildCanonicalUrl",{enumerable:true,get:function(){return chunkQBHCTDUJ_cjs.k}});Object.defineProperty(exports,"buildDescription",{enumerable:true,get:function(){return chunkQBHCTDUJ_cjs.j}});Object.defineProperty(exports,"buildFullUrl",{enumerable:true,get:function(){return chunkQBHCTDUJ_cjs.e}});Object.defineProperty(exports,"buildOpenGraph",{enumerable:true,get:function(){return chunkQBHCTDUJ_cjs.o}});Object.defineProperty(exports,"buildRobotsDirectives",{enumerable:true,get:function(){return chunkQBHCTDUJ_cjs.l}});Object.defineProperty(exports,"buildTitle",{enumerable:true,get:function(){return chunkQBHCTDUJ_cjs.i}});Object.defineProperty(exports,"buildTwitterMetadata",{enumerable:true,get:function(){return chunkQBHCTDUJ_cjs.p}});Object.defineProperty(exports,"createSEOConfig",{enumerable:true,get:function(){return chunkQBHCTDUJ_cjs.f}});Object.defineProperty(exports,"deepMerge",{enumerable:true,get:function(){return chunkQBHCTDUJ_cjs.c}});Object.defineProperty(exports,"mergeSEOConfig",{enumerable:true,get:function(){return chunkQBHCTDUJ_cjs.g}});Object.defineProperty(exports,"noIndex",{enumerable:true,get:function(){return chunkQBHCTDUJ_cjs.n}});Object.defineProperty(exports,"noIndexNoFollow",{enumerable:true,get:function(){return chunkQBHCTDUJ_cjs.m}});Object.defineProperty(exports,"normalizeSEOConfig",{enumerable:true,get:function(){return chunkQBHCTDUJ_cjs.h}});Object.defineProperty(exports,"normalizeUrl",{enumerable:true,get:function(){return chunkQBHCTDUJ_cjs.d}});Object.defineProperty(exports,"omitEmpty",{enumerable:true,get:function(){return chunkQBHCTDUJ_cjs.b}});Object.defineProperty(exports,"safeJsonLdSerialize",{enumerable:true,get:function(){return chunkQBHCTDUJ_cjs.a}});//# sourceMappingURL=index.cjs.map
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{s as JsonLd,r as SEOHead,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-YMCW2G4X.js';//# sourceMappingURL=index.js.map
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.5",
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": [
@@ -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"]}
@@ -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"]}