UNPKG

@digacy/astro-seo

Version:
515 lines (465 loc) 16.1 kB
import { escape } from "html-escaper"; import type { AstroSeoProps, OpenGraphMedia } from "../types"; const createMetaTag = (attributes: Record<string, string>): string => { const attrs = Object.entries(attributes) .map(([key, value]) => `${key}="${escape(value)}"`) .join(" "); return `<meta ${attrs}>`; }; const createLinkTag = (attributes: Record<string, string>): string => { const attrs = Object.entries(attributes) .map(([key, value]) => `${key}="${escape(value)}"`) .join(" "); return `<link ${attrs}>`; }; const createOpenGraphTag = ( property: string, content: string, ogPrefix: boolean = true ): string => { return createMetaTag({ property: ogPrefix ? `og:${property}` : `${property}`, content, }); }; const buildOpenGraphMediaTags = ( mediaType: "image" | "video", media: ReadonlyArray<OpenGraphMedia> ): string => { let tags = ""; const addTag = (tag: string) => { tags += tag + "\n"; }; media.forEach((medium) => { addTag(createOpenGraphTag(mediaType, medium.url)); if (medium.alt) { addTag(createOpenGraphTag(`${mediaType}:alt`, medium.alt)); } if (medium.secureUrl) { addTag( createOpenGraphTag(`${mediaType}:secure_url`, medium.secureUrl) ); } if (medium.type) { addTag(createOpenGraphTag(`${mediaType}:type`, medium.type)); } if (medium.width) { addTag( createOpenGraphTag( `${mediaType}:width`, medium.width.toString() ) ); } if (medium.height) { addTag( createOpenGraphTag( `${mediaType}:height`, medium.height.toString() ) ); } }); return tags; }; export const buildTags = (config: AstroSeoProps): string => { let tagsToRender = ""; const addTag = (tag: string) => { tagsToRender += tag + "\n"; }; const addMetaTag = (attributes: Record<string, string>) => { addTag( `<meta ${Object.entries(attributes) .map(([key, value]) => `${key}="${escape(value)}"`) .join(" ")} />` ); }; const addLinkTag = (attributes: Record<string, string>) => { addTag( `<link ${Object.entries(attributes) .map(([key, value]) => `${key}="${escape(value)}"`) .join(" ")} />` ); }; const addOpenGraphTag = (property: string, content: string) => { addMetaTag({ property: `og:${property}`, content }); }; // Title if (config.title) { const formattedTitle = config.titleTemplate ? config.titleTemplate.replace("%s", config.title) : config.title; addTag(`<title>${escape(formattedTitle)}</title>`); } // Description if (config.description) { addTag( createMetaTag({ name: "description", content: config.description }) ); } // Robots: noindex, nofollow, and other robotsProps let robotsContent: string[] = []; if (typeof config.noindex !== "undefined") { robotsContent.push(config.noindex ? "noindex" : "index"); } if (typeof config.nofollow !== "undefined") { robotsContent.push(config.nofollow ? "nofollow" : "follow"); } if (config.robotsProps) { const { nosnippet, maxSnippet, maxImagePreview, noarchive, unavailableAfter, noimageindex, notranslate, } = config.robotsProps; if (nosnippet) robotsContent.push("nosnippet"); if (typeof maxSnippet === "number") robotsContent.push(`max-snippet:${maxSnippet}`); if (maxImagePreview) robotsContent.push(`max-image-preview:${maxImagePreview}`); if (noarchive) robotsContent.push("noarchive"); if (unavailableAfter) robotsContent.push(`unavailable_after:${unavailableAfter}`); if (noimageindex) robotsContent.push("noimageindex"); if (notranslate) robotsContent.push("notranslate"); } if (robotsContent.length > 0) { addTag( createMetaTag({ name: "robots", content: robotsContent.join(",") }) ); } // Canonical if (config.canonical) { addTag(createLinkTag({ rel: "canonical", href: config.canonical })); } // Mobile Alternate if (config.mobileAlternate) { addTag( createLinkTag({ rel: "alternate", media: config.mobileAlternate.media, href: config.mobileAlternate.href, }) ); } // Language Alternates if (config.languageAlternates && config.languageAlternates.length > 0) { config.languageAlternates.forEach((languageAlternate) => { addTag( createLinkTag({ rel: "alternate", hreflang: languageAlternate.hreflang, href: languageAlternate.href, }) ); }); } // OpenGraph if (config.openGraph) { const title = config.openGraph?.title || config.title; if (title) { addTag(createOpenGraphTag("title", title)); } const description = config.openGraph?.description || config.description; if (description) { addTag(createOpenGraphTag("description", description)); } if (config.openGraph.url) { addTag(createOpenGraphTag("url", config.openGraph.url)); } if (config.openGraph.type) { addTag(createOpenGraphTag("type", config.openGraph.type)); } if (config.openGraph.images && config.openGraph.images.length) { addTag(buildOpenGraphMediaTags("image", config.openGraph.images)); } if (config.openGraph.videos && config.openGraph.videos.length) { addTag(buildOpenGraphMediaTags("video", config.openGraph.videos)); } if (config.openGraph.locale) { addTag(createOpenGraphTag("locale", config.openGraph.locale)); } if (config.openGraph.site_name) { addTag(createOpenGraphTag("site_name", config.openGraph.site_name)); } // Open Graph Profile if (config.openGraph.profile) { if (config.openGraph.profile.firstName) { addTag( createOpenGraphTag( "profile:first_name", config.openGraph.profile.firstName, false ) ); } if (config.openGraph.profile.lastName) { addTag( createOpenGraphTag( "profile:last_name", config.openGraph.profile.lastName, false ) ); } if (config.openGraph.profile.username) { addTag( createOpenGraphTag( "profile:username", config.openGraph.profile.username, false ) ); } if (config.openGraph.profile.gender) { addTag( createOpenGraphTag( "profile:gender", config.openGraph.profile.gender, false ) ); } } // Open Graph Book if (config.openGraph.book) { if ( config.openGraph.book.authors && config.openGraph.book.authors.length ) { config.openGraph.book.authors.forEach((author) => { addTag(createOpenGraphTag("book:author", author, false)); }); } if (config.openGraph.book.isbn) { addTag( createOpenGraphTag( "book:isbn", config.openGraph.book.isbn, false ) ); } if (config.openGraph.book.releaseDate) { addTag( createOpenGraphTag( "book:release_date", config.openGraph.book.releaseDate, false ) ); } if ( config.openGraph.book.tags && config.openGraph.book.tags.length ) { config.openGraph.book.tags.forEach((tag) => { addTag(createOpenGraphTag("book:tag", tag, false)); }); } } // Open Graph Article if (config.openGraph.article) { if (config.openGraph.article.publishedTime) { addTag( createOpenGraphTag( "article:published_time", config.openGraph.article.publishedTime, false ) ); } if (config.openGraph.article.modifiedTime) { addTag( createOpenGraphTag( "article:modified_time", config.openGraph.article.modifiedTime, false ) ); } if (config.openGraph.article.expirationTime) { addTag( createOpenGraphTag( "article:expiration_time", config.openGraph.article.expirationTime, false ) ); } if ( config.openGraph.article.authors && config.openGraph.article.authors.length ) { config.openGraph.article.authors.forEach((author) => { addTag(createOpenGraphTag("article:author", author, false)); }); } if (config.openGraph.article.section) { addTag( createOpenGraphTag( "article:section", config.openGraph.article.section, false ) ); } if ( config.openGraph.article.tags && config.openGraph.article.tags.length ) { config.openGraph.article.tags.forEach((tag) => { addTag(createOpenGraphTag("article:tag", tag, false)); }); } } // Open Graph Video if (config.openGraph.video) { if ( config.openGraph.video.actors && config.openGraph.video.actors.length ) { config.openGraph.video.actors.forEach((actor) => { addTag(createOpenGraphTag("video:actor", actor.profile)); if (actor.role) { addTag( createOpenGraphTag("video:actor:role", actor.role) ); } }); } if ( config.openGraph.video.directors && config.openGraph.video.directors.length ) { config.openGraph.video.directors.forEach((director) => { addTag(createOpenGraphTag("video:director", director)); }); } if ( config.openGraph.video.writers && config.openGraph.video.writers.length ) { config.openGraph.video.writers.forEach((writer) => { addTag(createOpenGraphTag("video:writer", writer)); }); } if (config.openGraph.video.duration) { addTag( createOpenGraphTag( "video:duration", config.openGraph.video.duration.toString() ) ); } if (config.openGraph.video.releaseDate) { addTag( createOpenGraphTag( "video:release_date", config.openGraph.video.releaseDate ) ); } if ( config.openGraph.video.tags && config.openGraph.video.tags.length ) { config.openGraph.video.tags.forEach((tag) => { addTag(createOpenGraphTag("video:tag", tag)); }); } if (config.openGraph.video.series) { addTag( createOpenGraphTag( "video:series", config.openGraph.video.series ) ); } } } // Facebook if (config.facebook && config.facebook.appId) { addTag( createMetaTag({ property: "fb:app_id", content: config.facebook.appId, }) ); } // Twitter if (config.twitter) { if (config.twitter.cardType) { addTag( createMetaTag({ name: "twitter:card", content: config.twitter.cardType, }) ); } if (config.twitter.site) { addTag( createMetaTag({ name: "twitter:site", content: config.twitter.site, }) ); } if (config.twitter.handle) { addTag( createMetaTag({ name: "twitter:creator", content: config.twitter.handle, }) ); } } // Additional Meta Tags if (config.additionalMetaTags && config.additionalMetaTags.length > 0) { config.additionalMetaTags.forEach((metaTag) => { const attributes: Record<string, string> = { content: metaTag.content, }; if ("name" in metaTag && metaTag.name) { attributes.name = metaTag.name; } else if ("property" in metaTag && metaTag.property) { attributes.property = metaTag.property; } else if ("httpEquiv" in metaTag && metaTag.httpEquiv) { attributes["http-equiv"] = metaTag.httpEquiv; } addTag(createMetaTag(attributes)); }); } // Additional Link Tags if (config.additionalLinkTags && config.additionalLinkTags.length > 0) { config.additionalLinkTags.forEach((linkTag) => { const attributes: Record<string, string> = { rel: linkTag.rel, href: linkTag.href, }; if (linkTag.sizes) { attributes.sizes = linkTag.sizes; } if (linkTag.media) { attributes.media = linkTag.media; } if (linkTag.type) { attributes.type = linkTag.type; } if (linkTag.color) { attributes.color = linkTag.color; } if (linkTag.as) { attributes.as = linkTag.as; } if (linkTag.crossOrigin) { attributes.crossorigin = linkTag.crossOrigin; } addTag(createLinkTag(attributes)); }); } return tagsToRender.trim(); };