UNPKG

@osdeibi/bucky-seo-react

Version:

React component for managing meta tags, Open Graph, and dynamic JSON-LD

507 lines (476 loc) 13.7 kB
import { useEffect } from 'react'; function useDynamicHead(cfg) { useEffect(() => { const metaElements = []; const ogElements = []; const scriptElements = []; // Title if (cfg.title) { document.title = cfg.title; } // Generic metas (cfg.metas || []).forEach(({ name, content }) => { const m = document.createElement("meta"); m.setAttribute("name", name); m.setAttribute("content", content); document.head.appendChild(m); metaElements.push(m); }); // Open Graph (cfg.ogs || []).forEach(({ property, content }) => { const m = document.createElement("meta"); m.setAttribute("property", property); m.setAttribute("content", content); document.head.appendChild(m); ogElements.push(m); }); // JSON-LD (cfg.jsonLd || []).forEach((obj) => { const s = document.createElement("script"); s.type = "application/ld+json"; s.text = JSON.stringify(obj); document.head.appendChild(s); scriptElements.push(s); }); return () => { metaElements.forEach((el) => document.head.removeChild(el)); ogElements.forEach((el) => document.head.removeChild(el)); scriptElements.forEach((el) => document.head.removeChild(el)); }; }, [ cfg.title, JSON.stringify(cfg.metas), JSON.stringify(cfg.ogs), JSON.stringify(cfg.jsonLd), ]); } function DynamicHead({ metaTags, structuredData, }) { // Construir arrays para el hook const metas = [ (metaTags === null || metaTags === void 0 ? void 0 : metaTags.description) && { name: "description", content: metaTags.description }, (metaTags === null || metaTags === void 0 ? void 0 : metaTags.canonicalUrl) && { name: "canonical", content: metaTags.canonicalUrl }, (metaTags === null || metaTags === void 0 ? void 0 : metaTags.robots) && { name: "robots", content: metaTags.robots }, ].filter(Boolean); const ogs = Object.entries((metaTags === null || metaTags === void 0 ? void 0 : metaTags.og) || {}).map(([key, val]) => ({ property: `og:${key}`, content: val, })); const jsonLd = (structuredData || []).map((sd) => ({ "@context": "https://schema.org", "@type": sd.type, ...sd.data, })); // Invocación del hook useDynamicHead({ title: metaTags === null || metaTags === void 0 ? void 0 : metaTags.title, metas, ogs, jsonLd, }); // No renderiza nada en el DOM return null; } function videoObject(data) { const obj = { name: data.name, thumbnailUrl: data.thumbnailUrl, uploadDate: data.uploadDate, ...(data.description && { description: data.description }), ...(data.contentUrl && { contentUrl: data.contentUrl }), ...(data.duration && { duration: data.duration }), ...(data.embedUrl && { embedUrl: data.embedUrl }), ...(data.expires && { expires: data.expires }), // BroadcastEvent for LIVE badge ...(data.publication && { publication: { "@type": "BroadcastEvent", ...data.publication, }, }), // InteractionCounter instead of legacy interactionCount ...(data.interactionStatistic && { interactionStatistic: { "@type": "InteractionCounter", interactionType: { "@type": "WatchAction" }, userInteractionCount: data.interactionStatistic, }, }), // Clip markup for key moments ...(data.hasPart && { hasPart: data.hasPart.map((clip) => ({ "@type": "Clip", ...clip, })), }), // SeekToAction for automatic key moments ...(data.potentialAction && { potentialAction: { "@type": "SeekToAction", target: data.potentialAction.target, "startOffset-input": `required name=${data.potentialAction.placeholderName}`, }, }), // Regions allowed or ineligible ...(data.regionsAllowed && { regionsAllowed: data.regionsAllowed }), ...(data.ineligibleRegion && { ineligibleRegion: data.ineligibleRegion }), }; return { type: "VideoObject", data: obj, }; } /** * Generador de esquema VacationRental que inyecta el JSON-LD con @context y @type. * Basado en el ejemplo oficial de Google Search Central. :contentReference[oaicite:0]{index=0} */ function vacationRental(opts) { return { type: "VacationRental", data: { "@context": "https://schema.org", "@type": "VacationRental", ...opts, }, }; } function paywalledContent(opts) { const { contentType, ...restProps } = opts; return { type: contentType, data: { "@context": "https://schema.org", "@type": contentType, ...restProps }, }; } function speakable(opts) { var _a; const workType = (_a = opts.type) !== null && _a !== void 0 ? _a : "WebPage"; const { speakable, type, ...rest } = opts; return { type: workType, data: { "@context": "https://schema.org", "@type": workType, ...rest, speakable, }, }; } function softwareApplication(opts) { return { type: "SoftwareApplication", data: { "@context": "https://schema.org", "@type": "SoftwareApplication", ...opts, }, }; } function reviewSnippet(opts) { const { contentType, ...rest } = opts; return { type: contentType, data: { "@context": "https://schema.org", "@type": contentType, ...rest, }, }; } function recipe(opts) { return { type: "Recipe", data: { "@context": "https://schema.org", "@type": "Recipe", ...opts, }, }; } /** * Generador de QAPage según Google Search Central :contentReference[oaicite:0]{index=0}. */ function qaPage(opts) { return { type: "QAPage", data: { "@context": "https://schema.org", "@type": "QAPage", mainEntity: opts.mainEntity, }, }; } /** * Generador de ProfilePage: * Produce un JSON-LD como en el ejemplo de Google Search Central. */ function profilePage(opts) { return { type: "ProfilePage", data: { "@context": "https://schema.org", "@type": "ProfilePage", mainEntity: opts.mainEntity, }, }; } function product(opts) { return { type: "Product", data: { "@context": "https://schema.org", "@type": "Product", ...opts, }, }; } /** * Generador de JSON-LD para Practice problems (Quiz) :contentReference[oaicite:0]{index=0} */ function quiz(opts) { return { type: "Quiz", data: { "@context": "https://schema.org", "@type": "Quiz", ...opts, }, }; } /** * Generador de Organization basado en el ejemplo oficial :contentReference[oaicite:0]{index=0} */ function organization(opts) { return { type: "Organization", data: { "@context": "https://schema.org", "@type": "Organization", ...opts, }, }; } /** * Generador de esquema Movie según Google Search Central. */ function movie(opts) { return { type: "Movie", data: { "@context": "https://schema.org", "@type": "Movie", ...opts, }, }; } /** * Generador de JSON-LD para Math Solvers basado en Google Search Central. * https://developers.google.com/search/docs/appearance/structured-data/math-solvers */ function mathSolver(opts) { var _a; const workType = (_a = opts.type) !== null && _a !== void 0 ? _a : "WebPage"; const { type: _ignored, stepByStep, mathExpression, ...rest } = opts; return { type: workType, data: { "@context": "https://schema.org", "@type": workType, ...rest, solverType: "MathSolver", // etiquetamos como MathSolver mathExpression, stepByStep, }, }; } /** * Generador de LocalBusiness según Google Search Central. * Ejemplo de Restaurant con LocalBusiness: * :contentReference[oaicite:0]{index=0} */ function localBusiness(opts) { return { type: "LocalBusiness", data: { "@context": "https://schema.org", "@type": "LocalBusiness", ...opts, }, }; } /** * Generador de JobPosting según Google Search Central . */ function jobPosting(opts) { return { type: "JobPosting", data: { "@context": "https://schema.org", "@type": "JobPosting", ...opts }, }; } /** * Genera un JSON-LD para ImageObject con licencias. * - `license` y `acquireLicensePage` son obligatorios :contentReference[oaicite:0]{index=0}. * - `creditText`, `creator` y `copyrightNotice` opcionales :contentReference[oaicite:1]{index=1}. */ function imageObject(opts) { return { type: "ImageObject", data: { "@context": "https://schema.org", "@type": "ImageObject", ...opts, }, }; } /** Generador de JSON-LD para FAQPage según Google Search Central */ function faqPage(opts) { return { type: "FAQPage", data: { "@context": "https://schema.org", "@type": "FAQPage", mainEntity: opts.mainEntity, }, }; } /** * Generador de JSON-LD para Event, basado en el ejemplo oficial :contentReference[oaicite:0]{index=0}. */ function event(opts) { return { type: "Event", data: { "@context": "https://schema.org", "@type": "Event", ...opts, }, }; } /** * Genera un JSON-LD tipo EmployerAggregateRating: */ function employerAggregateRating(opts) { return { type: "EmployeeRole", // o puedes usar "Organization" según contexto data: { "@context": "https://schema.org", "@type": "EmployerAggregateRating", ...opts, }, }; } /** * Education Q&A usa el mismo marcado que QAPage, * pero exportamos un helper con nombre más explícito. */ function educationQAPage(opts) { return { type: "QAPage", data: { "@context": "https://schema.org", "@type": "QAPage", mainEntity: opts.mainEntity, }, }; } /** * Generador de JSON-LD para un post de foro (DiscussionForumPosting). */ function discussionForumPosting(opts) { return { type: "DiscussionForumPosting", data: { "@context": "https://schema.org", "@type": "DiscussionForumPosting", ...opts, }, }; } /** * Generador de JSON-LD para Dataset. */ function dataset(opts) { return { type: "Dataset", data: { "@context": "https://schema.org", "@type": "Dataset", ...opts, }, }; } /** * Generador de JSON-LD para Course. */ function course(opts) { return { type: "Course", data: { "@context": "https://schema.org", "@type": "Course", ...opts, }, }; } function carousel(opts) { return { type: "ItemList", data: { "@context": "https://schema.org", "@type": "ItemList", itemListElement: opts.items.map(({ position, url, item }) => { const base = { "@type": "ListItem", position }; if (item) { return { ...base, item }; } else { return { ...base, url }; } }), }, }; } /** * Generador de JSON-LD para BreadcrumbList. * * Google espera un objeto @type=BreadcrumbList con un array * itemListElement de ListItem :contentReference[oaicite:0]{index=0}. */ function breadcrumbList(opts) { return { type: "BreadcrumbList", data: { "@context": "https://schema.org", "@type": "BreadcrumbList", itemListElement: opts.itemListElement.map(({ position, name, item }) => ({ "@type": "ListItem", position, name, item })) } }; } /** * Generador de JSON-LD para Article. */ function article(opts) { var _a; const type = (_a = opts.articleType) !== null && _a !== void 0 ? _a : "Article"; const { articleType, ...rest } = opts; return { type, data: { "@context": "https://schema.org", "@type": type, ...rest, }, }; } export { DynamicHead, article, breadcrumbList, carousel, course, dataset, discussionForumPosting, educationQAPage, employerAggregateRating, event, faqPage, imageObject, jobPosting, localBusiness, mathSolver, movie, organization, paywalledContent, product, profilePage, qaPage, quiz, recipe, reviewSnippet, softwareApplication, speakable, vacationRental, videoObject };