@power-seo/react
Advanced tools
+123
-55
@@ -1,14 +0,54 @@ | ||
| 'use strict'; | ||
| "use strict"; | ||
| var __defProp = Object.defineProperty; | ||
| var __getOwnPropDesc = Object.getOwnPropertyDescriptor; | ||
| var __getOwnPropNames = Object.getOwnPropertyNames; | ||
| var __hasOwnProp = Object.prototype.hasOwnProperty; | ||
| var __export = (target, all) => { | ||
| for (var name in all) | ||
| __defProp(target, name, { get: all[name], enumerable: true }); | ||
| }; | ||
| var __copyProps = (to, from, except, desc) => { | ||
| if (from && typeof from === "object" || typeof from === "function") { | ||
| for (let key of __getOwnPropNames(from)) | ||
| if (!__hasOwnProp.call(to, key) && key !== except) | ||
| __defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable }); | ||
| } | ||
| return to; | ||
| }; | ||
| var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod); | ||
| var react = require('react'); | ||
| var core = require('@power-seo/core'); | ||
| // src/index.ts | ||
| var index_exports = {}; | ||
| __export(index_exports, { | ||
| Breadcrumb: () => Breadcrumb, | ||
| Canonical: () => Canonical, | ||
| DefaultSEO: () => DefaultSEO, | ||
| Hreflang: () => Hreflang, | ||
| OpenGraph: () => OpenGraph, | ||
| Robots: () => Robots, | ||
| SEO: () => SEO, | ||
| SEOContext: () => SEOContext, | ||
| TwitterCard: () => TwitterCard, | ||
| renderLinkTags: () => renderLinkTags, | ||
| renderMetaTags: () => renderMetaTags, | ||
| useDefaultSEO: () => useDefaultSEO | ||
| }); | ||
| module.exports = __toCommonJS(index_exports); | ||
| // src/context.ts | ||
| var SEOContext = react.createContext(null); | ||
| var import_react = require("react"); | ||
| var SEOContext = (0, import_react.createContext)(null); | ||
| function useDefaultSEO() { | ||
| return react.useContext(SEOContext); | ||
| return (0, import_react.useContext)(SEOContext); | ||
| } | ||
| // src/components/DefaultSEO.ts | ||
| var import_react3 = require("react"); | ||
| var import_core = require("@power-seo/core"); | ||
| // src/head-tags.ts | ||
| var import_react2 = require("react"); | ||
| function renderMetaTags(tags) { | ||
| return react.createElement( | ||
| react.Fragment, | ||
| return (0, import_react2.createElement)( | ||
| import_react2.Fragment, | ||
| null, | ||
@@ -21,3 +61,3 @@ ...tags.map((tag, i) => { | ||
| props.key = `meta-${tag.name ?? tag.property ?? tag.httpEquiv ?? i}`; | ||
| return react.createElement("meta", props); | ||
| return (0, import_react2.createElement)("meta", props); | ||
| }) | ||
@@ -27,4 +67,4 @@ ); | ||
| function renderLinkTags(tags) { | ||
| return react.createElement( | ||
| react.Fragment, | ||
| return (0, import_react2.createElement)( | ||
| import_react2.Fragment, | ||
| null, | ||
@@ -43,3 +83,3 @@ ...tags.map((tag, i) => { | ||
| if (tag.crossOrigin) props.crossOrigin = tag.crossOrigin; | ||
| return react.createElement("link", props); | ||
| return (0, import_react2.createElement)("link", props); | ||
| }) | ||
@@ -51,9 +91,9 @@ ); | ||
| function DefaultSEO({ children, ...config }) { | ||
| const title = core.resolveTitle(config); | ||
| const metaTags = core.buildMetaTags(config); | ||
| const linkTags = core.buildLinkTags(config); | ||
| return react.createElement( | ||
| const title = (0, import_core.resolveTitle)(config); | ||
| const metaTags = (0, import_core.buildMetaTags)(config); | ||
| const linkTags = (0, import_core.buildLinkTags)(config); | ||
| return (0, import_react3.createElement)( | ||
| SEOContext.Provider, | ||
| { value: config }, | ||
| title ? react.createElement("title", null, title) : null, | ||
| title ? (0, import_react3.createElement)("title", null, title) : null, | ||
| renderMetaTags(metaTags), | ||
@@ -64,2 +104,6 @@ renderLinkTags(linkTags), | ||
| } | ||
| // src/components/SEO.ts | ||
| var import_react4 = require("react"); | ||
| var import_core2 = require("@power-seo/core"); | ||
| function SEO(props) { | ||
@@ -93,9 +137,9 @@ const defaults = useDefaultSEO(); | ||
| }; | ||
| const title = core.resolveTitle(config); | ||
| const metaTags = core.buildMetaTags(config); | ||
| const linkTags = core.buildLinkTags(config); | ||
| return react.createElement( | ||
| react.Fragment, | ||
| const title = (0, import_core2.resolveTitle)(config); | ||
| const metaTags = (0, import_core2.buildMetaTags)(config); | ||
| const linkTags = (0, import_core2.buildLinkTags)(config); | ||
| return (0, import_react4.createElement)( | ||
| import_react4.Fragment, | ||
| null, | ||
| title ? react.createElement("title", null, title) : null, | ||
| title ? (0, import_react4.createElement)("title", null, title) : null, | ||
| renderMetaTags(metaTags), | ||
@@ -105,16 +149,29 @@ renderLinkTags(linkTags) | ||
| } | ||
| // src/components/OpenGraph.ts | ||
| var import_react5 = require("react"); | ||
| var import_core3 = require("@power-seo/core"); | ||
| function OpenGraph(props) { | ||
| const tags = core.buildOpenGraphTags(props); | ||
| return react.createElement(react.Fragment, null, renderMetaTags(tags)); | ||
| const tags = (0, import_core3.buildOpenGraphTags)(props); | ||
| return (0, import_react5.createElement)(import_react5.Fragment, null, renderMetaTags(tags)); | ||
| } | ||
| // src/components/TwitterCard.ts | ||
| var import_react6 = require("react"); | ||
| var import_core4 = require("@power-seo/core"); | ||
| function TwitterCard(props) { | ||
| const tags = core.buildTwitterTags(props); | ||
| return react.createElement(react.Fragment, null, renderMetaTags(tags)); | ||
| const tags = (0, import_core4.buildTwitterTags)(props); | ||
| return (0, import_react6.createElement)(import_react6.Fragment, null, renderMetaTags(tags)); | ||
| } | ||
| // src/components/Canonical.ts | ||
| var import_react7 = require("react"); | ||
| var import_core5 = require("@power-seo/core"); | ||
| function Canonical({ url, baseUrl, trailingSlash = false }) { | ||
| let canonical = baseUrl ? core.resolveCanonical(baseUrl, url) : url; | ||
| let canonical = baseUrl ? (0, import_core5.resolveCanonical)(baseUrl, url) : url; | ||
| if (trailingSlash && !canonical.endsWith("/")) { | ||
| canonical += "/"; | ||
| } else if (!trailingSlash && canonical.endsWith("/") && canonical !== url.replace(/\/$/, "") + "/") ; | ||
| return react.createElement("link", { | ||
| } else if (!trailingSlash && canonical.endsWith("/") && canonical !== url.replace(/\/$/, "") + "/") { | ||
| } | ||
| return (0, import_react7.createElement)("link", { | ||
| rel: "canonical", | ||
@@ -124,10 +181,17 @@ href: canonical | ||
| } | ||
| // src/components/Robots.ts | ||
| var import_react8 = require("react"); | ||
| var import_core6 = require("@power-seo/core"); | ||
| function Robots(props) { | ||
| const content = core.buildRobotsContent(props); | ||
| const content = (0, import_core6.buildRobotsContent)(props); | ||
| if (!content) return null; | ||
| return react.createElement("meta", { name: "robots", content }); | ||
| return (0, import_react8.createElement)("meta", { name: "robots", content }); | ||
| } | ||
| // src/components/Hreflang.ts | ||
| var import_react9 = require("react"); | ||
| function Hreflang({ alternates, xDefault }) { | ||
| const links = alternates.map( | ||
| (alt) => react.createElement("link", { | ||
| (alt) => (0, import_react9.createElement)("link", { | ||
| key: `hreflang-${alt.hrefLang}`, | ||
@@ -141,3 +205,3 @@ rel: "alternate", | ||
| links.push( | ||
| react.createElement("link", { | ||
| (0, import_react9.createElement)("link", { | ||
| key: "hreflang-x-default", | ||
@@ -150,4 +214,7 @@ rel: "alternate", | ||
| } | ||
| return react.createElement(react.Fragment, null, ...links); | ||
| return (0, import_react9.createElement)(import_react9.Fragment, null, ...links); | ||
| } | ||
| // src/components/Breadcrumb.ts | ||
| var import_react10 = require("react"); | ||
| function Breadcrumb({ | ||
@@ -176,3 +243,3 @@ items, | ||
| breadcrumbItems.push( | ||
| react.createElement("span", { key: `sep-${index}`, "aria-hidden": "true" }, separator) | ||
| (0, import_react10.createElement)("span", { key: `sep-${index}`, "aria-hidden": "true" }, separator) | ||
| ); | ||
@@ -183,3 +250,3 @@ } | ||
| breadcrumbItems.push( | ||
| react.createElement( | ||
| (0, import_react10.createElement)( | ||
| "a", | ||
@@ -192,3 +259,3 @@ { key: `item-${index}`, href: item.url, className: linkClassName }, | ||
| breadcrumbItems.push( | ||
| react.createElement( | ||
| (0, import_react10.createElement)( | ||
| "span", | ||
@@ -206,6 +273,6 @@ { | ||
| children.push( | ||
| react.createElement( | ||
| (0, import_react10.createElement)( | ||
| "nav", | ||
| { "aria-label": "Breadcrumb", className }, | ||
| react.createElement( | ||
| (0, import_react10.createElement)( | ||
| "ol", | ||
@@ -227,3 +294,3 @@ { | ||
| children.push( | ||
| react.createElement("script", { | ||
| (0, import_react10.createElement)("script", { | ||
| key: "breadcrumb-jsonld", | ||
@@ -235,18 +302,19 @@ type: "application/ld+json", | ||
| } | ||
| return react.createElement(react.Fragment, null, ...children); | ||
| return (0, import_react10.createElement)(import_react10.Fragment, null, ...children); | ||
| } | ||
| exports.Breadcrumb = Breadcrumb; | ||
| exports.Canonical = Canonical; | ||
| exports.DefaultSEO = DefaultSEO; | ||
| exports.Hreflang = Hreflang; | ||
| exports.OpenGraph = OpenGraph; | ||
| exports.Robots = Robots; | ||
| exports.SEO = SEO; | ||
| exports.SEOContext = SEOContext; | ||
| exports.TwitterCard = TwitterCard; | ||
| exports.renderLinkTags = renderLinkTags; | ||
| exports.renderMetaTags = renderMetaTags; | ||
| exports.useDefaultSEO = useDefaultSEO; | ||
| //# sourceMappingURL=index.cjs.map | ||
| // Annotate the CommonJS export names for ESM import in node: | ||
| 0 && (module.exports = { | ||
| Breadcrumb, | ||
| Canonical, | ||
| DefaultSEO, | ||
| Hreflang, | ||
| OpenGraph, | ||
| Robots, | ||
| SEO, | ||
| SEOContext, | ||
| TwitterCard, | ||
| renderLinkTags, | ||
| renderMetaTags, | ||
| useDefaultSEO | ||
| }); | ||
| //# sourceMappingURL=index.cjs.map |
@@ -1,1 +0,1 @@ | ||
| {"version":3,"sources":["../src/context.ts","../src/head-tags.ts","../src/components/DefaultSEO.ts","../src/components/SEO.ts","../src/components/OpenGraph.ts","../src/components/TwitterCard.ts","../src/components/Canonical.ts","../src/components/Robots.ts","../src/components/Hreflang.ts","../src/components/Breadcrumb.ts"],"names":["createContext","useContext","createElement","Fragment","resolveTitle","buildMetaTags","buildLinkTags","buildOpenGraphTags","buildTwitterTags","resolveCanonical","buildRobotsContent"],"mappings":"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","file":"index.cjs","sourcesContent":["// ============================================================================\n// @power-seo/react — SEO Context for Default Configuration\n// ============================================================================\n\nimport { createContext, useContext } from 'react';\nimport type { SEOConfig } from '@power-seo/core';\n\nexport const SEOContext = createContext<SEOConfig | null>(null);\n\n/**\n * Hook to access the default SEO configuration from the nearest DefaultSEO provider.\n */\nexport function useDefaultSEO(): SEOConfig | null {\n return useContext(SEOContext);\n}\n","// ============================================================================\n// @power-seo/react — Head Tag Rendering (React 19 native + fallback)\n// ============================================================================\n\nimport { createElement, Fragment } from 'react';\nimport type { MetaTag, LinkTag } from '@power-seo/core';\n\n/**\n * Render meta tags as React elements.\n * In React 19, these automatically hoist to <head>.\n * In React 18, wrap with a Helmet provider or use a framework's Head component.\n */\nexport function renderMetaTags(tags: MetaTag[]) {\n return createElement(\n Fragment,\n null,\n ...tags.map((tag, i) => {\n const props: Record<string, string> = { content: tag.content };\n if (tag.name) props.name = tag.name;\n if (tag.property) props.property = tag.property;\n if (tag.httpEquiv) props.httpEquiv = tag.httpEquiv;\n props.key = `meta-${tag.name ?? tag.property ?? tag.httpEquiv ?? i}`;\n return createElement('meta', props);\n }),\n );\n}\n\n/**\n * Render link tags as React elements.\n */\nexport function renderLinkTags(tags: LinkTag[]) {\n return createElement(\n Fragment,\n null,\n ...tags.map((tag, i) => {\n const props: Record<string, string | undefined> = {\n rel: tag.rel,\n href: tag.href,\n key: `link-${tag.rel}-${tag.hreflang ?? i}`,\n };\n if (tag.hreflang) props.hrefLang = tag.hreflang;\n if (tag.type) props.type = tag.type;\n if (tag.sizes) props.sizes = tag.sizes;\n if (tag.media) props.media = tag.media;\n if (tag.as) props.as = tag.as;\n if (tag.crossOrigin) props.crossOrigin = tag.crossOrigin;\n return createElement('link', props);\n }),\n );\n}\n","// ============================================================================\n// @power-seo/react — <DefaultSEO> Component\n// ============================================================================\n\nimport { createElement } from 'react';\nimport type { ReactNode } from 'react';\nimport type { SEOConfig } from '@power-seo/core';\nimport { buildMetaTags, buildLinkTags, resolveTitle } from '@power-seo/core';\nimport { SEOContext } from '../context.js';\nimport { renderMetaTags, renderLinkTags } from '../head-tags.js';\n\nexport interface DefaultSEOProps extends SEOConfig {\n children?: ReactNode;\n}\n\n/**\n * Provide global default SEO configuration.\n * Renders default meta tags and wraps children with SEO context.\n *\n * @example\n * ```tsx\n * <DefaultSEO\n * titleTemplate=\"%s | My Site\"\n * defaultTitle=\"My Site\"\n * description=\"Default description\"\n * openGraph={{\n * type: 'website',\n * siteName: 'My Site',\n * }}\n * >\n * <App />\n * </DefaultSEO>\n * ```\n */\nexport function DefaultSEO({ children, ...config }: DefaultSEOProps) {\n const title = resolveTitle(config);\n const metaTags = buildMetaTags(config);\n const linkTags = buildLinkTags(config);\n\n return createElement(\n SEOContext.Provider,\n { value: config },\n title ? createElement('title', null, title) : null,\n renderMetaTags(metaTags),\n renderLinkTags(linkTags),\n children,\n );\n}\n","// ============================================================================\n// @power-seo/react — <SEO> Component\n// ============================================================================\n\nimport { createElement, Fragment } from 'react';\nimport type { SEOConfig } from '@power-seo/core';\nimport { buildMetaTags, buildLinkTags, resolveTitle } from '@power-seo/core';\nimport { useDefaultSEO } from '../context.js';\nimport { renderMetaTags, renderLinkTags } from '../head-tags.js';\n\nexport type SEOProps = SEOConfig;\n\n/**\n * All-in-one SEO component for per-page meta tag management.\n * Merges with DefaultSEO context if available.\n *\n * In React 19, <title>, <meta>, and <link> tags automatically hoist to <head>.\n * In React 18, use with a Helmet provider or framework Head component.\n *\n * @example\n * ```tsx\n * <SEO\n * title=\"About Us\"\n * description=\"Learn about our company\"\n * canonical=\"https://example.com/about\"\n * openGraph={{\n * title: 'About Us',\n * description: 'Learn about our company',\n * images: [{ url: 'https://example.com/about-og.jpg', width: 1200, height: 630 }],\n * }}\n * twitter={{\n * cardType: 'summary_large_image',\n * }}\n * />\n * ```\n */\nexport function SEO(props: SEOProps) {\n const defaults = useDefaultSEO();\n\n // Merge page config with defaults\n const config: SEOConfig = {\n ...defaults,\n ...props,\n // Deep merge for nested objects\n openGraph: {\n ...defaults?.openGraph,\n ...props.openGraph,\n images: props.openGraph?.images ?? defaults?.openGraph?.images,\n videos: props.openGraph?.videos ?? defaults?.openGraph?.videos,\n },\n twitter: {\n ...defaults?.twitter,\n ...props.twitter,\n },\n additionalMetaTags: [\n ...(defaults?.additionalMetaTags ?? []),\n ...(props.additionalMetaTags ?? []),\n ],\n additionalLinkTags: [\n ...(defaults?.additionalLinkTags ?? []),\n ...(props.additionalLinkTags ?? []),\n ],\n languageAlternates: props.languageAlternates ?? defaults?.languageAlternates,\n // Use page-specific title template or default\n titleTemplate: props.titleTemplate ?? defaults?.titleTemplate,\n };\n\n const title = resolveTitle(config);\n const metaTags = buildMetaTags(config);\n const linkTags = buildLinkTags(config);\n\n return createElement(\n Fragment,\n null,\n title ? createElement('title', null, title) : null,\n renderMetaTags(metaTags),\n renderLinkTags(linkTags),\n );\n}\n","// ============================================================================\n// @power-seo/react — <OpenGraph> Component\n// ============================================================================\n\nimport { createElement, Fragment } from 'react';\nimport type { OpenGraphConfig } from '@power-seo/core';\nimport { buildOpenGraphTags } from '@power-seo/core';\nimport { renderMetaTags } from '../head-tags.js';\n\nexport type OpenGraphProps = OpenGraphConfig;\n\n/**\n * Render Open Graph meta tags.\n *\n * @example\n * ```tsx\n * <OpenGraph\n * type=\"article\"\n * title=\"My Article\"\n * description=\"Article description\"\n * url=\"https://example.com/article\"\n * images={[{\n * url: 'https://example.com/og.jpg',\n * width: 1200,\n * height: 630,\n * alt: 'Article image',\n * }]}\n * article={{\n * publishedTime: '2025-01-01',\n * authors: ['https://example.com/author'],\n * tags: ['react', 'seo'],\n * }}\n * />\n * ```\n */\nexport function OpenGraph(props: OpenGraphProps) {\n const tags = buildOpenGraphTags(props);\n return createElement(Fragment, null, renderMetaTags(tags));\n}\n","// ============================================================================\n// @power-seo/react — <TwitterCard> Component\n// ============================================================================\n\nimport { createElement, Fragment } from 'react';\nimport type { TwitterCardConfig } from '@power-seo/core';\nimport { buildTwitterTags } from '@power-seo/core';\nimport { renderMetaTags } from '../head-tags.js';\n\nexport type TwitterCardProps = TwitterCardConfig;\n\n/**\n * Render Twitter Card meta tags.\n *\n * @example\n * ```tsx\n * <TwitterCard\n * cardType=\"summary_large_image\"\n * site=\"@mysite\"\n * creator=\"@author\"\n * title=\"My Article\"\n * description=\"Article description\"\n * image=\"https://example.com/twitter.jpg\"\n * imageAlt=\"Twitter card image\"\n * />\n * ```\n */\nexport function TwitterCard(props: TwitterCardProps) {\n const tags = buildTwitterTags(props);\n return createElement(Fragment, null, renderMetaTags(tags));\n}\n","// ============================================================================\n// @power-seo/react — <Canonical> Component\n// ============================================================================\n\nimport { createElement } from 'react';\nimport { resolveCanonical } from '@power-seo/core';\n\nexport interface CanonicalProps {\n /** The canonical URL (absolute or relative to baseUrl) */\n url: string;\n /** Base URL for resolving relative paths */\n baseUrl?: string;\n /** Whether to add trailing slash (default: false) */\n trailingSlash?: boolean;\n}\n\n/**\n * Render a canonical link tag.\n *\n * @example\n * ```tsx\n * <Canonical url=\"https://example.com/blog/post\" />\n * // or with base URL:\n * <Canonical url=\"/blog/post\" baseUrl=\"https://example.com\" />\n * ```\n */\nexport function Canonical({ url, baseUrl, trailingSlash = false }: CanonicalProps) {\n let canonical = baseUrl ? resolveCanonical(baseUrl, url) : url;\n\n if (trailingSlash && !canonical.endsWith('/')) {\n canonical += '/';\n } else if (\n !trailingSlash &&\n canonical.endsWith('/') &&\n canonical !== url.replace(/\\/$/, '') + '/'\n ) {\n // Only strip trailing slash if it was explicitly added\n }\n\n return createElement('link', {\n rel: 'canonical',\n href: canonical,\n });\n}\n","// ============================================================================\n// @power-seo/react — <Robots> Component\n// ============================================================================\n\nimport { createElement } from 'react';\nimport type { RobotsDirective } from '@power-seo/core';\nimport { buildRobotsContent } from '@power-seo/core';\n\nexport type RobotsProps = RobotsDirective;\n\n/**\n * Render a robots meta tag with per-page directives.\n *\n * @example\n * ```tsx\n * <Robots index={false} follow={true} maxSnippet={150} />\n * // Renders: <meta name=\"robots\" content=\"noindex, follow, max-snippet:150\" />\n * ```\n */\nexport function Robots(props: RobotsProps) {\n const content = buildRobotsContent(props);\n if (!content) return null;\n return createElement('meta', { name: 'robots', content });\n}\n","// ============================================================================\n// @power-seo/react — <Hreflang> Component\n// ============================================================================\n\nimport { createElement, Fragment } from 'react';\nimport type { HreflangConfig } from '@power-seo/core';\n\nexport interface HreflangProps {\n /** Language alternates */\n alternates: HreflangConfig[];\n /** Whether to include x-default (usually same as default language) */\n xDefault?: string;\n}\n\n/**\n * Render hreflang link tags for multi-language pages.\n *\n * @example\n * ```tsx\n * <Hreflang\n * alternates={[\n * { hrefLang: 'en', href: 'https://example.com/en/page' },\n * { hrefLang: 'fr', href: 'https://example.com/fr/page' },\n * { hrefLang: 'de', href: 'https://example.com/de/page' },\n * ]}\n * xDefault=\"https://example.com/en/page\"\n * />\n * ```\n */\nexport function Hreflang({ alternates, xDefault }: HreflangProps) {\n const links = alternates.map((alt) =>\n createElement('link', {\n key: `hreflang-${alt.hrefLang}`,\n rel: 'alternate',\n hrefLang: alt.hrefLang,\n href: alt.href,\n }),\n );\n\n if (xDefault) {\n links.push(\n createElement('link', {\n key: 'hreflang-x-default',\n rel: 'alternate',\n hrefLang: 'x-default',\n href: xDefault,\n }),\n );\n }\n\n return createElement(Fragment, null, ...links);\n}\n","// ============================================================================\n// @power-seo/react — <Breadcrumb> Component\n// ============================================================================\n\nimport { createElement, Fragment } from 'react';\n\nexport interface BreadcrumbItem {\n name: string;\n url?: string;\n}\n\nexport interface BreadcrumbProps {\n /** Breadcrumb items from root to current page */\n items: BreadcrumbItem[];\n /** Separator between items (default: \" / \") */\n separator?: string;\n /** CSS class for the nav element */\n className?: string;\n /** CSS class for each link */\n linkClassName?: string;\n /** CSS class for the current (last) item */\n activeClassName?: string;\n /** Whether to render the JSON-LD alongside the visual breadcrumb (default: true) */\n includeJsonLd?: boolean;\n}\n\n/**\n * Visual breadcrumb navigation with optional BreadcrumbList JSON-LD.\n *\n * @example\n * ```tsx\n * <Breadcrumb\n * items={[\n * { name: 'Home', url: '/' },\n * { name: 'Blog', url: '/blog' },\n * { name: 'Current Post' },\n * ]}\n * />\n * ```\n */\nexport function Breadcrumb({\n items,\n separator = ' / ',\n className,\n linkClassName,\n activeClassName,\n includeJsonLd = true,\n}: BreadcrumbProps) {\n // Build JSON-LD schema\n const jsonLdData = {\n '@context': 'https://schema.org' as const,\n '@type': 'BreadcrumbList' as const,\n itemListElement: items.map((item, index) => ({\n '@type': 'ListItem' as const,\n position: index + 1,\n name: item.name,\n ...(item.url ? { item: item.url } : {}),\n })),\n };\n\n const children: ReturnType<typeof createElement>[] = [];\n\n // Render visual breadcrumb\n const breadcrumbItems: ReturnType<typeof createElement>[] = [];\n items.forEach((item, index) => {\n if (index > 0) {\n breadcrumbItems.push(\n createElement('span', { key: `sep-${index}`, 'aria-hidden': 'true' }, separator),\n );\n }\n\n const isLast = index === items.length - 1;\n\n if (item.url && !isLast) {\n breadcrumbItems.push(\n createElement(\n 'a',\n { key: `item-${index}`, href: item.url, className: linkClassName },\n item.name,\n ),\n );\n } else {\n breadcrumbItems.push(\n createElement(\n 'span',\n {\n key: `item-${index}`,\n className: isLast ? activeClassName : undefined,\n 'aria-current': isLast ? 'page' : undefined,\n },\n item.name,\n ),\n );\n }\n });\n\n children.push(\n createElement(\n 'nav',\n { 'aria-label': 'Breadcrumb', className },\n createElement(\n 'ol',\n {\n style: {\n listStyle: 'none',\n padding: 0,\n margin: 0,\n display: 'flex',\n flexWrap: 'wrap' as const,\n },\n },\n ...breadcrumbItems,\n ),\n ),\n );\n\n // Render JSON-LD\n if (includeJsonLd) {\n children.push(\n createElement('script', {\n key: 'breadcrumb-jsonld',\n type: 'application/ld+json',\n dangerouslySetInnerHTML: { __html: JSON.stringify(jsonLdData) },\n }),\n );\n }\n\n return createElement(Fragment, null, ...children);\n}\n"]} | ||
| {"version":3,"sources":["../src/index.ts","../src/context.ts","../src/components/DefaultSEO.ts","../src/head-tags.ts","../src/components/SEO.ts","../src/components/OpenGraph.ts","../src/components/TwitterCard.ts","../src/components/Canonical.ts","../src/components/Robots.ts","../src/components/Hreflang.ts","../src/components/Breadcrumb.ts"],"sourcesContent":["// ============================================================================\n// @power-seo/react — Main Entry Point\n// ============================================================================\n\n// Context\nexport { SEOContext, useDefaultSEO } from './context.js';\n\n// Components\nexport { DefaultSEO } from './components/DefaultSEO.js';\nexport type { DefaultSEOProps } from './components/DefaultSEO.js';\n\nexport { SEO } from './components/SEO.js';\nexport type { SEOProps } from './components/SEO.js';\n\nexport { OpenGraph } from './components/OpenGraph.js';\nexport type { OpenGraphProps } from './components/OpenGraph.js';\n\nexport { TwitterCard } from './components/TwitterCard.js';\nexport type { TwitterCardProps } from './components/TwitterCard.js';\n\nexport { Canonical } from './components/Canonical.js';\nexport type { CanonicalProps } from './components/Canonical.js';\n\nexport { Robots } from './components/Robots.js';\nexport type { RobotsProps } from './components/Robots.js';\n\nexport { Hreflang } from './components/Hreflang.js';\nexport type { HreflangProps } from './components/Hreflang.js';\n\nexport { Breadcrumb } from './components/Breadcrumb.js';\nexport type { BreadcrumbProps, BreadcrumbItem } from './components/Breadcrumb.js';\n\n// Head tag utilities\nexport { renderMetaTags, renderLinkTags } from './head-tags.js';\n","'use client';\n// ============================================================================\n// @power-seo/react — SEO Context for Default Configuration\n// ============================================================================\n\nimport { createContext, useContext } from 'react';\nimport type { SEOConfig } from '@power-seo/core';\n\nexport const SEOContext = createContext<SEOConfig | null>(null);\n\n/**\n * Hook to access the default SEO configuration from the nearest DefaultSEO provider.\n */\nexport function useDefaultSEO(): SEOConfig | null {\n return useContext(SEOContext);\n}\n","'use client';\n// ============================================================================\n// @power-seo/react — <DefaultSEO> Component\n// ============================================================================\n\nimport { createElement } from 'react';\nimport type { ReactNode } from 'react';\nimport type { SEOConfig } from '@power-seo/core';\nimport { buildMetaTags, buildLinkTags, resolveTitle } from '@power-seo/core';\nimport { SEOContext } from '../context.js';\nimport { renderMetaTags, renderLinkTags } from '../head-tags.js';\n\nexport interface DefaultSEOProps extends SEOConfig {\n children?: ReactNode;\n}\n\n/**\n * Provide global default SEO configuration.\n * Renders default meta tags and wraps children with SEO context.\n *\n * @example\n * ```tsx\n * <DefaultSEO\n * titleTemplate=\"%s | My Site\"\n * defaultTitle=\"My Site\"\n * description=\"Default description\"\n * openGraph={{\n * type: 'website',\n * siteName: 'My Site',\n * }}\n * >\n * <App />\n * </DefaultSEO>\n * ```\n */\nexport function DefaultSEO({ children, ...config }: DefaultSEOProps) {\n const title = resolveTitle(config);\n const metaTags = buildMetaTags(config);\n const linkTags = buildLinkTags(config);\n\n return createElement(\n SEOContext.Provider,\n { value: config },\n title ? createElement('title', null, title) : null,\n renderMetaTags(metaTags),\n renderLinkTags(linkTags),\n children,\n );\n}\n","// ============================================================================\n// @power-seo/react — Head Tag Rendering (React 19 native + fallback)\n// ============================================================================\n\nimport { createElement, Fragment } from 'react';\nimport type { MetaTag, LinkTag } from '@power-seo/core';\n\n/**\n * Render meta tags as React elements.\n * In React 19, these automatically hoist to <head>.\n * In React 18, wrap with a Helmet provider or use a framework's Head component.\n */\nexport function renderMetaTags(tags: MetaTag[]) {\n return createElement(\n Fragment,\n null,\n ...tags.map((tag, i) => {\n const props: Record<string, string> = { content: tag.content };\n if (tag.name) props.name = tag.name;\n if (tag.property) props.property = tag.property;\n if (tag.httpEquiv) props.httpEquiv = tag.httpEquiv;\n props.key = `meta-${tag.name ?? tag.property ?? tag.httpEquiv ?? i}`;\n return createElement('meta', props);\n }),\n );\n}\n\n/**\n * Render link tags as React elements.\n */\nexport function renderLinkTags(tags: LinkTag[]) {\n return createElement(\n Fragment,\n null,\n ...tags.map((tag, i) => {\n const props: Record<string, string | undefined> = {\n rel: tag.rel,\n href: tag.href,\n key: `link-${tag.rel}-${tag.hreflang ?? i}`,\n };\n if (tag.hreflang) props.hrefLang = tag.hreflang;\n if (tag.type) props.type = tag.type;\n if (tag.sizes) props.sizes = tag.sizes;\n if (tag.media) props.media = tag.media;\n if (tag.as) props.as = tag.as;\n if (tag.crossOrigin) props.crossOrigin = tag.crossOrigin;\n return createElement('link', props);\n }),\n );\n}\n","'use client';\n// ============================================================================\n// @power-seo/react — <SEO> Component\n// ============================================================================\n\nimport { createElement, Fragment } from 'react';\nimport type { SEOConfig } from '@power-seo/core';\nimport { buildMetaTags, buildLinkTags, resolveTitle } from '@power-seo/core';\nimport { useDefaultSEO } from '../context.js';\nimport { renderMetaTags, renderLinkTags } from '../head-tags.js';\n\nexport type SEOProps = SEOConfig;\n\n/**\n * All-in-one SEO component for per-page meta tag management.\n * Merges with DefaultSEO context if available.\n *\n * In React 19, <title>, <meta>, and <link> tags automatically hoist to <head>.\n * In React 18, use with a Helmet provider or framework Head component.\n *\n * @example\n * ```tsx\n * <SEO\n * title=\"About Us\"\n * description=\"Learn about our company\"\n * canonical=\"https://example.com/about\"\n * openGraph={{\n * title: 'About Us',\n * description: 'Learn about our company',\n * images: [{ url: 'https://example.com/about-og.jpg', width: 1200, height: 630 }],\n * }}\n * twitter={{\n * cardType: 'summary_large_image',\n * }}\n * />\n * ```\n */\nexport function SEO(props: SEOProps) {\n const defaults = useDefaultSEO();\n\n // Merge page config with defaults\n const config: SEOConfig = {\n ...defaults,\n ...props,\n // Deep merge for nested objects\n openGraph: {\n ...defaults?.openGraph,\n ...props.openGraph,\n images: props.openGraph?.images ?? defaults?.openGraph?.images,\n videos: props.openGraph?.videos ?? defaults?.openGraph?.videos,\n },\n twitter: {\n ...defaults?.twitter,\n ...props.twitter,\n },\n additionalMetaTags: [\n ...(defaults?.additionalMetaTags ?? []),\n ...(props.additionalMetaTags ?? []),\n ],\n additionalLinkTags: [\n ...(defaults?.additionalLinkTags ?? []),\n ...(props.additionalLinkTags ?? []),\n ],\n languageAlternates: props.languageAlternates ?? defaults?.languageAlternates,\n // Use page-specific title template or default\n titleTemplate: props.titleTemplate ?? defaults?.titleTemplate,\n };\n\n const title = resolveTitle(config);\n const metaTags = buildMetaTags(config);\n const linkTags = buildLinkTags(config);\n\n return createElement(\n Fragment,\n null,\n title ? createElement('title', null, title) : null,\n renderMetaTags(metaTags),\n renderLinkTags(linkTags),\n );\n}\n","// ============================================================================\n// @power-seo/react — <OpenGraph> Component\n// ============================================================================\n\nimport { createElement, Fragment } from 'react';\nimport type { OpenGraphConfig } from '@power-seo/core';\nimport { buildOpenGraphTags } from '@power-seo/core';\nimport { renderMetaTags } from '../head-tags.js';\n\nexport type OpenGraphProps = OpenGraphConfig;\n\n/**\n * Render Open Graph meta tags.\n *\n * @example\n * ```tsx\n * <OpenGraph\n * type=\"article\"\n * title=\"My Article\"\n * description=\"Article description\"\n * url=\"https://example.com/article\"\n * images={[{\n * url: 'https://example.com/og.jpg',\n * width: 1200,\n * height: 630,\n * alt: 'Article image',\n * }]}\n * article={{\n * publishedTime: '2025-01-01',\n * authors: ['https://example.com/author'],\n * tags: ['react', 'seo'],\n * }}\n * />\n * ```\n */\nexport function OpenGraph(props: OpenGraphProps) {\n const tags = buildOpenGraphTags(props);\n return createElement(Fragment, null, renderMetaTags(tags));\n}\n","// ============================================================================\n// @power-seo/react — <TwitterCard> Component\n// ============================================================================\n\nimport { createElement, Fragment } from 'react';\nimport type { TwitterCardConfig } from '@power-seo/core';\nimport { buildTwitterTags } from '@power-seo/core';\nimport { renderMetaTags } from '../head-tags.js';\n\nexport type TwitterCardProps = TwitterCardConfig;\n\n/**\n * Render Twitter Card meta tags.\n *\n * @example\n * ```tsx\n * <TwitterCard\n * cardType=\"summary_large_image\"\n * site=\"@mysite\"\n * creator=\"@author\"\n * title=\"My Article\"\n * description=\"Article description\"\n * image=\"https://example.com/twitter.jpg\"\n * imageAlt=\"Twitter card image\"\n * />\n * ```\n */\nexport function TwitterCard(props: TwitterCardProps) {\n const tags = buildTwitterTags(props);\n return createElement(Fragment, null, renderMetaTags(tags));\n}\n","// ============================================================================\n// @power-seo/react — <Canonical> Component\n// ============================================================================\n\nimport { createElement } from 'react';\nimport { resolveCanonical } from '@power-seo/core';\n\nexport interface CanonicalProps {\n /** The canonical URL (absolute or relative to baseUrl) */\n url: string;\n /** Base URL for resolving relative paths */\n baseUrl?: string;\n /** Whether to add trailing slash (default: false) */\n trailingSlash?: boolean;\n}\n\n/**\n * Render a canonical link tag.\n *\n * @example\n * ```tsx\n * <Canonical url=\"https://example.com/blog/post\" />\n * // or with base URL:\n * <Canonical url=\"/blog/post\" baseUrl=\"https://example.com\" />\n * ```\n */\nexport function Canonical({ url, baseUrl, trailingSlash = false }: CanonicalProps) {\n let canonical = baseUrl ? resolveCanonical(baseUrl, url) : url;\n\n if (trailingSlash && !canonical.endsWith('/')) {\n canonical += '/';\n } else if (\n !trailingSlash &&\n canonical.endsWith('/') &&\n canonical !== url.replace(/\\/$/, '') + '/'\n ) {\n // Only strip trailing slash if it was explicitly added\n }\n\n return createElement('link', {\n rel: 'canonical',\n href: canonical,\n });\n}\n","// ============================================================================\n// @power-seo/react — <Robots> Component\n// ============================================================================\n\nimport { createElement } from 'react';\nimport type { RobotsDirective } from '@power-seo/core';\nimport { buildRobotsContent } from '@power-seo/core';\n\nexport type RobotsProps = RobotsDirective;\n\n/**\n * Render a robots meta tag with per-page directives.\n *\n * @example\n * ```tsx\n * <Robots index={false} follow={true} maxSnippet={150} />\n * // Renders: <meta name=\"robots\" content=\"noindex, follow, max-snippet:150\" />\n * ```\n */\nexport function Robots(props: RobotsProps) {\n const content = buildRobotsContent(props);\n if (!content) return null;\n return createElement('meta', { name: 'robots', content });\n}\n","// ============================================================================\n// @power-seo/react — <Hreflang> Component\n// ============================================================================\n\nimport { createElement, Fragment } from 'react';\nimport type { HreflangConfig } from '@power-seo/core';\n\nexport interface HreflangProps {\n /** Language alternates */\n alternates: HreflangConfig[];\n /** Whether to include x-default (usually same as default language) */\n xDefault?: string;\n}\n\n/**\n * Render hreflang link tags for multi-language pages.\n *\n * @example\n * ```tsx\n * <Hreflang\n * alternates={[\n * { hrefLang: 'en', href: 'https://example.com/en/page' },\n * { hrefLang: 'fr', href: 'https://example.com/fr/page' },\n * { hrefLang: 'de', href: 'https://example.com/de/page' },\n * ]}\n * xDefault=\"https://example.com/en/page\"\n * />\n * ```\n */\nexport function Hreflang({ alternates, xDefault }: HreflangProps) {\n const links = alternates.map((alt) =>\n createElement('link', {\n key: `hreflang-${alt.hrefLang}`,\n rel: 'alternate',\n hrefLang: alt.hrefLang,\n href: alt.href,\n }),\n );\n\n if (xDefault) {\n links.push(\n createElement('link', {\n key: 'hreflang-x-default',\n rel: 'alternate',\n hrefLang: 'x-default',\n href: xDefault,\n }),\n );\n }\n\n return createElement(Fragment, null, ...links);\n}\n","// ============================================================================\n// @power-seo/react — <Breadcrumb> Component\n// ============================================================================\n\nimport { createElement, Fragment } from 'react';\n\nexport interface BreadcrumbItem {\n name: string;\n url?: string;\n}\n\nexport interface BreadcrumbProps {\n /** Breadcrumb items from root to current page */\n items: BreadcrumbItem[];\n /** Separator between items (default: \" / \") */\n separator?: string;\n /** CSS class for the nav element */\n className?: string;\n /** CSS class for each link */\n linkClassName?: string;\n /** CSS class for the current (last) item */\n activeClassName?: string;\n /** Whether to render the JSON-LD alongside the visual breadcrumb (default: true) */\n includeJsonLd?: boolean;\n}\n\n/**\n * Visual breadcrumb navigation with optional BreadcrumbList JSON-LD.\n *\n * @example\n * ```tsx\n * <Breadcrumb\n * items={[\n * { name: 'Home', url: '/' },\n * { name: 'Blog', url: '/blog' },\n * { name: 'Current Post' },\n * ]}\n * />\n * ```\n */\nexport function Breadcrumb({\n items,\n separator = ' / ',\n className,\n linkClassName,\n activeClassName,\n includeJsonLd = true,\n}: BreadcrumbProps) {\n // Build JSON-LD schema\n const jsonLdData = {\n '@context': 'https://schema.org' as const,\n '@type': 'BreadcrumbList' as const,\n itemListElement: items.map((item, index) => ({\n '@type': 'ListItem' as const,\n position: index + 1,\n name: item.name,\n ...(item.url ? { item: item.url } : {}),\n })),\n };\n\n const children: ReturnType<typeof createElement>[] = [];\n\n // Render visual breadcrumb\n const breadcrumbItems: ReturnType<typeof createElement>[] = [];\n items.forEach((item, index) => {\n if (index > 0) {\n breadcrumbItems.push(\n createElement('span', { key: `sep-${index}`, 'aria-hidden': 'true' }, separator),\n );\n }\n\n const isLast = index === items.length - 1;\n\n if (item.url && !isLast) {\n breadcrumbItems.push(\n createElement(\n 'a',\n { key: `item-${index}`, href: item.url, className: linkClassName },\n item.name,\n ),\n );\n } else {\n breadcrumbItems.push(\n createElement(\n 'span',\n {\n key: `item-${index}`,\n className: isLast ? activeClassName : undefined,\n 'aria-current': isLast ? 'page' : undefined,\n },\n item.name,\n ),\n );\n }\n });\n\n children.push(\n createElement(\n 'nav',\n { 'aria-label': 'Breadcrumb', className },\n createElement(\n 'ol',\n {\n style: {\n listStyle: 'none',\n padding: 0,\n margin: 0,\n display: 'flex',\n flexWrap: 'wrap' as const,\n },\n },\n ...breadcrumbItems,\n ),\n ),\n );\n\n // Render JSON-LD\n if (includeJsonLd) {\n children.push(\n createElement('script', {\n key: 'breadcrumb-jsonld',\n type: 'application/ld+json',\n dangerouslySetInnerHTML: { __html: JSON.stringify(jsonLdData) },\n }),\n );\n }\n\n return createElement(Fragment, null, ...children);\n}\n"],"mappings":"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","names":["import_react","import_react","import_react","import_core","import_react","import_core","import_react","import_core","import_react","import_core","import_react","import_core","import_react","import_react"]} |
+72
-29
@@ -1,5 +0,3 @@ | ||
| import { createContext, useContext, createElement, Fragment } from 'react'; | ||
| import { resolveTitle, buildMetaTags, buildLinkTags, buildOpenGraphTags, buildTwitterTags, resolveCanonical, buildRobotsContent } from '@power-seo/core'; | ||
| // src/context.ts | ||
| import { createContext, useContext } from "react"; | ||
| var SEOContext = createContext(null); | ||
@@ -9,2 +7,9 @@ function useDefaultSEO() { | ||
| } | ||
| // src/components/DefaultSEO.ts | ||
| import { createElement as createElement2 } from "react"; | ||
| import { buildMetaTags, buildLinkTags, resolveTitle } from "@power-seo/core"; | ||
| // src/head-tags.ts | ||
| import { createElement, Fragment } from "react"; | ||
| function renderMetaTags(tags) { | ||
@@ -50,6 +55,6 @@ return createElement( | ||
| const linkTags = buildLinkTags(config); | ||
| return createElement( | ||
| return createElement2( | ||
| SEOContext.Provider, | ||
| { value: config }, | ||
| title ? createElement("title", null, title) : null, | ||
| title ? createElement2("title", null, title) : null, | ||
| renderMetaTags(metaTags), | ||
@@ -60,2 +65,6 @@ renderLinkTags(linkTags), | ||
| } | ||
| // src/components/SEO.ts | ||
| import { createElement as createElement3, Fragment as Fragment2 } from "react"; | ||
| import { buildMetaTags as buildMetaTags2, buildLinkTags as buildLinkTags2, resolveTitle as resolveTitle2 } from "@power-seo/core"; | ||
| function SEO(props) { | ||
@@ -89,9 +98,9 @@ const defaults = useDefaultSEO(); | ||
| }; | ||
| const title = resolveTitle(config); | ||
| const metaTags = buildMetaTags(config); | ||
| const linkTags = buildLinkTags(config); | ||
| return createElement( | ||
| Fragment, | ||
| const title = resolveTitle2(config); | ||
| const metaTags = buildMetaTags2(config); | ||
| const linkTags = buildLinkTags2(config); | ||
| return createElement3( | ||
| Fragment2, | ||
| null, | ||
| title ? createElement("title", null, title) : null, | ||
| title ? createElement3("title", null, title) : null, | ||
| renderMetaTags(metaTags), | ||
@@ -101,10 +110,22 @@ renderLinkTags(linkTags) | ||
| } | ||
| // src/components/OpenGraph.ts | ||
| import { createElement as createElement4, Fragment as Fragment3 } from "react"; | ||
| import { buildOpenGraphTags } from "@power-seo/core"; | ||
| function OpenGraph(props) { | ||
| const tags = buildOpenGraphTags(props); | ||
| return createElement(Fragment, null, renderMetaTags(tags)); | ||
| return createElement4(Fragment3, null, renderMetaTags(tags)); | ||
| } | ||
| // src/components/TwitterCard.ts | ||
| import { createElement as createElement5, Fragment as Fragment4 } from "react"; | ||
| import { buildTwitterTags } from "@power-seo/core"; | ||
| function TwitterCard(props) { | ||
| const tags = buildTwitterTags(props); | ||
| return createElement(Fragment, null, renderMetaTags(tags)); | ||
| return createElement5(Fragment4, null, renderMetaTags(tags)); | ||
| } | ||
| // src/components/Canonical.ts | ||
| import { createElement as createElement6 } from "react"; | ||
| import { resolveCanonical } from "@power-seo/core"; | ||
| function Canonical({ url, baseUrl, trailingSlash = false }) { | ||
@@ -114,4 +135,5 @@ let canonical = baseUrl ? resolveCanonical(baseUrl, url) : url; | ||
| canonical += "/"; | ||
| } else if (!trailingSlash && canonical.endsWith("/") && canonical !== url.replace(/\/$/, "") + "/") ; | ||
| return createElement("link", { | ||
| } else if (!trailingSlash && canonical.endsWith("/") && canonical !== url.replace(/\/$/, "") + "/") { | ||
| } | ||
| return createElement6("link", { | ||
| rel: "canonical", | ||
@@ -121,10 +143,17 @@ href: canonical | ||
| } | ||
| // src/components/Robots.ts | ||
| import { createElement as createElement7 } from "react"; | ||
| import { buildRobotsContent } from "@power-seo/core"; | ||
| function Robots(props) { | ||
| const content = buildRobotsContent(props); | ||
| if (!content) return null; | ||
| return createElement("meta", { name: "robots", content }); | ||
| return createElement7("meta", { name: "robots", content }); | ||
| } | ||
| // src/components/Hreflang.ts | ||
| import { createElement as createElement8, Fragment as Fragment5 } from "react"; | ||
| function Hreflang({ alternates, xDefault }) { | ||
| const links = alternates.map( | ||
| (alt) => createElement("link", { | ||
| (alt) => createElement8("link", { | ||
| key: `hreflang-${alt.hrefLang}`, | ||
@@ -138,3 +167,3 @@ rel: "alternate", | ||
| links.push( | ||
| createElement("link", { | ||
| createElement8("link", { | ||
| key: "hreflang-x-default", | ||
@@ -147,4 +176,7 @@ rel: "alternate", | ||
| } | ||
| return createElement(Fragment, null, ...links); | ||
| return createElement8(Fragment5, null, ...links); | ||
| } | ||
| // src/components/Breadcrumb.ts | ||
| import { createElement as createElement9, Fragment as Fragment6 } from "react"; | ||
| function Breadcrumb({ | ||
@@ -173,3 +205,3 @@ items, | ||
| breadcrumbItems.push( | ||
| createElement("span", { key: `sep-${index}`, "aria-hidden": "true" }, separator) | ||
| createElement9("span", { key: `sep-${index}`, "aria-hidden": "true" }, separator) | ||
| ); | ||
@@ -180,3 +212,3 @@ } | ||
| breadcrumbItems.push( | ||
| createElement( | ||
| createElement9( | ||
| "a", | ||
@@ -189,3 +221,3 @@ { key: `item-${index}`, href: item.url, className: linkClassName }, | ||
| breadcrumbItems.push( | ||
| createElement( | ||
| createElement9( | ||
| "span", | ||
@@ -203,6 +235,6 @@ { | ||
| children.push( | ||
| createElement( | ||
| createElement9( | ||
| "nav", | ||
| { "aria-label": "Breadcrumb", className }, | ||
| createElement( | ||
| createElement9( | ||
| "ol", | ||
@@ -224,3 +256,3 @@ { | ||
| children.push( | ||
| createElement("script", { | ||
| createElement9("script", { | ||
| key: "breadcrumb-jsonld", | ||
@@ -232,7 +264,18 @@ type: "application/ld+json", | ||
| } | ||
| return createElement(Fragment, null, ...children); | ||
| return createElement9(Fragment6, null, ...children); | ||
| } | ||
| export { Breadcrumb, Canonical, DefaultSEO, Hreflang, OpenGraph, Robots, SEO, SEOContext, TwitterCard, renderLinkTags, renderMetaTags, useDefaultSEO }; | ||
| //# sourceMappingURL=index.js.map | ||
| export { | ||
| Breadcrumb, | ||
| Canonical, | ||
| DefaultSEO, | ||
| Hreflang, | ||
| OpenGraph, | ||
| Robots, | ||
| SEO, | ||
| SEOContext, | ||
| TwitterCard, | ||
| renderLinkTags, | ||
| renderMetaTags, | ||
| useDefaultSEO | ||
| }; | ||
| //# sourceMappingURL=index.js.map |
@@ -1,1 +0,1 @@ | ||
| {"version":3,"sources":["../src/context.ts","../src/head-tags.ts","../src/components/DefaultSEO.ts","../src/components/SEO.ts","../src/components/OpenGraph.ts","../src/components/TwitterCard.ts","../src/components/Canonical.ts","../src/components/Robots.ts","../src/components/Hreflang.ts","../src/components/Breadcrumb.ts"],"names":["createElement","resolveTitle","buildMetaTags","buildLinkTags","Fragment"],"mappings":"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","file":"index.js","sourcesContent":["// ============================================================================\n// @power-seo/react — SEO Context for Default Configuration\n// ============================================================================\n\nimport { createContext, useContext } from 'react';\nimport type { SEOConfig } from '@power-seo/core';\n\nexport const SEOContext = createContext<SEOConfig | null>(null);\n\n/**\n * Hook to access the default SEO configuration from the nearest DefaultSEO provider.\n */\nexport function useDefaultSEO(): SEOConfig | null {\n return useContext(SEOContext);\n}\n","// ============================================================================\n// @power-seo/react — Head Tag Rendering (React 19 native + fallback)\n// ============================================================================\n\nimport { createElement, Fragment } from 'react';\nimport type { MetaTag, LinkTag } from '@power-seo/core';\n\n/**\n * Render meta tags as React elements.\n * In React 19, these automatically hoist to <head>.\n * In React 18, wrap with a Helmet provider or use a framework's Head component.\n */\nexport function renderMetaTags(tags: MetaTag[]) {\n return createElement(\n Fragment,\n null,\n ...tags.map((tag, i) => {\n const props: Record<string, string> = { content: tag.content };\n if (tag.name) props.name = tag.name;\n if (tag.property) props.property = tag.property;\n if (tag.httpEquiv) props.httpEquiv = tag.httpEquiv;\n props.key = `meta-${tag.name ?? tag.property ?? tag.httpEquiv ?? i}`;\n return createElement('meta', props);\n }),\n );\n}\n\n/**\n * Render link tags as React elements.\n */\nexport function renderLinkTags(tags: LinkTag[]) {\n return createElement(\n Fragment,\n null,\n ...tags.map((tag, i) => {\n const props: Record<string, string | undefined> = {\n rel: tag.rel,\n href: tag.href,\n key: `link-${tag.rel}-${tag.hreflang ?? i}`,\n };\n if (tag.hreflang) props.hrefLang = tag.hreflang;\n if (tag.type) props.type = tag.type;\n if (tag.sizes) props.sizes = tag.sizes;\n if (tag.media) props.media = tag.media;\n if (tag.as) props.as = tag.as;\n if (tag.crossOrigin) props.crossOrigin = tag.crossOrigin;\n return createElement('link', props);\n }),\n );\n}\n","// ============================================================================\n// @power-seo/react — <DefaultSEO> Component\n// ============================================================================\n\nimport { createElement } from 'react';\nimport type { ReactNode } from 'react';\nimport type { SEOConfig } from '@power-seo/core';\nimport { buildMetaTags, buildLinkTags, resolveTitle } from '@power-seo/core';\nimport { SEOContext } from '../context.js';\nimport { renderMetaTags, renderLinkTags } from '../head-tags.js';\n\nexport interface DefaultSEOProps extends SEOConfig {\n children?: ReactNode;\n}\n\n/**\n * Provide global default SEO configuration.\n * Renders default meta tags and wraps children with SEO context.\n *\n * @example\n * ```tsx\n * <DefaultSEO\n * titleTemplate=\"%s | My Site\"\n * defaultTitle=\"My Site\"\n * description=\"Default description\"\n * openGraph={{\n * type: 'website',\n * siteName: 'My Site',\n * }}\n * >\n * <App />\n * </DefaultSEO>\n * ```\n */\nexport function DefaultSEO({ children, ...config }: DefaultSEOProps) {\n const title = resolveTitle(config);\n const metaTags = buildMetaTags(config);\n const linkTags = buildLinkTags(config);\n\n return createElement(\n SEOContext.Provider,\n { value: config },\n title ? createElement('title', null, title) : null,\n renderMetaTags(metaTags),\n renderLinkTags(linkTags),\n children,\n );\n}\n","// ============================================================================\n// @power-seo/react — <SEO> Component\n// ============================================================================\n\nimport { createElement, Fragment } from 'react';\nimport type { SEOConfig } from '@power-seo/core';\nimport { buildMetaTags, buildLinkTags, resolveTitle } from '@power-seo/core';\nimport { useDefaultSEO } from '../context.js';\nimport { renderMetaTags, renderLinkTags } from '../head-tags.js';\n\nexport type SEOProps = SEOConfig;\n\n/**\n * All-in-one SEO component for per-page meta tag management.\n * Merges with DefaultSEO context if available.\n *\n * In React 19, <title>, <meta>, and <link> tags automatically hoist to <head>.\n * In React 18, use with a Helmet provider or framework Head component.\n *\n * @example\n * ```tsx\n * <SEO\n * title=\"About Us\"\n * description=\"Learn about our company\"\n * canonical=\"https://example.com/about\"\n * openGraph={{\n * title: 'About Us',\n * description: 'Learn about our company',\n * images: [{ url: 'https://example.com/about-og.jpg', width: 1200, height: 630 }],\n * }}\n * twitter={{\n * cardType: 'summary_large_image',\n * }}\n * />\n * ```\n */\nexport function SEO(props: SEOProps) {\n const defaults = useDefaultSEO();\n\n // Merge page config with defaults\n const config: SEOConfig = {\n ...defaults,\n ...props,\n // Deep merge for nested objects\n openGraph: {\n ...defaults?.openGraph,\n ...props.openGraph,\n images: props.openGraph?.images ?? defaults?.openGraph?.images,\n videos: props.openGraph?.videos ?? defaults?.openGraph?.videos,\n },\n twitter: {\n ...defaults?.twitter,\n ...props.twitter,\n },\n additionalMetaTags: [\n ...(defaults?.additionalMetaTags ?? []),\n ...(props.additionalMetaTags ?? []),\n ],\n additionalLinkTags: [\n ...(defaults?.additionalLinkTags ?? []),\n ...(props.additionalLinkTags ?? []),\n ],\n languageAlternates: props.languageAlternates ?? defaults?.languageAlternates,\n // Use page-specific title template or default\n titleTemplate: props.titleTemplate ?? defaults?.titleTemplate,\n };\n\n const title = resolveTitle(config);\n const metaTags = buildMetaTags(config);\n const linkTags = buildLinkTags(config);\n\n return createElement(\n Fragment,\n null,\n title ? createElement('title', null, title) : null,\n renderMetaTags(metaTags),\n renderLinkTags(linkTags),\n );\n}\n","// ============================================================================\n// @power-seo/react — <OpenGraph> Component\n// ============================================================================\n\nimport { createElement, Fragment } from 'react';\nimport type { OpenGraphConfig } from '@power-seo/core';\nimport { buildOpenGraphTags } from '@power-seo/core';\nimport { renderMetaTags } from '../head-tags.js';\n\nexport type OpenGraphProps = OpenGraphConfig;\n\n/**\n * Render Open Graph meta tags.\n *\n * @example\n * ```tsx\n * <OpenGraph\n * type=\"article\"\n * title=\"My Article\"\n * description=\"Article description\"\n * url=\"https://example.com/article\"\n * images={[{\n * url: 'https://example.com/og.jpg',\n * width: 1200,\n * height: 630,\n * alt: 'Article image',\n * }]}\n * article={{\n * publishedTime: '2025-01-01',\n * authors: ['https://example.com/author'],\n * tags: ['react', 'seo'],\n * }}\n * />\n * ```\n */\nexport function OpenGraph(props: OpenGraphProps) {\n const tags = buildOpenGraphTags(props);\n return createElement(Fragment, null, renderMetaTags(tags));\n}\n","// ============================================================================\n// @power-seo/react — <TwitterCard> Component\n// ============================================================================\n\nimport { createElement, Fragment } from 'react';\nimport type { TwitterCardConfig } from '@power-seo/core';\nimport { buildTwitterTags } from '@power-seo/core';\nimport { renderMetaTags } from '../head-tags.js';\n\nexport type TwitterCardProps = TwitterCardConfig;\n\n/**\n * Render Twitter Card meta tags.\n *\n * @example\n * ```tsx\n * <TwitterCard\n * cardType=\"summary_large_image\"\n * site=\"@mysite\"\n * creator=\"@author\"\n * title=\"My Article\"\n * description=\"Article description\"\n * image=\"https://example.com/twitter.jpg\"\n * imageAlt=\"Twitter card image\"\n * />\n * ```\n */\nexport function TwitterCard(props: TwitterCardProps) {\n const tags = buildTwitterTags(props);\n return createElement(Fragment, null, renderMetaTags(tags));\n}\n","// ============================================================================\n// @power-seo/react — <Canonical> Component\n// ============================================================================\n\nimport { createElement } from 'react';\nimport { resolveCanonical } from '@power-seo/core';\n\nexport interface CanonicalProps {\n /** The canonical URL (absolute or relative to baseUrl) */\n url: string;\n /** Base URL for resolving relative paths */\n baseUrl?: string;\n /** Whether to add trailing slash (default: false) */\n trailingSlash?: boolean;\n}\n\n/**\n * Render a canonical link tag.\n *\n * @example\n * ```tsx\n * <Canonical url=\"https://example.com/blog/post\" />\n * // or with base URL:\n * <Canonical url=\"/blog/post\" baseUrl=\"https://example.com\" />\n * ```\n */\nexport function Canonical({ url, baseUrl, trailingSlash = false }: CanonicalProps) {\n let canonical = baseUrl ? resolveCanonical(baseUrl, url) : url;\n\n if (trailingSlash && !canonical.endsWith('/')) {\n canonical += '/';\n } else if (\n !trailingSlash &&\n canonical.endsWith('/') &&\n canonical !== url.replace(/\\/$/, '') + '/'\n ) {\n // Only strip trailing slash if it was explicitly added\n }\n\n return createElement('link', {\n rel: 'canonical',\n href: canonical,\n });\n}\n","// ============================================================================\n// @power-seo/react — <Robots> Component\n// ============================================================================\n\nimport { createElement } from 'react';\nimport type { RobotsDirective } from '@power-seo/core';\nimport { buildRobotsContent } from '@power-seo/core';\n\nexport type RobotsProps = RobotsDirective;\n\n/**\n * Render a robots meta tag with per-page directives.\n *\n * @example\n * ```tsx\n * <Robots index={false} follow={true} maxSnippet={150} />\n * // Renders: <meta name=\"robots\" content=\"noindex, follow, max-snippet:150\" />\n * ```\n */\nexport function Robots(props: RobotsProps) {\n const content = buildRobotsContent(props);\n if (!content) return null;\n return createElement('meta', { name: 'robots', content });\n}\n","// ============================================================================\n// @power-seo/react — <Hreflang> Component\n// ============================================================================\n\nimport { createElement, Fragment } from 'react';\nimport type { HreflangConfig } from '@power-seo/core';\n\nexport interface HreflangProps {\n /** Language alternates */\n alternates: HreflangConfig[];\n /** Whether to include x-default (usually same as default language) */\n xDefault?: string;\n}\n\n/**\n * Render hreflang link tags for multi-language pages.\n *\n * @example\n * ```tsx\n * <Hreflang\n * alternates={[\n * { hrefLang: 'en', href: 'https://example.com/en/page' },\n * { hrefLang: 'fr', href: 'https://example.com/fr/page' },\n * { hrefLang: 'de', href: 'https://example.com/de/page' },\n * ]}\n * xDefault=\"https://example.com/en/page\"\n * />\n * ```\n */\nexport function Hreflang({ alternates, xDefault }: HreflangProps) {\n const links = alternates.map((alt) =>\n createElement('link', {\n key: `hreflang-${alt.hrefLang}`,\n rel: 'alternate',\n hrefLang: alt.hrefLang,\n href: alt.href,\n }),\n );\n\n if (xDefault) {\n links.push(\n createElement('link', {\n key: 'hreflang-x-default',\n rel: 'alternate',\n hrefLang: 'x-default',\n href: xDefault,\n }),\n );\n }\n\n return createElement(Fragment, null, ...links);\n}\n","// ============================================================================\n// @power-seo/react — <Breadcrumb> Component\n// ============================================================================\n\nimport { createElement, Fragment } from 'react';\n\nexport interface BreadcrumbItem {\n name: string;\n url?: string;\n}\n\nexport interface BreadcrumbProps {\n /** Breadcrumb items from root to current page */\n items: BreadcrumbItem[];\n /** Separator between items (default: \" / \") */\n separator?: string;\n /** CSS class for the nav element */\n className?: string;\n /** CSS class for each link */\n linkClassName?: string;\n /** CSS class for the current (last) item */\n activeClassName?: string;\n /** Whether to render the JSON-LD alongside the visual breadcrumb (default: true) */\n includeJsonLd?: boolean;\n}\n\n/**\n * Visual breadcrumb navigation with optional BreadcrumbList JSON-LD.\n *\n * @example\n * ```tsx\n * <Breadcrumb\n * items={[\n * { name: 'Home', url: '/' },\n * { name: 'Blog', url: '/blog' },\n * { name: 'Current Post' },\n * ]}\n * />\n * ```\n */\nexport function Breadcrumb({\n items,\n separator = ' / ',\n className,\n linkClassName,\n activeClassName,\n includeJsonLd = true,\n}: BreadcrumbProps) {\n // Build JSON-LD schema\n const jsonLdData = {\n '@context': 'https://schema.org' as const,\n '@type': 'BreadcrumbList' as const,\n itemListElement: items.map((item, index) => ({\n '@type': 'ListItem' as const,\n position: index + 1,\n name: item.name,\n ...(item.url ? { item: item.url } : {}),\n })),\n };\n\n const children: ReturnType<typeof createElement>[] = [];\n\n // Render visual breadcrumb\n const breadcrumbItems: ReturnType<typeof createElement>[] = [];\n items.forEach((item, index) => {\n if (index > 0) {\n breadcrumbItems.push(\n createElement('span', { key: `sep-${index}`, 'aria-hidden': 'true' }, separator),\n );\n }\n\n const isLast = index === items.length - 1;\n\n if (item.url && !isLast) {\n breadcrumbItems.push(\n createElement(\n 'a',\n { key: `item-${index}`, href: item.url, className: linkClassName },\n item.name,\n ),\n );\n } else {\n breadcrumbItems.push(\n createElement(\n 'span',\n {\n key: `item-${index}`,\n className: isLast ? activeClassName : undefined,\n 'aria-current': isLast ? 'page' : undefined,\n },\n item.name,\n ),\n );\n }\n });\n\n children.push(\n createElement(\n 'nav',\n { 'aria-label': 'Breadcrumb', className },\n createElement(\n 'ol',\n {\n style: {\n listStyle: 'none',\n padding: 0,\n margin: 0,\n display: 'flex',\n flexWrap: 'wrap' as const,\n },\n },\n ...breadcrumbItems,\n ),\n ),\n );\n\n // Render JSON-LD\n if (includeJsonLd) {\n children.push(\n createElement('script', {\n key: 'breadcrumb-jsonld',\n type: 'application/ld+json',\n dangerouslySetInnerHTML: { __html: JSON.stringify(jsonLdData) },\n }),\n );\n }\n\n return createElement(Fragment, null, ...children);\n}\n"]} | ||
| {"version":3,"sources":["../src/context.ts","../src/components/DefaultSEO.ts","../src/head-tags.ts","../src/components/SEO.ts","../src/components/OpenGraph.ts","../src/components/TwitterCard.ts","../src/components/Canonical.ts","../src/components/Robots.ts","../src/components/Hreflang.ts","../src/components/Breadcrumb.ts"],"sourcesContent":["'use client';\n// ============================================================================\n// @power-seo/react — SEO Context for Default Configuration\n// ============================================================================\n\nimport { createContext, useContext } from 'react';\nimport type { SEOConfig } from '@power-seo/core';\n\nexport const SEOContext = createContext<SEOConfig | null>(null);\n\n/**\n * Hook to access the default SEO configuration from the nearest DefaultSEO provider.\n */\nexport function useDefaultSEO(): SEOConfig | null {\n return useContext(SEOContext);\n}\n","'use client';\n// ============================================================================\n// @power-seo/react — <DefaultSEO> Component\n// ============================================================================\n\nimport { createElement } from 'react';\nimport type { ReactNode } from 'react';\nimport type { SEOConfig } from '@power-seo/core';\nimport { buildMetaTags, buildLinkTags, resolveTitle } from '@power-seo/core';\nimport { SEOContext } from '../context.js';\nimport { renderMetaTags, renderLinkTags } from '../head-tags.js';\n\nexport interface DefaultSEOProps extends SEOConfig {\n children?: ReactNode;\n}\n\n/**\n * Provide global default SEO configuration.\n * Renders default meta tags and wraps children with SEO context.\n *\n * @example\n * ```tsx\n * <DefaultSEO\n * titleTemplate=\"%s | My Site\"\n * defaultTitle=\"My Site\"\n * description=\"Default description\"\n * openGraph={{\n * type: 'website',\n * siteName: 'My Site',\n * }}\n * >\n * <App />\n * </DefaultSEO>\n * ```\n */\nexport function DefaultSEO({ children, ...config }: DefaultSEOProps) {\n const title = resolveTitle(config);\n const metaTags = buildMetaTags(config);\n const linkTags = buildLinkTags(config);\n\n return createElement(\n SEOContext.Provider,\n { value: config },\n title ? createElement('title', null, title) : null,\n renderMetaTags(metaTags),\n renderLinkTags(linkTags),\n children,\n );\n}\n","// ============================================================================\n// @power-seo/react — Head Tag Rendering (React 19 native + fallback)\n// ============================================================================\n\nimport { createElement, Fragment } from 'react';\nimport type { MetaTag, LinkTag } from '@power-seo/core';\n\n/**\n * Render meta tags as React elements.\n * In React 19, these automatically hoist to <head>.\n * In React 18, wrap with a Helmet provider or use a framework's Head component.\n */\nexport function renderMetaTags(tags: MetaTag[]) {\n return createElement(\n Fragment,\n null,\n ...tags.map((tag, i) => {\n const props: Record<string, string> = { content: tag.content };\n if (tag.name) props.name = tag.name;\n if (tag.property) props.property = tag.property;\n if (tag.httpEquiv) props.httpEquiv = tag.httpEquiv;\n props.key = `meta-${tag.name ?? tag.property ?? tag.httpEquiv ?? i}`;\n return createElement('meta', props);\n }),\n );\n}\n\n/**\n * Render link tags as React elements.\n */\nexport function renderLinkTags(tags: LinkTag[]) {\n return createElement(\n Fragment,\n null,\n ...tags.map((tag, i) => {\n const props: Record<string, string | undefined> = {\n rel: tag.rel,\n href: tag.href,\n key: `link-${tag.rel}-${tag.hreflang ?? i}`,\n };\n if (tag.hreflang) props.hrefLang = tag.hreflang;\n if (tag.type) props.type = tag.type;\n if (tag.sizes) props.sizes = tag.sizes;\n if (tag.media) props.media = tag.media;\n if (tag.as) props.as = tag.as;\n if (tag.crossOrigin) props.crossOrigin = tag.crossOrigin;\n return createElement('link', props);\n }),\n );\n}\n","'use client';\n// ============================================================================\n// @power-seo/react — <SEO> Component\n// ============================================================================\n\nimport { createElement, Fragment } from 'react';\nimport type { SEOConfig } from '@power-seo/core';\nimport { buildMetaTags, buildLinkTags, resolveTitle } from '@power-seo/core';\nimport { useDefaultSEO } from '../context.js';\nimport { renderMetaTags, renderLinkTags } from '../head-tags.js';\n\nexport type SEOProps = SEOConfig;\n\n/**\n * All-in-one SEO component for per-page meta tag management.\n * Merges with DefaultSEO context if available.\n *\n * In React 19, <title>, <meta>, and <link> tags automatically hoist to <head>.\n * In React 18, use with a Helmet provider or framework Head component.\n *\n * @example\n * ```tsx\n * <SEO\n * title=\"About Us\"\n * description=\"Learn about our company\"\n * canonical=\"https://example.com/about\"\n * openGraph={{\n * title: 'About Us',\n * description: 'Learn about our company',\n * images: [{ url: 'https://example.com/about-og.jpg', width: 1200, height: 630 }],\n * }}\n * twitter={{\n * cardType: 'summary_large_image',\n * }}\n * />\n * ```\n */\nexport function SEO(props: SEOProps) {\n const defaults = useDefaultSEO();\n\n // Merge page config with defaults\n const config: SEOConfig = {\n ...defaults,\n ...props,\n // Deep merge for nested objects\n openGraph: {\n ...defaults?.openGraph,\n ...props.openGraph,\n images: props.openGraph?.images ?? defaults?.openGraph?.images,\n videos: props.openGraph?.videos ?? defaults?.openGraph?.videos,\n },\n twitter: {\n ...defaults?.twitter,\n ...props.twitter,\n },\n additionalMetaTags: [\n ...(defaults?.additionalMetaTags ?? []),\n ...(props.additionalMetaTags ?? []),\n ],\n additionalLinkTags: [\n ...(defaults?.additionalLinkTags ?? []),\n ...(props.additionalLinkTags ?? []),\n ],\n languageAlternates: props.languageAlternates ?? defaults?.languageAlternates,\n // Use page-specific title template or default\n titleTemplate: props.titleTemplate ?? defaults?.titleTemplate,\n };\n\n const title = resolveTitle(config);\n const metaTags = buildMetaTags(config);\n const linkTags = buildLinkTags(config);\n\n return createElement(\n Fragment,\n null,\n title ? createElement('title', null, title) : null,\n renderMetaTags(metaTags),\n renderLinkTags(linkTags),\n );\n}\n","// ============================================================================\n// @power-seo/react — <OpenGraph> Component\n// ============================================================================\n\nimport { createElement, Fragment } from 'react';\nimport type { OpenGraphConfig } from '@power-seo/core';\nimport { buildOpenGraphTags } from '@power-seo/core';\nimport { renderMetaTags } from '../head-tags.js';\n\nexport type OpenGraphProps = OpenGraphConfig;\n\n/**\n * Render Open Graph meta tags.\n *\n * @example\n * ```tsx\n * <OpenGraph\n * type=\"article\"\n * title=\"My Article\"\n * description=\"Article description\"\n * url=\"https://example.com/article\"\n * images={[{\n * url: 'https://example.com/og.jpg',\n * width: 1200,\n * height: 630,\n * alt: 'Article image',\n * }]}\n * article={{\n * publishedTime: '2025-01-01',\n * authors: ['https://example.com/author'],\n * tags: ['react', 'seo'],\n * }}\n * />\n * ```\n */\nexport function OpenGraph(props: OpenGraphProps) {\n const tags = buildOpenGraphTags(props);\n return createElement(Fragment, null, renderMetaTags(tags));\n}\n","// ============================================================================\n// @power-seo/react — <TwitterCard> Component\n// ============================================================================\n\nimport { createElement, Fragment } from 'react';\nimport type { TwitterCardConfig } from '@power-seo/core';\nimport { buildTwitterTags } from '@power-seo/core';\nimport { renderMetaTags } from '../head-tags.js';\n\nexport type TwitterCardProps = TwitterCardConfig;\n\n/**\n * Render Twitter Card meta tags.\n *\n * @example\n * ```tsx\n * <TwitterCard\n * cardType=\"summary_large_image\"\n * site=\"@mysite\"\n * creator=\"@author\"\n * title=\"My Article\"\n * description=\"Article description\"\n * image=\"https://example.com/twitter.jpg\"\n * imageAlt=\"Twitter card image\"\n * />\n * ```\n */\nexport function TwitterCard(props: TwitterCardProps) {\n const tags = buildTwitterTags(props);\n return createElement(Fragment, null, renderMetaTags(tags));\n}\n","// ============================================================================\n// @power-seo/react — <Canonical> Component\n// ============================================================================\n\nimport { createElement } from 'react';\nimport { resolveCanonical } from '@power-seo/core';\n\nexport interface CanonicalProps {\n /** The canonical URL (absolute or relative to baseUrl) */\n url: string;\n /** Base URL for resolving relative paths */\n baseUrl?: string;\n /** Whether to add trailing slash (default: false) */\n trailingSlash?: boolean;\n}\n\n/**\n * Render a canonical link tag.\n *\n * @example\n * ```tsx\n * <Canonical url=\"https://example.com/blog/post\" />\n * // or with base URL:\n * <Canonical url=\"/blog/post\" baseUrl=\"https://example.com\" />\n * ```\n */\nexport function Canonical({ url, baseUrl, trailingSlash = false }: CanonicalProps) {\n let canonical = baseUrl ? resolveCanonical(baseUrl, url) : url;\n\n if (trailingSlash && !canonical.endsWith('/')) {\n canonical += '/';\n } else if (\n !trailingSlash &&\n canonical.endsWith('/') &&\n canonical !== url.replace(/\\/$/, '') + '/'\n ) {\n // Only strip trailing slash if it was explicitly added\n }\n\n return createElement('link', {\n rel: 'canonical',\n href: canonical,\n });\n}\n","// ============================================================================\n// @power-seo/react — <Robots> Component\n// ============================================================================\n\nimport { createElement } from 'react';\nimport type { RobotsDirective } from '@power-seo/core';\nimport { buildRobotsContent } from '@power-seo/core';\n\nexport type RobotsProps = RobotsDirective;\n\n/**\n * Render a robots meta tag with per-page directives.\n *\n * @example\n * ```tsx\n * <Robots index={false} follow={true} maxSnippet={150} />\n * // Renders: <meta name=\"robots\" content=\"noindex, follow, max-snippet:150\" />\n * ```\n */\nexport function Robots(props: RobotsProps) {\n const content = buildRobotsContent(props);\n if (!content) return null;\n return createElement('meta', { name: 'robots', content });\n}\n","// ============================================================================\n// @power-seo/react — <Hreflang> Component\n// ============================================================================\n\nimport { createElement, Fragment } from 'react';\nimport type { HreflangConfig } from '@power-seo/core';\n\nexport interface HreflangProps {\n /** Language alternates */\n alternates: HreflangConfig[];\n /** Whether to include x-default (usually same as default language) */\n xDefault?: string;\n}\n\n/**\n * Render hreflang link tags for multi-language pages.\n *\n * @example\n * ```tsx\n * <Hreflang\n * alternates={[\n * { hrefLang: 'en', href: 'https://example.com/en/page' },\n * { hrefLang: 'fr', href: 'https://example.com/fr/page' },\n * { hrefLang: 'de', href: 'https://example.com/de/page' },\n * ]}\n * xDefault=\"https://example.com/en/page\"\n * />\n * ```\n */\nexport function Hreflang({ alternates, xDefault }: HreflangProps) {\n const links = alternates.map((alt) =>\n createElement('link', {\n key: `hreflang-${alt.hrefLang}`,\n rel: 'alternate',\n hrefLang: alt.hrefLang,\n href: alt.href,\n }),\n );\n\n if (xDefault) {\n links.push(\n createElement('link', {\n key: 'hreflang-x-default',\n rel: 'alternate',\n hrefLang: 'x-default',\n href: xDefault,\n }),\n );\n }\n\n return createElement(Fragment, null, ...links);\n}\n","// ============================================================================\n// @power-seo/react — <Breadcrumb> Component\n// ============================================================================\n\nimport { createElement, Fragment } from 'react';\n\nexport interface BreadcrumbItem {\n name: string;\n url?: string;\n}\n\nexport interface BreadcrumbProps {\n /** Breadcrumb items from root to current page */\n items: BreadcrumbItem[];\n /** Separator between items (default: \" / \") */\n separator?: string;\n /** CSS class for the nav element */\n className?: string;\n /** CSS class for each link */\n linkClassName?: string;\n /** CSS class for the current (last) item */\n activeClassName?: string;\n /** Whether to render the JSON-LD alongside the visual breadcrumb (default: true) */\n includeJsonLd?: boolean;\n}\n\n/**\n * Visual breadcrumb navigation with optional BreadcrumbList JSON-LD.\n *\n * @example\n * ```tsx\n * <Breadcrumb\n * items={[\n * { name: 'Home', url: '/' },\n * { name: 'Blog', url: '/blog' },\n * { name: 'Current Post' },\n * ]}\n * />\n * ```\n */\nexport function Breadcrumb({\n items,\n separator = ' / ',\n className,\n linkClassName,\n activeClassName,\n includeJsonLd = true,\n}: BreadcrumbProps) {\n // Build JSON-LD schema\n const jsonLdData = {\n '@context': 'https://schema.org' as const,\n '@type': 'BreadcrumbList' as const,\n itemListElement: items.map((item, index) => ({\n '@type': 'ListItem' as const,\n position: index + 1,\n name: item.name,\n ...(item.url ? { item: item.url } : {}),\n })),\n };\n\n const children: ReturnType<typeof createElement>[] = [];\n\n // Render visual breadcrumb\n const breadcrumbItems: ReturnType<typeof createElement>[] = [];\n items.forEach((item, index) => {\n if (index > 0) {\n breadcrumbItems.push(\n createElement('span', { key: `sep-${index}`, 'aria-hidden': 'true' }, separator),\n );\n }\n\n const isLast = index === items.length - 1;\n\n if (item.url && !isLast) {\n breadcrumbItems.push(\n createElement(\n 'a',\n { key: `item-${index}`, href: item.url, className: linkClassName },\n item.name,\n ),\n );\n } else {\n breadcrumbItems.push(\n createElement(\n 'span',\n {\n key: `item-${index}`,\n className: isLast ? activeClassName : undefined,\n 'aria-current': isLast ? 'page' : undefined,\n },\n item.name,\n ),\n );\n }\n });\n\n children.push(\n createElement(\n 'nav',\n { 'aria-label': 'Breadcrumb', className },\n createElement(\n 'ol',\n {\n style: {\n listStyle: 'none',\n padding: 0,\n margin: 0,\n display: 'flex',\n flexWrap: 'wrap' as const,\n },\n },\n ...breadcrumbItems,\n ),\n ),\n );\n\n // Render JSON-LD\n if (includeJsonLd) {\n children.push(\n createElement('script', {\n key: 'breadcrumb-jsonld',\n type: 'application/ld+json',\n dangerouslySetInnerHTML: { __html: JSON.stringify(jsonLdData) },\n }),\n );\n }\n\n return createElement(Fragment, null, ...children);\n}\n"],"mappings":"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","names":["createElement","createElement","createElement","Fragment","buildMetaTags","buildLinkTags","resolveTitle","resolveTitle","buildMetaTags","buildLinkTags","createElement","Fragment","createElement","Fragment","createElement","Fragment","createElement","Fragment","createElement","Fragment","createElement","createElement","createElement","Fragment","createElement","Fragment"]} |
+11
-3
| { | ||
| "name": "@power-seo/react", | ||
| "version": "1.0.4", | ||
| "version": "1.0.5", | ||
| "description": "Framework-agnostic React SEO components — meta tags, Open Graph, Twitter Card, breadcrumbs, and more", | ||
@@ -39,3 +39,3 @@ "license": "MIT", | ||
| "@testing-library/react": "^16.0.0", | ||
| "@types/react": "^19.0.0", | ||
| "@types/react": "^19.2.14", | ||
| "@types/react-dom": "^19.0.0", | ||
@@ -45,3 +45,3 @@ "jsdom": "^25.0.0", | ||
| "react-dom": "^19.0.0", | ||
| "rimraf": "^6.0.0", | ||
| "rimraf": "^6.1.3", | ||
| "tsup": "^8.3.0", | ||
@@ -68,3 +68,11 @@ "typescript": "^5.7.0", | ||
| "access": "public" | ||
| }, | ||
| "bugs": { | ||
| "url": "https://github.com/CyberCraftBD/power-seo/issues" | ||
| }, | ||
| "homepage": "https://github.com/CyberCraftBD/power-seo/tree/main/packages/react#readme", | ||
| "funding": { | ||
| "type": "github", | ||
| "url": "https://github.com/sponsors/cybercraftbd" | ||
| } | ||
| } |
+367
-244
@@ -1,5 +0,7 @@ | ||
| # @power-seo/react — React SEO Components for Meta Tags, Open Graph, Twitter Card, Robots & Breadcrumbs | ||
| # @power-seo/react | ||
| [](https://www.npmjs.com/package/@power-seo/react) | ||
| [](https://www.npmjs.com/package/@power-seo/react) | ||
| [](https://socket.dev/npm/package/@power-seo/react) | ||
| [](https://github.com/CyberCraftBD/power-seo/actions) | ||
| [](https://opensource.org/licenses/MIT) | ||
@@ -10,53 +12,35 @@ [](https://www.typescriptlang.org/) | ||
| --- | ||
| Declarative React components for SEO meta tag management — title templates, Open Graph, Twitter Cards, canonical URLs, robots directives, hreflang, and breadcrumbs with JSON-LD, all from a single composable API that renders directly to the DOM. | ||
| ## Overview | ||
| > **Zero third-party dependencies** — only `react` and `@power-seo/core` as peers. | ||
| **@power-seo/react** is a complete set of declarative React components for SEO meta tag management for Next.js Pages Router, Vite, Gatsby, and any React application that renders to the DOM, helping you manage titles, Open Graph, Twitter Cards, canonical URLs, robots directives, hreflang, and breadcrumbs from a single composable API. | ||
| **What it does** | ||
| - ✅ **All-in-one `<SEO>` component** — renders title, meta description, canonical, robots, Open Graph, and Twitter Card from one component | ||
| - ✅ **Context-based defaults** — `<DefaultSEO>` at app root sets site-wide defaults; pages override selectively | ||
| - ✅ **Full robots directive support** — all 10 directives including `noindex`, `noarchive`, `max-snippet`, `max-image-preview`, `unavailable_after` | ||
| - ✅ **Hreflang internationalization** — `<Hreflang>` renders `<link rel="alternate">` tags for multi-language sites | ||
| - ✅ **Breadcrumbs with JSON-LD** — `<Breadcrumb>` renders visible nav plus embedded BreadcrumbList structured data | ||
| **What it is not** | ||
| - ❌ **Not an App Router solution** — use `@power-seo/meta` for Next.js App Router `generateMetadata()` | ||
| - ❌ **Not server-side only** — requires `react-helmet-async` under the hood; targets DOM-rendering React apps | ||
| **Recommended for** | ||
| - **Next.js Pages Router apps**, **Vite + React SPAs**, **Gatsby sites**, and **Create React App projects** | ||
| --- | ||
| ## Why @power-seo/react Matters | ||
| ## Why @power-seo/react? | ||
| **The problem** | ||
| | | Without | With | | ||
| | ----------------- | -------------------------------------- | ------------------------------------------------------ | | ||
| | Title management | ❌ Ad-hoc `<title>` tags per page | ✅ `<DefaultSEO>` enforces site-wide title template | | ||
| | Open Graph | ❌ Missing or inconsistent `og:*` tags | ✅ Typed `<OpenGraph>` and `<SEO openGraph={...}>` | | ||
| | Twitter Cards | ❌ Hand-coded `twitter:*` strings | ✅ Typed `<TwitterCard>` with all card types | | ||
| | Robots directives | ❌ Raw content strings with typos | ✅ Boolean and enum props — no raw string errors | | ||
| | Canonical URLs | ❌ Omitted or duplicated | ✅ `<Canonical>` and `<SEO canonical={...}>` | | ||
| | Hreflang | ❌ Manual `<link>` tags per locale | ✅ `<Hreflang>` renders all alternates + x-default | | ||
| | Breadcrumbs | ❌ HTML nav only, no structured data | ✅ `<Breadcrumb>` renders nav + BreadcrumbList JSON-LD | | ||
| | Framework support | ❌ Locked to next-seo or react-helmet | ✅ Next.js Pages Router, Vite, Gatsby, React 18/19 | | ||
| - **Scattered meta tag management** — teams add `<Helmet>` blocks inconsistently across hundreds of components | ||
| - **Missing Open Graph images** — no single source of truth for default OG images leads to bare link previews | ||
| - **Incorrect robots directives** — hand-crafted `content` strings omit valid directives or include typos | ||
| **Why developers care** | ||
| - **SEO:** Consistent title templates and canonical URLs prevent duplicate content penalties | ||
| - **UX:** Correct OG and Twitter Card images dramatically increase social click-through | ||
| - **Performance:** Centralized `<DefaultSEO>` prevents redundant meta tag renders on every page | ||
| --- | ||
| ## Key Features | ||
| ## Features | ||
| - **`<SEO>` all-in-one component** — renders title, meta description, canonical, robots, Open Graph, and Twitter Card tags from a single component | ||
| - **`<DefaultSEO>` context-based defaults** — set site-wide title template, default OG image, and global robots directives; individual pages override selectively | ||
| - **`<Robots>` full directive support** — all 10 robots directives including `noindex`, `nofollow`, `noarchive`, `nosnippet`, `noimageindex`, `notranslate`, `max-snippet`, `max-image-preview`, `max-video-preview`, `unavailable_after` | ||
| - **`<SEO>` all-in-one component** — renders title, meta description, canonical, robots, Open Graph, and Twitter Card from a single component | ||
| - **`<DefaultSEO>` context-based defaults** — set site-wide title template, default OG image, and global robots directives at the app root; pages override selectively | ||
| - **`<Robots>` full directive support** — all 10 directives including `noindex`, `nofollow`, `noarchive`, `nosnippet`, `noimageindex`, `notranslate`, `max-snippet`, `max-image-preview`, `max-video-preview`, `unavailable_after` | ||
| - **`<OpenGraph>` all OG properties** — `og:title`, `og:description`, `og:type`, `og:url`, `og:image` (with width/height/alt), `og:site_name`, `og:locale`, `og:article:*` properties | ||
| - **`<TwitterCard>` all card types** — `summary`, `summary_large_image`, `app`, `player`; with site, creator, title, description, image | ||
| - **`<Canonical>` link tag** — renders `<link rel="canonical">` with proper href | ||
| - **`<Canonical>` link tag** — renders `<link rel="canonical">` with proper href; supports base URL resolution and trailing slash control | ||
| - **`<Hreflang>` i18n alternate links** — renders `<link rel="alternate" hreflang="...">` tags for multi-language sites including `x-default` | ||
| - **`<Breadcrumb>` with JSON-LD** — renders visible breadcrumb navigation plus embedded `application/ld+json` BreadcrumbList | ||
| - **`<Breadcrumb>` with JSON-LD** — renders visible breadcrumb navigation plus embedded `application/ld+json` BreadcrumbList structured data | ||
| - **`renderMetaTags` / `renderLinkTags` utilities** — convert `@power-seo/core` tag arrays into React elements | ||
| - **React 19 native hoisting** — `<title>`, `<meta>`, and `<link>` tags hoist to `<head>` automatically in React 19; works with framework Head components in React 18 | ||
| - **TypeScript-first** — full `.d.ts` declarations, all props fully typed | ||
@@ -67,15 +51,39 @@ - **Tree-shakeable** — import only the components you use | ||
| ## Benefits of Using @power-seo/react | ||
| ## Comparison | ||
| - **Improved consistency**: `<DefaultSEO>` enforces site-wide title templates and OG defaults with zero duplication | ||
| - **Better social visibility**: Correct `<TwitterCard>` and `<OpenGraph>` images increase social click-through rates | ||
| - **Safer SEO implementation**: Typed robots directive props prevent typos that could accidentally noindex pages | ||
| - **Faster delivery**: Drop-in components eliminate boilerplate meta tag code from every page component | ||
| | Feature | @power-seo/react | next-seo | react-helmet | react-helmet-async | | ||
| | ----------------------------------- | :--------------: | :------: | :----------: | :----------------: | | ||
| | Typed robots directives | ✅ | ✅ | ❌ | ❌ | | ||
| | DefaultSEO context pattern | ✅ | ✅ | ❌ | ❌ | | ||
| | Hreflang support | ✅ | ✅ | ❌ | ❌ | | ||
| | Breadcrumb with JSON-LD | ✅ | ✅ | ❌ | ❌ | | ||
| | `max-snippet` / `max-image-preview` | ✅ | ✅ | ❌ | ❌ | | ||
| | No third-party runtime deps | ✅ | ❌ | ❌ | ❌ | | ||
| | React 19 native head hoisting | ✅ | ❌ | ❌ | ❌ | | ||
| | TypeScript-first API | ✅ | ✅ | ⚠️ | ⚠️ | | ||
| | Tree-shakeable | ✅ | Partial | ❌ | ❌ | | ||
| | Works in Next.js Pages Router | ✅ | ✅ | ✅ | ✅ | | ||
| | Works in Vite / Gatsby / CRA | ✅ | ❌ | ✅ | ✅ | | ||
| --- | ||
| ## Installation | ||
| ```bash | ||
| npm install @power-seo/react @power-seo/core | ||
| ``` | ||
| ```bash | ||
| yarn add @power-seo/react @power-seo/core | ||
| ``` | ||
| ```bash | ||
| pnpm add @power-seo/react @power-seo/core | ||
| ``` | ||
| --- | ||
| ## Quick Start | ||
| ```tsx | ||
| import { HelmetProvider } from 'react-helmet-async'; | ||
| import { DefaultSEO, SEO } from '@power-seo/react'; | ||
@@ -85,14 +93,13 @@ | ||
| return ( | ||
| <HelmetProvider> | ||
| <DefaultSEO | ||
| titleTemplate="%s | My Site" | ||
| defaultTitle="My Site" | ||
| description="The best site on the internet." | ||
| openGraph={{ type: 'website', siteName: 'My Site' }} | ||
| twitter={{ site: '@mysite', cardType: 'summary_large_image' }} | ||
| /> | ||
| <DefaultSEO | ||
| titleTemplate="%s | My Site" | ||
| defaultTitle="My Site" | ||
| description="The best site on the internet." | ||
| openGraph={{ type: 'website', siteName: 'My Site' }} | ||
| twitter={{ site: '@mysite', cardType: 'summary_large_image' }} | ||
| > | ||
| <Router> | ||
| <Routes /> | ||
| </Router> | ||
| </HelmetProvider> | ||
| </DefaultSEO> | ||
| ); | ||
@@ -119,5 +126,5 @@ } | ||
| **What you should see** | ||
| **What you get in the DOM `<head>`:** | ||
| - `<title>My Post Title | My Site</title>` in the DOM `<head>` | ||
| - `<title>My Post Title | My Site</title>` | ||
| - Correct `og:image`, `twitter:card`, and `link rel="canonical"` tags on every page | ||
@@ -127,180 +134,211 @@ | ||
| ## Installation | ||
| ## Usage | ||
| ```bash | ||
| npm i @power-seo/react | ||
| # or | ||
| yarn add @power-seo/react | ||
| # or | ||
| pnpm add @power-seo/react | ||
| # or | ||
| bun add @power-seo/react | ||
| ``` | ||
| ### Site-Wide Defaults with `<DefaultSEO>` | ||
| **Peer dependencies:** | ||
| Place `<DefaultSEO>` once at your app root. It stores the config in React context so every nested `<SEO>` component can merge against it. | ||
| ```bash | ||
| npm install react react-dom react-helmet-async | ||
| ```tsx | ||
| import { DefaultSEO } from '@power-seo/react'; | ||
| function App({ children }) { | ||
| return ( | ||
| <DefaultSEO | ||
| titleTemplate="%s | Acme Corp" | ||
| defaultTitle="Acme Corp" | ||
| description="Enterprise software built for scale." | ||
| openGraph={{ | ||
| type: 'website', | ||
| siteName: 'Acme Corp', | ||
| images: [{ url: 'https://acme.com/og-default.jpg', width: 1200, height: 630 }], | ||
| }} | ||
| twitter={{ site: '@acmecorp', cardType: 'summary_large_image' }} | ||
| > | ||
| {children} | ||
| </DefaultSEO> | ||
| ); | ||
| } | ||
| ``` | ||
| > Note: Wrap your app in `<HelmetProvider>` from `react-helmet-async`. | ||
| ### Per-Page SEO with `<SEO>` | ||
| --- | ||
| `<SEO>` merges the page-level config with the `<DefaultSEO>` context. Only provide the props that differ from the site defaults. | ||
| ## Framework Compatibility | ||
| ```tsx | ||
| import { SEO } from '@power-seo/react'; | ||
| **Supported** | ||
| function ProductPage({ product }) { | ||
| return ( | ||
| <> | ||
| <SEO | ||
| title={product.name} | ||
| description={product.summary} | ||
| canonical={`https://acme.com/products/${product.slug}`} | ||
| openGraph={{ | ||
| type: 'website', | ||
| images: [{ url: product.image, width: 1200, height: 630, alt: product.name }], | ||
| }} | ||
| /> | ||
| <main>{/* page content */}</main> | ||
| </> | ||
| ); | ||
| } | ||
| ``` | ||
| - ✅ Next.js Pages Router — works with `_app.tsx` and per-page `<SEO>` components | ||
| - ✅ Vite + React — works in standard SPA setup | ||
| - ✅ Gatsby — works with `gatsby-plugin-react-helmet` or standalone | ||
| - ✅ Create React App — works out of the box | ||
| ### Robots Directives | ||
| **Environment notes** | ||
| Use the `<Robots>` component directly, or pass a `robots` object to `<SEO>`. All 10 standard directives are supported via typed props. | ||
| - **SSR/SSG:** Supported via `react-helmet-async` (SSR-safe) | ||
| - **Edge runtime:** Not supported (requires DOM/React reconciler) | ||
| - **Next.js App Router:** Not recommended — use `@power-seo/meta` instead for App Router | ||
| ```tsx | ||
| import { Robots } from '@power-seo/react'; | ||
| --- | ||
| // Noindex a staging page | ||
| <Robots index={false} follow={true} /> | ||
| // → <meta name="robots" content="noindex, follow" /> | ||
| ## Use Cases | ||
| // Advanced directives | ||
| <Robots | ||
| index={true} | ||
| follow={true} | ||
| maxSnippet={150} | ||
| maxImagePreview="large" | ||
| unavailableAfter="2026-12-31T00:00:00Z" | ||
| /> | ||
| // → <meta name="robots" content="index, follow, max-snippet:150, max-image-preview:large, unavailable_after:2026-12-31T00:00:00Z" /> | ||
| ``` | ||
| - **Next.js Pages Router sites** — per-page SEO with site-wide defaults | ||
| - **SaaS marketing sites** — consistent OG cards for every landing page | ||
| - **Blog platforms** — article Open Graph with `og:article:publishedTime` and author data | ||
| - **E-commerce listings** — product pages with correct canonical and Twitter Cards | ||
| - **Multi-language sites** — hreflang alternate link management across locale variants | ||
| - **Staging environments** — easily `noindex` entire environments with a single `<DefaultSEO robots={{ index: false }} />` | ||
| - **Breadcrumb navigation** — visible breadcrumbs with embedded JSON-LD for Google rich results | ||
| Use `buildRobotsContent` from `@power-seo/core` if you need the raw robots string outside a component: | ||
| --- | ||
| ```ts | ||
| import { buildRobotsContent } from '@power-seo/core'; | ||
| ## Example (Before / After) | ||
| ```text | ||
| Before: | ||
| - Each page has a different <Helmet> implementation with inconsistent OG formats | ||
| - Some pages missing canonical, some missing twitter:card | ||
| - Robots directives hand-coded as raw strings with typos | ||
| After (@power-seo/react): | ||
| - <DefaultSEO> enforces site-wide title template and default OG image | ||
| - <SEO> on each page overrides only what differs — canonical, OG image, article properties | ||
| - <Robots> accepts typed boolean props — no raw content string typos | ||
| buildRobotsContent({ index: false, follow: true, maxSnippet: 150 }); | ||
| // → "noindex, follow, max-snippet:150" | ||
| ``` | ||
| --- | ||
| ### Open Graph Tags | ||
| ## Implementation Best Practices | ||
| Use `<OpenGraph>` for standalone OG tag rendering, or pass `openGraph` to `<SEO>`. | ||
| - **Always use `<HelmetProvider>`** at the root — required for SSR and correct head management | ||
| - **Set `<DefaultSEO>` once** at the app root — every page inherits defaults without re-declaring | ||
| - **Use `noindex={true}` on `<SEO>`** for staging, admin, and private pages — not in `next.config.js` redirects | ||
| - **Always set `canonical`** on pages with query parameters to prevent duplicate content | ||
| - **Include `x-default` in `<Hreflang>`** to signal the default language for international visitors | ||
| ```tsx | ||
| import { OpenGraph } from '@power-seo/react'; | ||
| --- | ||
| <OpenGraph | ||
| type="article" | ||
| title="How to Build a React SEO Pipeline" | ||
| description="A step-by-step guide to SEO in React applications." | ||
| url="https://example.com/blog/react-seo" | ||
| images={[ | ||
| { url: 'https://example.com/react-seo-og.jpg', width: 1200, height: 630, alt: 'React SEO' }, | ||
| ]} | ||
| article={{ | ||
| publishedTime: '2026-01-15T00:00:00Z', | ||
| authors: ['https://example.com/author/jane'], | ||
| tags: ['react', 'seo', 'typescript'], | ||
| }} | ||
| />; | ||
| ``` | ||
| ## Architecture Overview | ||
| ### Twitter Cards | ||
| **Where it runs** | ||
| Use `<TwitterCard>` for standalone Twitter/X card tag rendering, or pass `twitter` to `<SEO>`. | ||
| - **Client-side (CSR):** `react-helmet-async` manages `<head>` updates on navigation | ||
| - **Server-side (SSR):** `HelmetProvider` collects head tags during render and serializes them into the initial HTML | ||
| - **CI/CD:** No direct CI integration — audit head tags with `@power-seo/audit` | ||
| ```tsx | ||
| import { TwitterCard } from '@power-seo/react'; | ||
| **Data flow** | ||
| <TwitterCard | ||
| cardType="summary_large_image" | ||
| site="@mysite" | ||
| creator="@author" | ||
| title="How to Build a React SEO Pipeline" | ||
| description="A step-by-step guide to SEO in React applications." | ||
| image="https://example.com/twitter-card.jpg" | ||
| imageAlt="React SEO guide" | ||
| />; | ||
| ``` | ||
| 1. **Input**: Page-level title, description, OG config, robots directive props | ||
| 2. **Analysis**: `react-helmet-async` merges `<DefaultSEO>` defaults with `<SEO>` overrides | ||
| 3. **Output**: Rendered `<head>` tags: `<title>`, `<meta>`, `<link>`, `<script>` (JSON-LD for breadcrumbs) | ||
| 4. **Action**: Correct head tags for crawlers, social scrapers, and browser tab display | ||
| ### Canonical URL | ||
| --- | ||
| ```tsx | ||
| import { Canonical } from '@power-seo/react'; | ||
| ## Features Comparison with Popular Packages | ||
| // Absolute URL | ||
| <Canonical url="https://example.com/blog/react-seo" /> | ||
| | Capability | next-seo | react-helmet | react-helmet-async | @power-seo/react | | ||
| | ----------------------------------- | -------: | -----------: | -----------------: | ---------------: | | ||
| | Typed robots directives | ✅ | ❌ | ❌ | ✅ | | ||
| | DefaultSEO context pattern | ✅ | ❌ | ❌ | ✅ | | ||
| | Hreflang support | ✅ | ❌ | ❌ | ✅ | | ||
| | Breadcrumb with JSON-LD | ✅ | ❌ | ❌ | ✅ | | ||
| | `max-snippet` / `max-image-preview` | ✅ | ❌ | ❌ | ✅ | | ||
| | TypeScript-first API | ✅ | ⚠️ | ⚠️ | ✅ | | ||
| // With base URL resolution | ||
| <Canonical url="/blog/react-seo" baseUrl="https://example.com" /> | ||
| ``` | ||
| --- | ||
| ### Hreflang for Multi-Language Sites | ||
| ## [@power-seo](https://www.npmjs.com/org/power-seo) Ecosystem | ||
| ```tsx | ||
| import { Hreflang } from '@power-seo/react'; | ||
| All 17 packages are independently installable — use only what you need. | ||
| <Hreflang | ||
| alternates={[ | ||
| { hrefLang: 'en', href: 'https://example.com/en/page' }, | ||
| { hrefLang: 'fr', href: 'https://example.com/fr/page' }, | ||
| { hrefLang: 'de', href: 'https://example.com/de/page' }, | ||
| ]} | ||
| xDefault="https://example.com/en/page" | ||
| />; | ||
| ``` | ||
| | Package | Install | Description | | ||
| | ------------------------------------------------------------------------------------------ | ----------------------------------- | -------------------------------------------------------------------------- | | ||
| | [`@power-seo/core`](https://www.npmjs.com/package/@power-seo/core) | `npm i @power-seo/core` | Framework-agnostic utilities, types, validators, and constants | | ||
| | [`@power-seo/react`](https://www.npmjs.com/package/@power-seo/react) | `npm i @power-seo/react` | React SEO components — meta, Open Graph, Twitter Card, robots, breadcrumbs | | ||
| | [`@power-seo/meta`](https://www.npmjs.com/package/@power-seo/meta) | `npm i @power-seo/meta` | SSR meta helpers for Next.js App Router, Remix v2, and generic SSR | | ||
| | [`@power-seo/schema`](https://www.npmjs.com/package/@power-seo/schema) | `npm i @power-seo/schema` | Type-safe JSON-LD structured data — 20 builders + 18 React components | | ||
| | [`@power-seo/content-analysis`](https://www.npmjs.com/package/@power-seo/content-analysis) | `npm i @power-seo/content-analysis` | Yoast-style SEO content scoring engine with React components | | ||
| | [`@power-seo/readability`](https://www.npmjs.com/package/@power-seo/readability) | `npm i @power-seo/readability` | Readability scoring — Flesch-Kincaid, Gunning Fog, Coleman-Liau, ARI | | ||
| | [`@power-seo/preview`](https://www.npmjs.com/package/@power-seo/preview) | `npm i @power-seo/preview` | SERP, Open Graph, and Twitter/X Card preview generators | | ||
| | [`@power-seo/sitemap`](https://www.npmjs.com/package/@power-seo/sitemap) | `npm i @power-seo/sitemap` | XML sitemap generation, streaming, index splitting, and validation | | ||
| | [`@power-seo/redirects`](https://www.npmjs.com/package/@power-seo/redirects) | `npm i @power-seo/redirects` | Redirect engine with Next.js, Remix, and Express adapters | | ||
| | [`@power-seo/links`](https://www.npmjs.com/package/@power-seo/links) | `npm i @power-seo/links` | Link graph analysis — orphan detection, suggestions, equity scoring | | ||
| | [`@power-seo/audit`](https://www.npmjs.com/package/@power-seo/audit) | `npm i @power-seo/audit` | Full SEO audit engine — meta, content, structure, performance rules | | ||
| | [`@power-seo/images`](https://www.npmjs.com/package/@power-seo/images) | `npm i @power-seo/images` | Image SEO — alt text, lazy loading, format analysis, image sitemaps | | ||
| | [`@power-seo/ai`](https://www.npmjs.com/package/@power-seo/ai) | `npm i @power-seo/ai` | LLM-agnostic AI prompt templates and parsers for SEO tasks | | ||
| | [`@power-seo/analytics`](https://www.npmjs.com/package/@power-seo/analytics) | `npm i @power-seo/analytics` | Merge GSC + audit data, trend analysis, ranking insights, dashboard | | ||
| | [`@power-seo/search-console`](https://www.npmjs.com/package/@power-seo/search-console) | `npm i @power-seo/search-console` | Google Search Console API — OAuth2, service account, URL inspection | | ||
| | [`@power-seo/integrations`](https://www.npmjs.com/package/@power-seo/integrations) | `npm i @power-seo/integrations` | Semrush and Ahrefs API clients with rate limiting and pagination | | ||
| | [`@power-seo/tracking`](https://www.npmjs.com/package/@power-seo/tracking) | `npm i @power-seo/tracking` | GA4, Clarity, PostHog, Plausible, Fathom — scripts + consent management | | ||
| ### Breadcrumb Navigation with JSON-LD | ||
| ### Ecosystem vs alternatives | ||
| `<Breadcrumb>` renders both the visible `<nav>` element and an embedded `application/ld+json` BreadcrumbList script for Google rich results. | ||
| | Need | Common approach | @power-seo approach | | ||
| | ----------------------- | --------------------------- | ---------------------------------------- | | ||
| | React meta tags | `next-seo` / `react-helmet` | `@power-seo/react` — typed components | | ||
| | App Router metadata | `generateMetadata()` | `@power-seo/meta` — typed helpers | | ||
| | JSON-LD structured data | Manual `<script>` tags | `@power-seo/schema` — typed builders | | ||
| | Sitemap generation | `next-sitemap` | `@power-seo/sitemap` — streaming + index | | ||
| ```tsx | ||
| import { Breadcrumb } from '@power-seo/react'; | ||
| --- | ||
| <Breadcrumb | ||
| items={[{ name: 'Home', url: '/' }, { name: 'Blog', url: '/blog' }, { name: 'React SEO Guide' }]} | ||
| />; | ||
| ``` | ||
| ## Enterprise Integration | ||
| Customize the separator and styling: | ||
| **Multi-tenant SaaS** | ||
| ```tsx | ||
| <Breadcrumb | ||
| items={breadcrumbItems} | ||
| separator=" › " | ||
| className="breadcrumb-nav" | ||
| linkClassName="breadcrumb-link" | ||
| activeClassName="breadcrumb-active" | ||
| includeJsonLd={true} | ||
| /> | ||
| ``` | ||
| - **Tenant-aware title templates**: Pass tenant name into `<DefaultSEO titleTemplate="%s | {tenantName}" />` | ||
| - **Per-tenant OG images**: Different default OG image per tenant via `<DefaultSEO openGraph={{ images: [...] }} />` | ||
| - **Staging noindex**: Wrap entire staging apps in `<DefaultSEO robots={{ index: false }} />` | ||
| ### Noindexing Entire Environments | ||
| **ERP / internal portals** | ||
| ```tsx | ||
| // Noindex all staging pages with a single DefaultSEO prop | ||
| <DefaultSEO | ||
| robots={{ index: false, follow: false }} | ||
| titleTemplate="%s | Staging" | ||
| defaultTitle="Staging" | ||
| > | ||
| {children} | ||
| </DefaultSEO> | ||
| ``` | ||
| - Apply `noindex, nofollow` globally on internal modules | ||
| - Add breadcrumbs with JSON-LD only on public-facing pages | ||
| - Use `<Hreflang>` for internationalized ERP portals with locale-specific URLs | ||
| ### Using `renderMetaTags` and `renderLinkTags` | ||
| **Recommended integration pattern** | ||
| If you generate tag arrays via `@power-seo/core` utilities directly, use these helpers to convert them to React elements: | ||
| - Set `<DefaultSEO>` in `_app.tsx` or root layout | ||
| - Override with `<SEO>` on each page using data fetched server-side | ||
| - Audit head tags in CI using `@power-seo/audit` | ||
| ```tsx | ||
| import { buildMetaTags, buildLinkTags } from '@power-seo/core'; | ||
| import { renderMetaTags, renderLinkTags } from '@power-seo/react'; | ||
| --- | ||
| const metaTags = buildMetaTags({ description: 'My page', noindex: true }); | ||
| const linkTags = buildLinkTags({ canonical: 'https://example.com/page' }); | ||
| ## Scope and Limitations | ||
| return ( | ||
| <> | ||
| {renderMetaTags(metaTags)} | ||
| {renderLinkTags(linkTags)} | ||
| </> | ||
| ); | ||
| ``` | ||
| **This package does** | ||
| - ✅ Render React head tag components for DOM-based React apps | ||
| - ✅ Manage title templates, Open Graph, Twitter Cards, robots, canonical, hreflang | ||
| - ✅ Render breadcrumb navigation with embedded JSON-LD | ||
| **This package does not** | ||
| - ❌ Work with Next.js App Router `generateMetadata()` — use `@power-seo/meta` instead | ||
| - ❌ Generate sitemaps — use `@power-seo/sitemap` | ||
| - ❌ Build JSON-LD schemas beyond breadcrumbs — use `@power-seo/schema` | ||
| --- | ||
@@ -312,29 +350,39 @@ | ||
| | Component | Description | | ||
| | --------------- | -------------------------------------------------------------------------------------- | | ||
| | `<SEO>` | All-in-one per-page SEO component — title, description, canonical, robots, OG, Twitter | | ||
| | `<DefaultSEO>` | App-root defaults — title template, global OG, global Twitter, global robots | | ||
| | `<Robots>` | Renders `<meta name="robots">` with all supported directives | | ||
| | `<OpenGraph>` | Renders Open Graph `og:*` meta tags | | ||
| | `<TwitterCard>` | Renders Twitter Card `twitter:*` meta tags | | ||
| | `<Canonical>` | Renders `<link rel="canonical">` | | ||
| | `<Hreflang>` | Renders `<link rel="alternate" hreflang="...">` tags | | ||
| | `<Breadcrumb>` | Renders breadcrumb nav + embedded BreadcrumbList JSON-LD | | ||
| | Component | Description | | ||
| | --------------- | ------------------------------------------------------------------------------------------------------------- | | ||
| | `<DefaultSEO>` | App-root defaults — title template, global OG, global Twitter, global robots; wraps children with SEO context | | ||
| | `<SEO>` | All-in-one per-page SEO component — title, description, canonical, robots, OG, Twitter | | ||
| | `<Robots>` | Renders `<meta name="robots">` with all supported directives | | ||
| | `<OpenGraph>` | Renders Open Graph `og:*` meta tags | | ||
| | `<TwitterCard>` | Renders Twitter Card `twitter:*` meta tags | | ||
| | `<Canonical>` | Renders `<link rel="canonical">` | | ||
| | `<Hreflang>` | Renders `<link rel="alternate" hreflang="...">` tags | | ||
| | `<Breadcrumb>` | Renders breadcrumb nav + embedded BreadcrumbList JSON-LD | | ||
| ### SEO Props | ||
| | Prop | Type | Default | Description | | ||
| | -------------------- | ----------------- | ------- | --------------------------------------------------------------- | | ||
| | `title` | `string` | — | Page title (applied to title template if DefaultSEO is present) | | ||
| | `description` | `string` | — | Meta description | | ||
| | `canonical` | `string` | — | Canonical URL | | ||
| | `robots` | `RobotsDirective` | — | Robots directive config object | | ||
| | `openGraph` | `OpenGraphConfig` | — | Open Graph configuration | | ||
| | `twitter` | `TwitterConfig` | — | Twitter Card configuration | | ||
| | `noindex` | `boolean` | `false` | Shorthand for `robots.index = false` | | ||
| | `nofollow` | `boolean` | `false` | Shorthand for `robots.follow = false` | | ||
| | `languageAlternates` | `HreflangEntry[]` | — | Hreflang entries for i18n | | ||
| | `additionalMetaTags` | `MetaTag[]` | — | Any additional custom meta tags | | ||
| | `additionalLinkTags` | `LinkTag[]` | — | Any additional custom link tags | | ||
| | Prop | Type | Default | Description | | ||
| | -------------------- | ------------------- | ------- | ------------------------------------------------------------------ | | ||
| | `title` | `string` | — | Page title (applied to title template if `DefaultSEO` is present) | | ||
| | `defaultTitle` | `string` | — | Fallback title when no `title` prop is provided | | ||
| | `titleTemplate` | `string` | — | Template string; `%s` is replaced by `title` (e.g. `"%s \| Site"`) | | ||
| | `description` | `string` | — | Meta description | | ||
| | `canonical` | `string` | — | Canonical URL | | ||
| | `robots` | `RobotsDirective` | — | Robots directive config object | | ||
| | `openGraph` | `OpenGraphConfig` | — | Open Graph configuration | | ||
| | `twitter` | `TwitterCardConfig` | — | Twitter Card configuration | | ||
| | `noindex` | `boolean` | `false` | Shorthand for `robots.index = false` | | ||
| | `nofollow` | `boolean` | `false` | Shorthand for `robots.follow = false` | | ||
| | `languageAlternates` | `HreflangEntry[]` | — | Hreflang entries for i18n | | ||
| | `additionalMetaTags` | `MetaTag[]` | — | Additional custom meta tags | | ||
| | `additionalLinkTags` | `LinkTag[]` | — | Additional custom link tags | | ||
| ### DefaultSEO Props | ||
| `<DefaultSEO>` accepts all `SEO` props plus: | ||
| | Prop | Type | Default | Description | | ||
| | ---------- | ----------- | ------- | -------------------------------------------------- | | ||
| | `children` | `ReactNode` | — | App subtree that inherits the SEO context defaults | | ||
| ### Robots Props | ||
@@ -355,2 +403,17 @@ | ||
| ### Canonical Props | ||
| | Prop | Type | Default | Description | | ||
| | --------------- | --------- | ------- | -------------------------------------------- | | ||
| | `url` | `string` | — | **Required.** The canonical URL | | ||
| | `baseUrl` | `string` | — | Base URL for resolving relative `url` values | | ||
| | `trailingSlash` | `boolean` | `false` | Append trailing slash to the resolved URL | | ||
| ### Hreflang Props | ||
| | Prop | Type | Default | Description | | ||
| | ------------ | ------------------ | ------- | ------------------------------------------------------------------- | | ||
| | `alternates` | `HreflangConfig[]` | — | **Required.** Array of `{ hrefLang: string; href: string }` objects | | ||
| | `xDefault` | `string` | — | URL for the `x-default` alternate link tag | | ||
| ### Breadcrumb Props | ||
@@ -361,52 +424,101 @@ | ||
| | `items` | `BreadcrumbItem[]` | — | **Required.** Array of `{ name: string; url?: string }` objects | | ||
| | `separator` | `string` | `'/'` | Visual separator between breadcrumb items | | ||
| | `separator` | `string` | `' / '` | Visual separator between breadcrumb items | | ||
| | `className` | `string` | — | CSS class for the outer `<nav>` element | | ||
| | `itemClassName` | `string` | — | CSS class for each `<span>` item | | ||
| | `activeClassName` | `string` | — | CSS class for the last (current) item | | ||
| | `renderJsonLd` | `boolean` | `true` | Whether to render the BreadcrumbList JSON-LD script | | ||
| | `linkClassName` | `string` | — | CSS class for each `<a>` link element | | ||
| | `activeClassName` | `string` | — | CSS class for the last (current) item `<span>` | | ||
| | `includeJsonLd` | `boolean` | `true` | Whether to render the BreadcrumbList JSON-LD script | | ||
| ### Utility Functions | ||
| ```ts | ||
| import { buildRobotsContent, parseRobotsContent } from '@power-seo/react'; | ||
| | Function | Signature | Description | | ||
| | ---------------- | ----------------------------------- | ---------------------------------------------------------- | | ||
| | `renderMetaTags` | `(tags: MetaTag[]) => ReactElement` | Converts `@power-seo/core` MetaTag array to React elements | | ||
| | `renderLinkTags` | `(tags: LinkTag[]) => ReactElement` | Converts `@power-seo/core` LinkTag array to React elements | | ||
| buildRobotsContent({ index: false, follow: true, maxSnippet: 150 }); | ||
| // → "noindex, follow, max-snippet:150" | ||
| ``` | ||
| ### Hooks | ||
| | Hook | Signature | Description | | ||
| | --------------- | ------------------------- | ---------------------------------------------------------- | | ||
| | `useDefaultSEO` | `() => SEOConfig \| null` | Returns the current `DefaultSEO` config from React context | | ||
| --- | ||
| ## Contributing | ||
| ## Types | ||
| - Issues: [github.com/cybercraftbd/power-seo/issues](https://github.com/cybercraftbd/power-seo/issues) | ||
| - PRs: [github.com/cybercraftbd/power-seo/pulls](https://github.com/cybercraftbd/power-seo/pulls) | ||
| - Development setup: | ||
| 1. `pnpm i` | ||
| 2. `pnpm build` | ||
| 3. `pnpm test` | ||
| | Type | Description | | ||
| | ------------------ | ------------------------------------------------------------ | | ||
| | `SEOProps` | Alias of `SEOConfig` from `@power-seo/core` | | ||
| | `DefaultSEOProps` | `SEOConfig & { children?: ReactNode }` | | ||
| | `RobotsProps` | Alias of `RobotsDirective` from `@power-seo/core` | | ||
| | `OpenGraphProps` | Alias of `OpenGraphConfig` from `@power-seo/core` | | ||
| | `TwitterCardProps` | Alias of `TwitterCardConfig` from `@power-seo/core` | | ||
| | `CanonicalProps` | `{ url: string; baseUrl?: string; trailingSlash?: boolean }` | | ||
| | `HreflangProps` | `{ alternates: HreflangConfig[]; xDefault?: string }` | | ||
| | `BreadcrumbProps` | See Breadcrumb Props table above | | ||
| | `BreadcrumbItem` | `{ name: string; url?: string }` | | ||
| **Release workflow** | ||
| --- | ||
| - `npm version patch|minor|major` | ||
| - `npm publish --access public` | ||
| ## Use Cases | ||
| - **Next.js Pages Router sites** — per-page SEO with site-wide defaults via `_app.tsx` | ||
| - **Vite + React SPAs** — manage head tags in single-page apps without a full meta framework | ||
| - **Gatsby sites** — declarative SEO components alongside Gatsby's static rendering | ||
| - **SaaS marketing sites** — consistent OG cards and title templates across every landing page | ||
| - **Blog platforms** — article Open Graph with `og:article:publishedTime` and author tags | ||
| - **E-commerce listings** — product canonical URLs and Twitter Cards at scale | ||
| - **Multi-language sites** — hreflang alternate link management across locale variants | ||
| - **Staging environments** — easily `noindex` entire environments with a single `<DefaultSEO robots={{ index: false }} />` | ||
| - **Breadcrumb navigation** — visible breadcrumbs with embedded JSON-LD for Google rich results | ||
| --- | ||
| ## About [CyberCraft Bangladesh](https://ccbd.dev) | ||
| ## Architecture Overview | ||
| **[CyberCraft Bangladesh](https://ccbd.dev)** is a Bangladesh-based enterprise-grade software engineering company specializing in ERP system development, AI-powered SaaS and business applications, full-stack SEO services, custom website development, and scalable eCommerce platforms. We design and develop intelligent, automation-driven SaaS and enterprise solutions that help startups, SMEs, NGOs, educational institutes, and large organizations streamline operations, enhance digital visibility, and accelerate growth through modern cloud-native technologies. | ||
| - **Pure React** — no compiled binary, no native modules, no third-party head management library | ||
| - **Zero runtime dependencies** — only `react` and `@power-seo/core` as peer dependencies | ||
| - **React 19 native hoisting** — `<title>`, `<meta>`, and `<link>` elements hoist to `<head>` automatically in React 19; wrap with a framework Head component in React 18 | ||
| - **Context-based defaults** — `<DefaultSEO>` uses `React.createContext` so defaults flow through the tree without prop drilling | ||
| - **DOM-targeting** — renders directly to the browser DOM; for Next.js App Router use `@power-seo/meta` instead | ||
| - **Tree-shakeable** — `"sideEffects": false` with named exports per component | ||
| - **Dual ESM + CJS** — ships both formats via tsup for any bundler or `require()` usage | ||
| - **Full TypeScript** — all component props, config types, and utility function signatures are exported | ||
| | | | | ||
| | -------------------- | -------------------------------------------------------------- | | ||
| | **Website** | [ccbd.dev](https://ccbd.dev) | | ||
| | **GitHub** | [github.com/cybercraftbd](https://github.com/cybercraftbd) | | ||
| | **npm Organization** | [npmjs.com/org/power-seo](https://www.npmjs.com/org/power-seo) | | ||
| | **Email** | [info@ccbd.dev](mailto:info@ccbd.dev) | | ||
| --- | ||
| ## Supply Chain Security | ||
| - No install scripts (`postinstall`, `preinstall`) | ||
| - No runtime network access | ||
| - No `eval` or dynamic code execution | ||
| - npm provenance enabled — every release is signed via Sigstore through GitHub Actions | ||
| - CI-signed builds — all releases published via verified `github.com/CyberCraftBD/power-seo` workflow | ||
| - Safe for SSR, Edge-adjacent, and server environments | ||
| --- | ||
| ## License | ||
| ## The [@power-seo](https://www.npmjs.com/org/power-seo) Ecosystem | ||
| **MIT** | ||
| All 17 packages are independently installable — use only what you need. | ||
| | Package | Install | Description | | ||
| | ------------------------------------------------------------------------------------------ | ----------------------------------- | ----------------------------------------------------------------------- | | ||
| | [`@power-seo/core`](https://www.npmjs.com/package/@power-seo/core) | `npm i @power-seo/core` | Framework-agnostic utilities, types, validators, and constants | | ||
| | [`@power-seo/react`](https://www.npmjs.com/package/@power-seo/react) | `npm i @power-seo/react` | React SEO components — meta, Open Graph, Twitter Card, breadcrumbs | | ||
| | [`@power-seo/meta`](https://www.npmjs.com/package/@power-seo/meta) | `npm i @power-seo/meta` | SSR meta helpers for Next.js App Router, Remix v2, and generic SSR | | ||
| | [`@power-seo/schema`](https://www.npmjs.com/package/@power-seo/schema) | `npm i @power-seo/schema` | Type-safe JSON-LD structured data — 20 builders + 18 React components | | ||
| | [`@power-seo/content-analysis`](https://www.npmjs.com/package/@power-seo/content-analysis) | `npm i @power-seo/content-analysis` | Yoast-style SEO content scoring engine with React components | | ||
| | [`@power-seo/readability`](https://www.npmjs.com/package/@power-seo/readability) | `npm i @power-seo/readability` | Readability scoring — Flesch-Kincaid, Gunning Fog, Coleman-Liau, ARI | | ||
| | [`@power-seo/preview`](https://www.npmjs.com/package/@power-seo/preview) | `npm i @power-seo/preview` | SERP, Open Graph, and Twitter/X Card preview generators | | ||
| | [`@power-seo/sitemap`](https://www.npmjs.com/package/@power-seo/sitemap) | `npm i @power-seo/sitemap` | XML sitemap generation, streaming, index splitting, and validation | | ||
| | [`@power-seo/redirects`](https://www.npmjs.com/package/@power-seo/redirects) | `npm i @power-seo/redirects` | Redirect engine with Next.js, Remix, and Express adapters | | ||
| | [`@power-seo/links`](https://www.npmjs.com/package/@power-seo/links) | `npm i @power-seo/links` | Link graph analysis — orphan detection, suggestions, equity scoring | | ||
| | [`@power-seo/audit`](https://www.npmjs.com/package/@power-seo/audit) | `npm i @power-seo/audit` | Full SEO audit engine — meta, content, structure, performance rules | | ||
| | [`@power-seo/images`](https://www.npmjs.com/package/@power-seo/images) | `npm i @power-seo/images` | Image SEO — alt text, lazy loading, format analysis, image sitemaps | | ||
| | [`@power-seo/ai`](https://www.npmjs.com/package/@power-seo/ai) | `npm i @power-seo/ai` | LLM-agnostic AI prompt templates and parsers for SEO tasks | | ||
| | [`@power-seo/analytics`](https://www.npmjs.com/package/@power-seo/analytics) | `npm i @power-seo/analytics` | Merge GSC + audit data, trend analysis, ranking insights, dashboard | | ||
| | [`@power-seo/search-console`](https://www.npmjs.com/package/@power-seo/search-console) | `npm i @power-seo/search-console` | Google Search Console API — OAuth2, service account, URL inspection | | ||
| | [`@power-seo/integrations`](https://www.npmjs.com/package/@power-seo/integrations) | `npm i @power-seo/integrations` | Semrush and Ahrefs API clients with rate limiting and pagination | | ||
| | [`@power-seo/tracking`](https://www.npmjs.com/package/@power-seo/tracking) | `npm i @power-seo/tracking` | GA4, Clarity, PostHog, Plausible, Fathom — scripts + consent management | | ||
| --- | ||
@@ -416,4 +528,15 @@ | ||
| ```text | ||
| seo, react, meta-tags, open-graph, twitter-card, robots, canonical, hreflang, breadcrumb, json-ld, nextjs, gatsby, typescript, react-helmet | ||
| ``` | ||
| react seo · meta tags react · open graph react · twitter card react · react helmet alternative · next.js pages router seo · react canonical url · robots meta react · hreflang react · breadcrumb json-ld · react seo components · typescript react seo · declarative seo react · react head management · seo context react · react 19 seo · vite react seo · gatsby seo components · react open graph · react twitter card · react breadcrumb structured data · noindex react · react meta description | ||
| --- | ||
| ## About [CyberCraft Bangladesh](https://ccbd.dev) | ||
| **[CyberCraft Bangladesh](https://ccbd.dev)** is a Bangladesh-based enterprise-grade software development and Full Stack SEO service provider company specializing in ERP system development, AI-powered SaaS and business applications, full-stack SEO services, custom website development, and scalable eCommerce platforms. We design and develop intelligent, automation-driven SaaS and enterprise solutions that help startups, SMEs, NGOs, educational institutes, and large organizations streamline operations, enhance digital visibility, and accelerate growth through modern cloud-native technologies. | ||
| [](https://ccbd.dev) | ||
| [](https://github.com/cybercraftbd) | ||
| [](https://www.npmjs.com/org/power-seo) | ||
| [](mailto:info@ccbd.dev) | ||
| © 2026 [CyberCraft Bangladesh](https://ccbd.dev) · Released under the [MIT License](../../LICENSE) |
No bug tracker
MaintenancePackage does not have a linked bug tracker in package.json.
Found 1 instance in 1 package
No website
QualityPackage does not have a website.
Found 1 instance in 1 package
103918
8.45%750
14.68%0
-100%0
-100%534
29.93%