ai-seo
Version:
AI-native JSON-LD schema utility with automated URL-to-Schema generation, intelligent content analysis, caching, rate limiting, performance monitoring, and AI optimization (ChatGPT, Voice). Complete automation & scale features. Zero runtime dependencies.
1,796 lines (1,573 loc) • 235 kB
JavaScript
// Simple AI-friendly SEO utility with enhanced features
const performanceNow = (() => {
const perf = globalThis.performance;
return perf && typeof perf.now === 'function'
? perf.now.bind(perf)
: Date.now;
})();
// Global registry to track injected schemas
const schemaRegistry = new Map();
// Advanced Caching System for v1.5.0
class SchemaCache {
constructor() {
this.cache = new Map();
this.config = {
strategy: 'intelligent', // 'none', 'basic', 'intelligent'
ttl: 3600000, // 1 hour default
maxSize: 50,
enableCompression: true,
enableMetrics: true
};
this.metrics = {
hits: 0,
misses: 0,
compressionSavings: 0,
averageAccessTime: 0
};
this.accessTimes = [];
}
configure(options = {}) {
this.config = { ...this.config, ...options };
// Clear cache if strategy changed to 'none'
if (this.config.strategy === 'none') {
this.clear();
}
// Enforce max size
if (this.cache.size > this.config.maxSize) {
this._enforceMaxSize();
}
}
_generateKey(schema, options = {}) {
// Create a stable cache key based on schema content and options
const keyData = {
type: schema['@type'],
content: JSON.stringify(schema),
options: JSON.stringify(options)
};
// Simple hash function for cache key
return this._hash(JSON.stringify(keyData));
}
_hash(str) {
let hash = 0;
if (str.length === 0) return hash.toString();
for (let i = 0; i < str.length; i++) {
const char = str.charCodeAt(i);
hash = ((hash << 5) - hash) + char;
hash = hash & hash; // Convert to 32-bit integer
}
return Math.abs(hash).toString(36);
}
_compress(data) {
if (!this.config.enableCompression) return data;
// Simple compression simulation (in real implementation, use actual compression)
const original = JSON.stringify(data);
const compressed = original.replace(/\s+/g, ' ').trim();
if (this.config.enableMetrics) {
this.metrics.compressionSavings += (original.length - compressed.length);
}
return compressed;
}
_decompress(data) {
if (!this.config.enableCompression) return data;
return typeof data === 'string' ? JSON.parse(data) : data;
}
_enforceMaxSize() {
if (this.cache.size <= this.config.maxSize) return;
// LRU eviction: remove oldest entries
const entries = Array.from(this.cache.entries());
entries.sort((a, b) => a[1].lastAccessed - b[1].lastAccessed);
const toRemove = entries.slice(0, this.cache.size - this.config.maxSize);
toRemove.forEach(([key]) => this.cache.delete(key));
}
_isExpired(entry) {
if (!this.config.ttl) return false;
return Date.now() - entry.timestamp > this.config.ttl;
}
_recordAccess(hit = true) {
const accessTime = performanceNow();
this.accessTimes.push(accessTime);
if (this.config.enableMetrics) {
if (hit) {
this.metrics.hits++;
} else {
this.metrics.misses++;
}
// Keep only last 100 access times for average calculation
if (this.accessTimes.length > 100) {
this.accessTimes = this.accessTimes.slice(-50); // More aggressive cleanup
}
this.metrics.averageAccessTime = this.accessTimes.reduce((a, b) => a + b, 0) / this.accessTimes.length;
}
}
get(schema, options = {}) {
if (this.config.strategy === 'none') return null;
const key = this._generateKey(schema, options);
const entry = this.cache.get(key);
if (!entry || this._isExpired(entry)) {
this._recordAccess(false);
if (entry) this.cache.delete(key); // Remove expired entry
return null;
}
// Update last accessed time
entry.lastAccessed = Date.now();
this.cache.set(key, entry);
this._recordAccess(true);
return this._decompress(entry.data);
}
set(schema, options = {}, result) {
if (this.config.strategy === 'none') return false;
const key = this._generateKey(schema, options);
const compressedData = this._compress(result);
const entry = {
data: compressedData,
timestamp: Date.now(),
lastAccessed: Date.now(),
schemaType: schema['@type'],
size: JSON.stringify(result).length
};
this.cache.set(key, entry);
this._enforceMaxSize();
return true;
}
clear() {
this.cache.clear();
this.metrics = {
hits: 0,
misses: 0,
compressionSavings: 0,
averageAccessTime: 0
};
this.accessTimes = [];
}
getMetrics() {
const hitRate = this.metrics.hits + this.metrics.misses > 0
? (this.metrics.hits / (this.metrics.hits + this.metrics.misses)) * 100
: 0;
return {
...this.metrics,
hitRate: Math.round(hitRate * 100) / 100,
cacheSize: this.cache.size,
maxSize: this.config.maxSize,
strategy: this.config.strategy,
totalEntries: this.metrics.hits + this.metrics.misses
};
}
// Intelligent caching strategies
shouldCache(schema) {
if (this.config.strategy === 'none') return false;
if (this.config.strategy === 'basic') return true;
// Intelligent strategy: cache based on schema complexity and reuse patterns
if (this.config.strategy === 'intelligent') {
const schemaSize = JSON.stringify(schema).length;
const isComplex = schemaSize > 500; // Cache complex schemas
const hasMultipleProperties = Object.keys(schema).length > 5;
const isReusableType = ['Product', 'Article', 'LocalBusiness', 'Event'].includes(schema['@type']);
return isComplex || hasMultipleProperties || isReusableType;
}
return true;
}
}
// Global cache instance
const globalSchemaCache = new SchemaCache();
// Cache configuration API
export const Cache = {
configure: (options) => globalSchemaCache.configure(options),
clear: () => globalSchemaCache.clear(),
getMetrics: () => globalSchemaCache.getMetrics(),
getInstance: () => globalSchemaCache
};
// Lazy Schema Loading System for v1.5.0
export class LazySchema {
constructor(type = 'Thing') {
this.schemaType = type;
this.loadCondition = 'immediate'; // 'immediate', 'visible', 'interaction', 'custom'
this.dataProvider = null;
this.element = null;
this.observer = null;
this.loaded = false;
this.config = {
rootMargin: '50px',
threshold: 0.1,
debug: false
};
}
// Configure when to load the schema
loadWhen(condition, customFn = null) {
this.loadCondition = condition;
if (condition === 'custom' && typeof customFn === 'function') {
this.customCondition = customFn;
}
return this;
}
// Set data provider function
withData(dataFn) {
if (typeof dataFn === 'function') {
this.dataProvider = dataFn;
}
return this;
}
// Configure lazy loading options
configure(options = {}) {
this.config = { ...this.config, ...options };
return this;
}
// Create placeholder element for visibility tracking
_createPlaceholder() {
if (!isBrowserEnvironment()) return null;
const placeholder = document.createElement('div');
placeholder.setAttribute('data-lazy-schema', 'true');
placeholder.setAttribute('data-schema-type', this.schemaType);
placeholder.style.height = '1px';
placeholder.style.width = '1px';
placeholder.style.position = 'absolute';
placeholder.style.top = '0';
placeholder.style.left = '0';
placeholder.style.pointerEvents = 'none';
placeholder.style.opacity = '0';
return placeholder;
}
// Load schema immediately
_loadImmediate() {
return this._executeLoad();
}
// Setup visibility observer
_setupVisibilityObserver() {
if (!isBrowserEnvironment() || !window.IntersectionObserver) {
// Fallback to immediate loading if IntersectionObserver not available
return this._loadImmediate();
}
this.element = this._createPlaceholder();
if (!this.element) return null;
document.body.appendChild(this.element);
this.observer = new IntersectionObserver((entries) => {
entries.forEach(entry => {
if (entry.isIntersecting && !this.loaded) {
this._executeLoad();
this.observer.disconnect();
this.element.remove();
}
});
}, {
rootMargin: this.config.rootMargin,
threshold: this.config.threshold
});
this.observer.observe(this.element);
return this.element;
}
// Setup interaction observer
_setupInteractionObserver() {
if (!isBrowserEnvironment()) {
return this._loadImmediate();
}
const events = ['click', 'scroll', 'keydown', 'touchstart'];
const loadOnInteraction = () => {
if (!this.loaded) {
this._executeLoad();
events.forEach(event => {
document.removeEventListener(event, loadOnInteraction, { passive: true });
});
}
};
events.forEach(event => {
document.addEventListener(event, loadOnInteraction, { passive: true });
});
return { type: 'interaction-listener', events };
}
// Execute the actual schema loading
_executeLoad() {
if (this.loaded) return null;
try {
const data = this.dataProvider ? this.dataProvider() : {};
const schema = this._buildSchema(data);
const result = initSEO({
schema,
debug: this.config.debug,
id: `lazy-${this.schemaType.toLowerCase()}-${Date.now()}`
});
this.loaded = true;
if (this.config.debug) {
debugLog(`Lazy loaded ${this.schemaType} schema`, 'info', true);
}
return result;
} catch (error) {
debugLog(`Error in lazy schema loading: ${error.message}`, 'error', this.config.debug);
return null;
}
}
// Build schema from data
_buildSchema(data) {
const baseSchema = {
"@context": "https://schema.org",
"@type": this.schemaType
};
// Merge with provided data
return { ...baseSchema, ...data };
}
// Custom condition checking
_checkCustomCondition() {
if (typeof this.customCondition === 'function') {
try {
return this.customCondition();
} catch (error) {
debugLog(`Error in custom condition: ${error.message}`, 'error', this.config.debug);
return true; // Fallback to loading
}
}
return true;
}
// Main injection method
inject(options = {}) {
this.config = { ...this.config, ...options };
switch (this.loadCondition) {
case 'immediate':
return this._loadImmediate();
case 'visible':
return this._setupVisibilityObserver();
case 'interaction':
return this._setupInteractionObserver();
case 'custom':
if (this._checkCustomCondition()) {
return this._loadImmediate();
}
return null;
default:
return this._loadImmediate();
}
}
// Cleanup method
cleanup() {
if (this.observer) {
this.observer.disconnect();
this.observer = null;
}
if (this.element && this.element.parentNode) {
this.element.parentNode.removeChild(this.element);
this.element = null;
}
}
}
// Performance Monitoring System for v1.5.0
export const Performance = {
metrics: {
schemaInjections: [],
averageInjectionTime: 0,
cacheHitRate: 0,
totalSchemas: 0,
performanceScore: 100
},
// Record schema injection performance
recordInjection(duration, fromCache = false, schemaType = 'Unknown') {
const metric = {
duration,
fromCache,
schemaType,
timestamp: Date.now()
};
this.metrics.schemaInjections.push(metric);
// Keep only last 100 measurements
if (this.metrics.schemaInjections.length > 100) {
this.metrics.schemaInjections = this.metrics.schemaInjections.slice(-100);
}
this._updateMetrics();
},
// Update calculated metrics
_updateMetrics() {
const injections = this.metrics.schemaInjections;
if (injections.length === 0) return;
// Calculate average injection time
const totalTime = injections.reduce((sum, m) => sum + m.duration, 0);
this.metrics.averageInjectionTime = totalTime / injections.length;
// Calculate cache hit rate
const cacheHits = injections.filter(m => m.fromCache).length;
this.metrics.cacheHitRate = (cacheHits / injections.length) * 100;
// Update total schemas
this.metrics.totalSchemas = injections.length;
// Calculate performance score
this._calculatePerformanceScore();
},
// Calculate overall performance score
_calculatePerformanceScore() {
let score = 100;
// Deduct points for slow injections
if (this.metrics.averageInjectionTime > 5) score -= 20;
else if (this.metrics.averageInjectionTime > 2) score -= 10;
// Add points for good cache hit rate
if (this.metrics.cacheHitRate > 80) score += 10;
else if (this.metrics.cacheHitRate < 20) score -= 15;
// Deduct points for too many schemas (performance impact)
if (this.metrics.totalSchemas > 50) score -= 10;
this.metrics.performanceScore = Math.max(0, Math.min(100, score));
},
// Get performance report
getReport() {
const report = {
...this.metrics,
recommendations: this._generateRecommendations(),
timestamp: new Date().toISOString()
};
return report;
},
// Generate performance recommendations
_generateRecommendations() {
const recommendations = [];
if (this.metrics.averageInjectionTime > 5) {
recommendations.push({
type: 'performance',
severity: 'high',
message: 'Schema injection time is slow. Consider enabling caching or reducing schema complexity.',
action: 'Enable intelligent caching: Cache.configure({ strategy: "intelligent" })'
});
}
if (this.metrics.cacheHitRate < 30 && this.metrics.totalSchemas > 10) {
recommendations.push({
type: 'caching',
severity: 'medium',
message: 'Low cache hit rate detected. Review caching strategy.',
action: 'Optimize for reusable schemas or increase cache size: Cache.configure({ maxSize: 100 })'
});
}
if (this.metrics.totalSchemas > 20) {
recommendations.push({
type: 'optimization',
severity: 'medium',
message: 'Many schemas detected. Consider lazy loading for better performance.',
action: 'Use LazySchema for non-critical schemas: new LazySchema("Product").loadWhen("visible")'
});
}
if (this.metrics.performanceScore > 90) {
recommendations.push({
type: 'success',
severity: 'info',
message: 'Excellent performance! Your schema implementation is optimized.',
action: 'Continue monitoring performance as your application grows.'
});
}
return recommendations;
},
// Clear performance data
clear() {
this.metrics = {
schemaInjections: [],
averageInjectionTime: 0,
cacheHitRate: 0,
totalSchemas: 0,
performanceScore: 100
};
}
};
// Schema builder helpers for common schema types
export const SchemaHelpers = {
/**
* Create a Product schema
*/
createProduct({
name,
description,
image,
brand,
offers = {},
aggregateRating,
review,
sku,
mpn,
gtin,
...additionalProps
} = {}) {
const schema = {
"@context": "https://schema.org",
"@type": "Product",
name,
description,
...additionalProps
};
if (image) schema.image = Array.isArray(image) ? image : [image];
if (brand) schema.brand = typeof brand === 'string' ? { "@type": "Brand", name: brand } : brand;
if (sku) schema.sku = sku;
if (mpn) schema.mpn = mpn;
if (gtin) schema.gtin = gtin;
if (offers && Object.keys(offers).length > 0) {
schema.offers = {
"@type": "Offer",
priceCurrency: offers.priceCurrency || "USD",
price: offers.price,
availability: offers.availability || "https://schema.org/InStock",
...offers
};
}
if (aggregateRating) {
schema.aggregateRating = {
"@type": "AggregateRating",
ratingValue: aggregateRating.ratingValue,
reviewCount: aggregateRating.reviewCount,
...aggregateRating
};
}
if (review) {
schema.review = Array.isArray(review) ? review.map(r => ({
"@type": "Review",
reviewRating: r.rating ? { "@type": "Rating", ratingValue: r.rating } : undefined,
author: r.author ? { "@type": "Person", name: r.author } : undefined,
...r
})) : [review];
}
return schema;
},
/**
* Create an Article schema
*/
createArticle({
headline,
description,
author,
datePublished,
dateModified,
image,
publisher,
url,
wordCount,
articleSection,
keywords,
...additionalProps
} = {}) {
const schema = {
"@context": "https://schema.org",
"@type": "Article",
headline,
description,
...additionalProps
};
if (author) {
schema.author = typeof author === 'string'
? { "@type": "Person", name: author }
: author;
}
if (datePublished) schema.datePublished = datePublished;
if (dateModified) schema.dateModified = dateModified;
if (image) schema.image = Array.isArray(image) ? image : [image];
if (url) schema.url = url;
if (wordCount) schema.wordCount = wordCount;
if (articleSection) schema.articleSection = articleSection;
if (keywords) schema.keywords = Array.isArray(keywords) ? keywords : [keywords];
if (publisher) {
schema.publisher = typeof publisher === 'string'
? { "@type": "Organization", name: publisher }
: publisher;
}
return schema;
},
/**
* Create a LocalBusiness schema
*/
createLocalBusiness({
name,
description,
address,
telephone,
openingHours,
url,
image,
priceRange,
aggregateRating,
geo,
businessType = "LocalBusiness",
...additionalProps
} = {}) {
const schema = {
"@context": "https://schema.org",
"@type": businessType,
name,
description,
...additionalProps
};
if (address) {
schema.address = typeof address === 'string'
? { "@type": "PostalAddress", streetAddress: address }
: { "@type": "PostalAddress", ...address };
}
if (telephone) schema.telephone = telephone;
if (url) schema.url = url;
if (image) schema.image = Array.isArray(image) ? image : [image];
if (priceRange) schema.priceRange = priceRange;
if (openingHours) {
schema.openingHours = Array.isArray(openingHours) ? openingHours : [openingHours];
}
if (aggregateRating) {
schema.aggregateRating = {
"@type": "AggregateRating",
ratingValue: aggregateRating.ratingValue,
reviewCount: aggregateRating.reviewCount,
...aggregateRating
};
}
if (geo) {
schema.geo = {
"@type": "GeoCoordinates",
latitude: geo.latitude,
longitude: geo.longitude,
...geo
};
}
return schema;
},
/**
* Create an Event schema
*/
createEvent({
name,
description,
startDate,
endDate,
location,
organizer,
offers,
image,
url,
eventStatus = "https://schema.org/EventScheduled",
eventAttendanceMode = "https://schema.org/OfflineEventAttendanceMode",
...additionalProps
} = {}) {
const schema = {
"@context": "https://schema.org",
"@type": "Event",
name,
description,
startDate,
eventStatus,
eventAttendanceMode,
...additionalProps
};
if (endDate) schema.endDate = endDate;
if (url) schema.url = url;
if (image) schema.image = Array.isArray(image) ? image : [image];
if (location) {
if (typeof location === 'string') {
schema.location = {
"@type": "Place",
name: location
};
} else if (location.address || location.geo) {
schema.location = {
"@type": "Place",
name: location.name,
address: location.address ? {
"@type": "PostalAddress",
...(typeof location.address === 'string'
? { streetAddress: location.address }
: location.address)
} : undefined,
geo: location.geo ? {
"@type": "GeoCoordinates",
latitude: location.geo.latitude,
longitude: location.geo.longitude
} : undefined,
...location
};
} else {
schema.location = { "@type": "Place", ...location };
}
}
if (organizer) {
schema.organizer = typeof organizer === 'string'
? { "@type": "Organization", name: organizer }
: organizer;
}
if (offers) {
schema.offers = Array.isArray(offers) ? offers.map(offer => ({
"@type": "Offer",
priceCurrency: offer.priceCurrency || "USD",
...offer
})) : {
"@type": "Offer",
priceCurrency: offers.priceCurrency || "USD",
...offers
};
}
return schema;
}
};
// Schema Composer API - Fluent interface for complex schema building
export class SchemaComposer {
constructor(type = 'Thing') {
this.schema = {
"@context": "https://schema.org",
"@type": type
};
}
// Core properties
name(value) {
this.schema.name = value;
return this;
}
description(value) {
this.schema.description = value;
return this;
}
url(value) {
this.schema.url = value;
return this;
}
image(value) {
this.schema.image = Array.isArray(value) ? value : [value];
return this;
}
// Organization/Business specific
address(value) {
if (typeof value === 'string') {
this.schema.address = { "@type": "PostalAddress", streetAddress: value };
} else {
this.schema.address = { "@type": "PostalAddress", ...value };
}
return this;
}
telephone(value) {
this.schema.telephone = value;
return this;
}
email(value) {
this.schema.email = value;
return this;
}
// Product specific
brand(value) {
this.schema.brand = typeof value === 'string'
? { "@type": "Brand", name: value }
: value;
return this;
}
offers(priceOptions) {
if (Array.isArray(priceOptions)) {
this.schema.offers = priceOptions.map(offer => ({
"@type": "Offer",
priceCurrency: offer.priceCurrency || "USD",
...offer
}));
} else {
this.schema.offers = {
"@type": "Offer",
priceCurrency: priceOptions.priceCurrency || "USD",
...priceOptions
};
}
return this;
}
// Article specific
author(value) {
this.schema.author = typeof value === 'string'
? { "@type": "Person", name: value }
: value;
return this;
}
publisher(value) {
this.schema.publisher = typeof value === 'string'
? { "@type": "Organization", name: value }
: value;
return this;
}
datePublished(value) {
this.schema.datePublished = value;
return this;
}
dateModified(value) {
this.schema.dateModified = value;
return this;
}
keywords(value) {
this.schema.keywords = Array.isArray(value) ? value : [value];
return this;
}
// Event specific
startDate(value) {
this.schema.startDate = value;
return this;
}
endDate(value) {
this.schema.endDate = value;
return this;
}
location(value) {
if (typeof value === 'string') {
this.schema.location = { "@type": "Place", name: value };
} else {
this.schema.location = { "@type": "Place", ...value };
}
return this;
}
organizer(value) {
this.schema.organizer = typeof value === 'string'
? { "@type": "Organization", name: value }
: value;
return this;
}
// Rating and reviews
rating(ratingValue, reviewCount, bestRating = 5, worstRating = 1) {
this.schema.aggregateRating = {
"@type": "AggregateRating",
ratingValue,
reviewCount,
bestRating,
worstRating
};
return this;
}
review(reviewData) {
if (!this.schema.review) this.schema.review = [];
const reviews = Array.isArray(reviewData) ? reviewData : [reviewData];
this.schema.review.push(...reviews.map(review => ({
"@type": "Review",
reviewRating: review.rating ? { "@type": "Rating", ratingValue: review.rating } : undefined,
author: review.author ? { "@type": "Person", name: review.author } : undefined,
...review
})));
return this;
}
// Geographic coordinates
geo(latitude, longitude) {
this.schema.geo = {
"@type": "GeoCoordinates",
latitude,
longitude
};
return this;
}
// Opening hours for businesses
openingHours(hours) {
this.schema.openingHours = Array.isArray(hours) ? hours : [hours];
return this;
}
// Price range
priceRange(range) {
this.schema.priceRange = range;
return this;
}
// Custom properties
addProperty(key, value) {
this.schema[key] = value;
return this;
}
// Merge with another schema
merge(otherSchema) {
this.schema = { ...this.schema, ...otherSchema };
return this;
}
// Build and return the schema
build() {
return { ...this.schema };
}
// Build and inject into DOM
inject(options = {}) {
const schema = this.build();
return initSEO({ schema, ...options });
}
}
// Convenience factory functions
export const createSchema = (type) => new SchemaComposer(type);
export const product = () => new SchemaComposer('Product');
export const article = () => new SchemaComposer('Article');
export const organization = () => new SchemaComposer('Organization');
export const localBusiness = (businessType = 'LocalBusiness') => new SchemaComposer(businessType);
export const event = () => new SchemaComposer('Event');
export const person = () => new SchemaComposer('Person');
export const website = () => new SchemaComposer('WebSite');
export const webpage = () => new SchemaComposer('WebPage');
// Framework Integrations
export const Frameworks = {
/**
* React Hooks
*/
React: {
// Hook for managing single schema
useSEO: (schemaOrFunction) => {
// Note: In real React environment, this would use actual React hooks
// This is a compatible interface that works in any environment
let currentSchema = null;
let cleanup = null;
const updateSchema = () => {
// Clean up previous schema
if (cleanup) cleanup();
// Get new schema
const schema = typeof schemaOrFunction === 'function'
? schemaOrFunction()
: schemaOrFunction;
if (schema) {
const element = initSEO({ schema });
if (element) {
cleanup = () => {
if (element.parentNode) {
element.parentNode.removeChild(element);
}
};
currentSchema = schema;
}
}
};
// Initial setup
updateSchema();
return {
schema: currentSchema,
update: updateSchema,
cleanup: () => cleanup && cleanup()
};
},
// Hook for managing multiple schemas
useMultipleSEO: (schemasOrFunction) => {
let currentElements = [];
let cleanup = null;
const updateSchemas = () => {
// Clean up previous schemas
if (cleanup) cleanup();
const schemas = typeof schemasOrFunction === 'function'
? schemasOrFunction()
: schemasOrFunction;
if (schemas && schemas.length > 0) {
const results = injectMultipleSchemas(schemas);
currentElements = results.filter(r => r.success).map(r => r.element);
cleanup = () => {
currentElements.forEach(el => {
if (el && el.parentNode) {
el.parentNode.removeChild(el);
}
});
currentElements = [];
};
}
};
updateSchemas();
return {
elements: currentElements,
update: updateSchemas,
cleanup: () => cleanup && cleanup()
};
},
// HOC for easy schema injection
withSEO: (Component, schemaOrFunction) => {
return (props) => {
const schema = typeof schemaOrFunction === 'function'
? schemaOrFunction(props)
: schemaOrFunction;
if (schema) {
initSEO({ schema });
}
return Component(props);
};
}
},
/**
* Vue Composables
*/
Vue: {
// Composable for managing single schema
useSEO: (schemaOrRef, options = {}) => {
let currentElement = null;
let cleanup = null;
const updateSchema = () => {
if (cleanup) cleanup();
const schema = typeof schemaOrRef === 'function'
? schemaOrRef()
: (schemaOrRef?.value || schemaOrRef);
if (schema) {
const element = initSEO({ schema, ...options });
if (element) {
currentElement = element;
cleanup = () => {
if (element.parentNode) {
element.parentNode.removeChild(element);
}
};
}
}
};
updateSchema();
return {
element: currentElement,
update: updateSchema,
cleanup: () => cleanup && cleanup()
};
},
// Composable for multiple schemas
useMultipleSEO: (schemasOrRef, options = {}) => {
let currentElements = [];
let cleanup = null;
const updateSchemas = () => {
if (cleanup) cleanup();
const schemas = typeof schemasOrRef === 'function'
? schemasOrRef()
: (schemasOrRef?.value || schemasOrRef);
if (schemas && schemas.length > 0) {
const results = injectMultipleSchemas(schemas, options);
currentElements = results.filter(r => r.success).map(r => r.element);
cleanup = () => {
currentElements.forEach(el => {
if (el && el.parentNode) {
el.parentNode.removeChild(el);
}
});
currentElements = [];
};
}
};
updateSchemas();
return {
elements: currentElements,
update: updateSchemas,
cleanup: () => cleanup && cleanup()
};
}
},
/**
* Svelte Stores
*/
Svelte: {
// Schema store
createSEOStore: (initialSchema = null) => {
const subscribers = [];
let currentSchema = initialSchema;
let currentElement = null;
const subscribe = (callback) => {
subscribers.push(callback);
callback(currentSchema);
return () => {
const index = subscribers.indexOf(callback);
if (index > -1) subscribers.splice(index, 1);
};
};
const set = (schema) => {
// Clean up previous
if (currentElement && currentElement.parentNode) {
currentElement.parentNode.removeChild(currentElement);
}
currentSchema = schema;
// Inject new schema
if (schema) {
currentElement = initSEO({ schema });
}
// Notify subscribers
subscribers.forEach(callback => callback(currentSchema));
};
const update = (updater) => {
set(updater(currentSchema));
};
// Initial injection
if (initialSchema) {
currentElement = initSEO({ schema: initialSchema });
}
return {
subscribe,
set,
update,
get: () => currentSchema
};
},
// Multiple schemas store
createMultipleSEOStore: (initialSchemas = []) => {
const subscribers = [];
let currentSchemas = initialSchemas;
let currentElements = [];
const subscribe = (callback) => {
subscribers.push(callback);
callback(currentSchemas);
return () => {
const index = subscribers.indexOf(callback);
if (index > -1) subscribers.splice(index, 1);
};
};
const set = (schemas) => {
// Clean up previous
currentElements.forEach(el => {
if (el && el.parentNode) {
el.parentNode.removeChild(el);
}
});
currentSchemas = schemas;
// Inject new schemas
if (schemas && schemas.length > 0) {
const results = injectMultipleSchemas(schemas);
currentElements = results.filter(r => r.success).map(r => r.element);
} else {
currentElements = [];
}
// Notify subscribers
subscribers.forEach(callback => callback(currentSchemas));
};
const update = (updater) => {
set(updater(currentSchemas));
};
// Initial injection
if (initialSchemas.length > 0) {
const results = injectMultipleSchemas(initialSchemas);
currentElements = results.filter(r => r.success).map(r => r.element);
}
return {
subscribe,
set,
update,
get: () => currentSchemas
};
}
}
};
// Schema Templates - Pre-built templates for common industries
export const Templates = {
/**
* E-commerce Templates
*/
ecommerce: {
// Product listing page
productPage: (productData) => {
return product()
.name(productData.name)
.description(productData.description)
.image(productData.images || productData.image)
.brand(productData.brand)
.offers({
price: productData.price,
priceCurrency: productData.currency || 'USD',
availability: productData.inStock ? 'https://schema.org/InStock' : 'https://schema.org/OutOfStock',
seller: productData.seller
})
.rating(productData.rating, productData.reviewCount)
.addProperty('sku', productData.sku)
.addProperty('gtin', productData.gtin || productData.upc)
.build();
},
// Online store
onlineStore: (storeData) => {
return organization()
.name(storeData.name)
.description(storeData.description)
.url(storeData.website)
.email(storeData.email)
.telephone(storeData.phone)
.addProperty('@type', 'OnlineStore')
.addProperty('paymentAccepted', storeData.paymentMethods || ['Credit Card', 'PayPal'])
.addProperty('currenciesAccepted', storeData.currencies || ['USD'])
.build();
}
},
/**
* Restaurant & Food Service Templates
*/
restaurant: {
// Restaurant listing
restaurant: (restaurantData) => {
return localBusiness('Restaurant')
.name(restaurantData.name)
.description(restaurantData.description)
.address(restaurantData.address)
.telephone(restaurantData.phone)
.url(restaurantData.website)
.openingHours(restaurantData.hours)
.priceRange(restaurantData.priceRange || '$$')
.rating(restaurantData.rating, restaurantData.reviewCount)
.geo(restaurantData.latitude, restaurantData.longitude)
.addProperty('servesCuisine', restaurantData.cuisine)
.addProperty('acceptsReservations', restaurantData.acceptsReservations || true)
.build();
},
// Menu item
menuItem: (itemData) => {
return createSchema('MenuItem')
.name(itemData.name)
.description(itemData.description)
.image(itemData.image)
.offers({
price: itemData.price,
priceCurrency: itemData.currency || 'USD'
})
.addProperty('nutrition', itemData.nutrition)
.addProperty('suitableForDiet', itemData.dietaryRestrictions)
.build();
}
},
/**
* Healthcare Templates
*/
healthcare: {
// Medical practice
medicalOrganization: (practiceData) => {
return localBusiness('MedicalOrganization')
.name(practiceData.name)
.description(practiceData.description)
.address(practiceData.address)
.telephone(practiceData.phone)
.url(practiceData.website)
.openingHours(practiceData.hours)
.addProperty('medicalSpecialty', practiceData.specialties)
.addProperty('acceptedInsurance', practiceData.insurance)
.build();
},
// Healthcare provider
physician: (doctorData) => {
return person()
.name(doctorData.name)
.addProperty('@type', 'Physician')
.addProperty('medicalSpecialty', doctorData.specialties)
.addProperty('affiliation', doctorData.hospital)
.addProperty('alumniOf', doctorData.education)
.addProperty('availableService', doctorData.services)
.build();
}
},
/**
* Real Estate Templates
*/
realEstate: {
// Property listing
realEstateProperty: (propertyData) => {
return createSchema('RealEstateListing')
.name(propertyData.title)
.description(propertyData.description)
.image(propertyData.images)
.address(propertyData.address)
.addProperty('listingAgent', {
'@type': 'RealEstateAgent',
name: propertyData.agent.name,
telephone: propertyData.agent.phone
})
.addProperty('price', {
'@type': 'PriceSpecification',
price: propertyData.price,
priceCurrency: propertyData.currency || 'USD'
})
.addProperty('numberOfRooms', propertyData.bedrooms)
.addProperty('numberOfBathroomsTotal', propertyData.bathrooms)
.addProperty('floorSize', propertyData.squareFeet)
.build();
},
// Real estate agency
realEstateAgency: (agencyData) => {
return localBusiness('RealEstateAgent')
.name(agencyData.name)
.description(agencyData.description)
.address(agencyData.address)
.telephone(agencyData.phone)
.url(agencyData.website)
.email(agencyData.email)
.addProperty('areaServed', agencyData.serviceAreas)
.build();
}
},
/**
* Education Templates
*/
education: {
// Educational institution
school: (schoolData) => {
return organization()
.name(schoolData.name)
.description(schoolData.description)
.address(schoolData.address)
.telephone(schoolData.phone)
.url(schoolData.website)
.addProperty('@type', 'EducationalOrganization')
.addProperty('foundingDate', schoolData.founded)
.addProperty('numberOfStudents', schoolData.enrollment)
.build();
},
// Online course
course: (courseData) => {
return createSchema('Course')
.name(courseData.title)
.description(courseData.description)
.addProperty('provider', {
'@type': 'Organization',
name: courseData.provider
})
.addProperty('courseCode', courseData.code)
.addProperty('educationalLevel', courseData.level)
.addProperty('timeRequired', courseData.duration)
.offers({
price: courseData.price || 0,
priceCurrency: courseData.currency || 'USD'
})
.build();
}
},
/**
* Professional Services Templates
*/
professional: {
// Law firm
lawFirm: (firmData) => {
return localBusiness('LegalService')
.name(firmData.name)
.description(firmData.description)
.address(firmData.address)
.telephone(firmData.phone)
.url(firmData.website)
.email(firmData.email)
.addProperty('areaOfLaw', firmData.practiceAreas)
.addProperty('jurisdiction', firmData.jurisdictions)
.build();
},
// Consulting firm
consultingFirm: (firmData) => {
return localBusiness('ProfessionalService')
.name(firmData.name)
.description(firmData.description)
.address(firmData.address)
.telephone(firmData.phone)
.url(firmData.website)
.email(firmData.email)
.addProperty('serviceType', firmData.services)
.addProperty('areaServed', firmData.serviceAreas)
.build();
}
},
/**
* Event Templates
*/
events: {
// Conference
conference: (eventData) => {
return event()
.name(eventData.name)
.description(eventData.description)
.startDate(eventData.startDate)
.endDate(eventData.endDate)
.location({
name: eventData.venue,
address: eventData.address
})
.organizer(eventData.organizer)
.offers({
price: eventData.ticketPrice,
priceCurrency: eventData.currency || 'USD',
url: eventData.ticketUrl
})
.addProperty('eventAttendanceMode',
eventData.isVirtual ? 'https://schema.org/OnlineEventAttendanceMode' :
'https://schema.org/OfflineEventAttendanceMode')
.build();
},
// Workshop
workshop: (workshopData) => {
return event()
.name(workshopData.name)
.description(workshopData.description)
.startDate(workshopData.startDate)
.endDate(workshopData.endDate)
.location(workshopData.location)
.organizer(workshopData.instructor)
.addProperty('maximumAttendeeCapacity', workshopData.maxAttendees)
.addProperty('educationalLevel', workshopData.level)
.offers({
price: workshopData.price,
priceCurrency: workshopData.currency || 'USD'
})
.build();
}
},
/**
* Job & Career Templates
*/
jobs: {
// Job posting
jobPosting: (jobData) => {
return createSchema('JobPosting')
.name(jobData.title)
.description(jobData.description)
.addProperty('datePosted', jobData.datePosted || new Date().toISOString())
.addProperty('validThrough', jobData.validThrough)
.addProperty('employmentType', jobData.employmentType || 'FULL_TIME')
.addProperty('hiringOrganization', {
'@type': 'Organization',
name: jobData.company,
sameAs: jobData.companyWebsite,
logo: jobData.companyLogo
})
.addProperty('jobLocation', {
'@type': 'Place',
address: {
'@type': 'PostalAddress',
streetAddress: jobData.location?.address,
addressLocality: jobData.location?.city,
addressRegion: jobData.location?.state,
postalCode: jobData.location?.zipCode,
addressCountry: jobData.location?.country || 'US'
}
})
.addProperty('baseSalary', jobData.salary ? {
'@type': 'MonetaryAmount',
currency: jobData.salary.currency || 'USD',
value: {
'@type': 'QuantitativeValue',
minValue: jobData.salary.min,
maxValue: jobData.salary.max,
unitText: jobData.salary.period || 'YEAR'
}
} : undefined)
.addProperty('workFromHome', jobData.remote)
.addProperty('qualifications', jobData.requirements)
.addProperty('responsibilities', jobData.responsibilities)
.addProperty('skills', jobData.skills)
.addProperty('benefits', jobData.benefits)
.addProperty('industry', jobData.industry)
.addProperty('occupationalCategory', jobData.category)
.build();
},
// Company profile
company: (companyData) => {
return organization()
.name(companyData.name)
.description(companyData.description)
.url(companyData.website)
.address(companyData.address)
.telephone(companyData.phone)
.email(companyData.email)
.addProperty('logo', companyData.logo)
.addProperty('foundingDate', companyData.founded)
.addProperty('numberOfEmployees', companyData.employeeCount)
.addProperty('industry', companyData.industry)
.addProperty('slogan', companyData.slogan)
.addProperty('awards', companyData.awards)
.build();
}
},
/**
* Recipe & Food Templates
*/
recipe: {
// Recipe schema
recipe: (recipeData) => {
return createSchema('Recipe')
.name(recipeData.name)
.description(recipeData.description)
.image(recipeData.images)
.author(recipeData.author)
.datePublished(recipeData.datePublished)
.addProperty('recipeYield', recipeData.servings)
.addProperty('prepTime', recipeData.prepTime) // ISO 8601 duration
.addProperty('cookTime', recipeData.cookTime)
.addProperty('totalTime', recipeData.totalTime)
.addProperty('recipeCategory', recipeData.category)
.addProperty('recipeCuisine', recipeData.cuisine)
.addProperty('keywords', recipeData.keywords)
.addProperty('recipeIngredient', recipeData.ingredients)
.addProperty('recipeInstructions', recipeData.instructions?.map(step => ({
'@type': 'HowToStep',
text: step.text,
image: step.image,
name: step.name
})))
.addProperty('nutrition', recipeData.nutrition ? {
'@type': 'NutritionInformation',
calories: recipeData.nutrition.calories,
proteinContent: recipeData.nutrition.protein,
fatContent: recipeData.nutrition.fat,
carbohydrateContent: recipeData.nutrition.carbs,
fiberContent: recipeData.nutrition.fiber,
sugarContent: recipeData.nutrition.sugar,
sodiumContent: recipeData.nutrition.sodium
} : undefined)
.rating(recipeData.rating, recipeData.reviewCount)
.addProperty('video', recipeData.videoUrl ? {
'@type': 'VideoObject',
name: recipeData.name,
description: recipeData.description,
contentUrl: recipeData.videoUrl,
thumbnailUrl: recipeData.videoThumbnail
} : undefined)
.build();
},
// Restaurant menu
menu: (menuData) => {
const menuSections = menuData.sections?.map(section => ({
'@type': 'MenuSection',
name: section.name,
description: section.description,
hasMenuItem: section.items?.map(item => ({
'@type': 'MenuItem',
name: item.name,
description: item.description,
offers: {
'@type': 'Offer',
price: item.price,
priceCurrency: item.currency || 'USD'
},
nutrition: item.nutrition,
suitableForDiet: item.dietaryRestrictions
}))
}));
return createSchema('Menu')
.name(menuData.name)
.description(menuData.description)
.addProperty('hasMenuSection', menuSections)
.addProperty('provider', {
'@type': 'Restaurant',
name: menuData.restaurant
})
.build();
}
},
/**
* Media & Content Templates
*/
media: {
// Video content
video: (videoData) => {
return createSchema('VideoObject')
.name(videoData.title)
.description(videoData.description)
.addProperty('contentUrl', videoData.videoUrl)
.addProperty('embedUrl', videoData.embedUrl)
.addProperty('thumbnailUrl', videoData.thumbnail)
.addProperty('uploadDate', videoData.uploadDate)
.addProperty('duration', videoData.duration) // ISO 8601 duration
.addProperty('contentRating', videoData.rating)
.addProperty('genre', videoData.genre)
.addProperty('keywords', videoData.tags)
.addProperty('creator', {
'@type': 'Person',
name: videoData.creator,
url: videoData.creatorUrl
})
.addProperty('publisher', {
'@type': 'Organization',
name: videoData.publisher,
logo: videoData.publisherLogo
})
.addProperty('interactionStatistic', [
{
'@type': 'InteractionCounter',
interactionType: 'https://schema.org/WatchAction',
userInteractionCount: videoData.viewCount
},
{
'@type': 'InteractionCounter',
interactionType: 'https://schema.org/LikeAction',
userInteractionCount: videoData.likeCount
}
])
.build();
},
// Podcast episode
podcast: (episodeData) => {
return createSchema('PodcastEpisode')
.name(episodeData.title)
.description(episodeData.description)
.url(episodeData.url)
.addProperty('episodeNumber', episodeData.episodeNumber)
.addProperty('seasonNumber', episodeData.seasonNumber)
.addProperty('datePublished', episodeData.publishDate)
.addProperty('duration', episodeData.duration)
.addProperty('associatedMedia', {
'@type': 'MediaObject',
contentUrl: episodeData.audioUrl,
encodingFormat: episodeData.audioFormat || 'audio/mpeg'
})
.addProperty('partOfSeries', {
'@type': 'PodcastSeries',
name: episodeData.podcastName,
url: episodeData.podcastUrl
})
.addProperty('creator', episodeData.hosts?.map(host => ({
'@type': 'Person',
name: host.name,
url: host.url
})))
.addProperty('keywords', episodeData.tags)
.build();
},
// Software application
software: (appData) => {
return createSchema('SoftwareApp