@ai-growth/nextjs
Version:
Seamlessly integrate Sanity CMS with Next.js applications for automated blog routing and rendering
122 lines (121 loc) • 5.24 kB
JavaScript
import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
/**
* @fileoverview SEO Head Component
*
* This component provides comprehensive SEO functionality including
* meta tags, Open Graph, Twitter Cards, and JSON-LD structured data.
*/
import React from 'react';
import Head from 'next/head';
import { processSEO, sanitizeSEO } from '../utils/seo';
import { escapeJsonLD } from '../utils/seo/structured-data';
/**
* SEO Head component that generates comprehensive SEO metadata
*
* @example
* ```tsx
* <SEOHead
* content={post}
* siteSettings={siteSettings}
* template="article"
* url="https://example.com/blog/post-slug"
* />
* ```
*/
export const SEOHead = (props) => {
// Process SEO data and generate results
const seoResult = processSEO(props);
// Sanitize SEO data for optimal display
const sanitizedSEO = sanitizeSEO({
title: seoResult.title,
description: seoResult.description,
canonical: seoResult.canonical,
keywords: props.seo?.keywords || [],
noIndex: props.seo?.noIndex || false,
noFollow: props.seo?.noFollow || false
});
return (_jsxs(Head, { children: [renderMetaTags(seoResult.metaTags), _jsx("title", { children: sanitizedSEO.title }), sanitizedSEO.canonical && (_jsx("link", { rel: "canonical", href: sanitizedSEO.canonical })), props.seo?.alternateLanguages?.map((lang, index) => (_jsx("link", { rel: "alternate", hrefLang: `${lang.language}${lang.region ? `-${lang.region}` : ''}`, href: lang.url }, index))), seoResult.structuredData.map((data, index) => (_jsx("script", { type: "application/ld+json", dangerouslySetInnerHTML: {
__html: escapeJsonLD(data)
} }, index))), _jsx("link", { rel: "dns-prefetch", href: "//fonts.googleapis.com" }), _jsx("link", { rel: "dns-prefetch", href: "//cdn.sanity.io" }), _jsx("link", { rel: "preconnect", href: "https://fonts.gstatic.com", crossOrigin: "" })] }));
};
/**
* Render meta tags from the generated list
*
* @param metaTags - Array of meta tags to render
* @returns JSX elements for meta tags
*/
function renderMetaTags(metaTags) {
return metaTags.map((tag, index) => {
const key = `meta-${index}`;
// Handle charset meta tag
if (tag.charset) {
return _jsx("meta", { charSet: tag.charset }, key);
}
// Handle http-equiv meta tag
if (tag.httpEquiv) {
return _jsx("meta", { httpEquiv: tag.httpEquiv, content: tag.content }, key);
}
// Handle property meta tag (Open Graph)
if (tag.property) {
return _jsx("meta", { property: tag.property, content: tag.content }, key);
}
// Handle name meta tag (standard and Twitter)
if (tag.name) {
return _jsx("meta", { name: tag.name, content: tag.content }, key);
}
// Fallback for any other meta tag type
return _jsx("meta", { content: tag.content }, key);
});
}
/**
* SEO Head component with error boundary
*/
export const SafeSEOHead = (props) => {
try {
return _jsx(SEOHead, { ...props });
}
catch (error) {
console.error('SEO Head error:', error);
// Fallback SEO with basic meta tags
return (_jsxs(Head, { children: [_jsx("title", { children: props.content?.title || props.siteSettings?.title || 'AI Growth' }), _jsx("meta", { name: "description", content: props.content?.excerpt || props.siteSettings?.description || '' }), _jsx("meta", { name: "viewport", content: "width=device-width, initial-scale=1" }), _jsx("meta", { charSet: "utf-8" })] }));
}
};
/**
* Hook for accessing SEO data without rendering
*
* @param props - SEO Head props
* @returns Processed SEO result
*/
export function useSEOData(props) {
return React.useMemo(() => {
try {
return processSEO(props);
}
catch (error) {
console.error('SEO processing error:', error);
return {
metaTags: [],
structuredData: [],
title: props.content?.title || 'AI Growth',
description: props.content?.excerpt || '',
canonical: props.url || '',
image: undefined
};
}
}, [props]);
}
/**
* Utility component for JSON-LD structured data only
*
* @param props - Structured data props
*/
export const StructuredDataOnly = ({ data }) => (_jsx(Head, { children: data.map((item, index) => (_jsx("script", { type: "application/ld+json", dangerouslySetInnerHTML: {
__html: escapeJsonLD(item)
} }, index))) }));
/**
* Utility component for basic meta tags only
*
* @param props - Basic meta props
*/
export const BasicSEO = ({ title, description, canonical, noIndex }) => (_jsxs(Head, { children: [_jsx("title", { children: title }), description && _jsx("meta", { name: "description", content: description }), canonical && _jsx("link", { rel: "canonical", href: canonical }), _jsx("meta", { name: "viewport", content: "width=device-width, initial-scale=1" }), _jsx("meta", { charSet: "utf-8" }), noIndex && _jsx("meta", { name: "robots", content: "noindex, nofollow" })] }));
export default SEOHead;