<\/div>/i;
if (!fallbackHtml || !rootPattern.test(html)) {
return html;
}
return html.replace(rootPattern, `
${fallbackHtml}
`);
}
function buildHomeMeta({ catalogCount, imageUrl, canonicalUrl }) {
const visibleCount = Math.max(catalogCount, HOME_CATALOG_COUNT_FALLBACK);
const formattedCount = visibleCount.toLocaleString('en-US');
const title = `Antigravity Awesome Skills GitHub | ${formattedCount}+ AI coding skills`;
const description = `Explore the GitHub library of ${formattedCount}+ installable agentic skills, specialized plugins, bundles, and workflows for Claude Code, Cursor, Codex CLI, Gemini CLI, Antigravity, and other AI coding assistants.`;
const catalogBaseUrl = canonicalUrl.replace(/\/$/, '');
const sourceCodeEntity = {
'@context': 'https://schema.org',
'@type': 'SoftwareSourceCode',
name: SITE_NAME,
description: `Installable GitHub library of ${formattedCount}+ agentic skills, specialized plugins, bundles, and workflows for AI coding assistants.`,
url: REPOSITORY_URL,
sameAs: [
canonicalUrl,
HOSTED_CATALOG_URL,
'https://www.npmjs.com/package/antigravity-awesome-skills',
],
mainEntityOfPage: canonicalUrl,
codeRepository: REPOSITORY_URL,
applicationCategory: 'DeveloperApplication',
keywords: [
'AI coding assistant skills',
'Claude Code skills',
'Codex CLI skills',
'Cursor skills',
'Gemini CLI skills',
'Antigravity skills',
'Antigravity CLI skills',
'GitHub AI skills repository',
'AI agent skills GitHub',
'specialized plugins',
'SKILL.md',
],
isAccessibleForFree: true,
programmingLanguage: {
'@type': 'ComputerLanguage',
name: 'Markdown',
url: 'https://en.wikipedia.org/wiki/Markdown',
},
license: `${REPOSITORY_URL}/blob/main/LICENSE`,
};
return {
title,
description,
canonicalUrl,
ogTitle: title,
ogDescription: description,
ogImage: imageUrl,
twitterCard: 'summary_large_image',
jsonLd: [
{
'@context': 'https://schema.org',
'@type': 'CollectionPage',
name: SITE_NAME,
description,
url: canonicalUrl,
sameAs: REPOSITORY_URL,
about: sourceCodeEntity,
isPartOf: {
'@type': 'WebSite',
name: SITE_NAME,
url: catalogBaseUrl,
},
mainEntity: {
'@type': 'ItemList',
name: `${SITE_NAME} catalog`,
},
},
{
'@context': 'https://schema.org',
'@type': 'Organization',
'@id': `${REPOSITORY_URL}#organization`,
name: SITE_NAME,
url: REPOSITORY_URL,
sameAs: [
'https://x.com/AASkills_',
'https://www.npmjs.com/package/antigravity-awesome-skills',
HOSTED_CATALOG_URL,
],
},
{
'@context': 'https://schema.org',
'@type': 'WebSite',
name: SITE_NAME,
url: catalogBaseUrl,
sameAs: REPOSITORY_URL,
inLanguage: 'en',
potentialAction: {
'@type': 'SearchAction',
target: `${catalogBaseUrl}/?q={search_term_string}`,
'query-input': 'required name=search_term_string',
},
},
sourceCodeEntity,
{
'@context': 'https://schema.org',
'@type': 'FAQPage',
url: canonicalUrl,
mainEntity: buildFaqItems(`${formattedCount}+`).map((item) => ({
'@type': 'Question',
name: item.question,
acceptedAnswer: {
'@type': 'Answer',
text: item.answer,
},
})),
},
],
};
}
function buildSkillMeta({ skill, isPriority, imageUrl, canonicalUrl }) {
const safeName = safeText(skill.name) || 'Unnamed skill';
const safeDescription = safeText(skill.description) || 'Installable AI skill';
const safeCategory = safeText(skill.category) || 'Tools';
const safeSource = safeText(skill.source) || 'community contributors';
const added = skill.date_added ? `Added ${skill.date_added}. ` : '';
const trust = isPriority ? ' Prioritized in our catalog for quality and reuse. ' : ' ';
const title = `${safeName} | ${SITE_NAME}`;
const description = `${added}Use the @${safeName} skill for ${safeDescription} (${safeCategory}, ${safeSource}).${trust}Install and run quickly with your CLI workflow.`;
return {
title,
description,
canonicalUrl,
ogTitle: `@${safeName} | ${SITE_NAME}`,
ogDescription: description,
ogImage: imageUrl,
twitterCard: 'summary',
jsonLd: {
'@context': 'https://schema.org',
'@type': 'SoftwareApplication',
'@id': canonicalUrl,
name: `@${safeName}`,
applicationCategory: safeCategory,
description,
url: canonicalUrl,
offers: {
'@type': 'Offer',
price: '0',
priceCurrency: 'USD',
availability: 'https://schema.org/InStock',
},
provider: {
'@type': 'Organization',
name: SITE_NAME,
},
keywords: [safeCategory, safeSource],
inLanguage: 'en',
operatingSystem: 'Cross-platform',
isPartOf: {
'@type': 'CollectionPage',
name: SITE_NAME,
},
},
};
}
function buildPluginsMeta({ pluginCount, imageUrl, canonicalUrl }) {
const countLabel = pluginCount > 0 ? `${pluginCount.toLocaleString('en-US')} ` : '';
const description = `Compare ${countLabel}specialized plugin packs for web apps, security, data analytics, documents, DevOps, QA, OSS maintenance, mobile apps, automation, and agent or MCP systems.`;
return {
title: `AAS Specialized Plugins | ${countLabel}AI coding workflow packs`,
description,
canonicalUrl,
ogTitle: 'AAS Specialized Plugins | AI coding workflow packs',
ogDescription: description,
ogImage: imageUrl,
twitterCard: 'summary_large_image',
jsonLd: [
{
'@context': 'https://schema.org',
'@type': 'CollectionPage',
name: 'AAS Specialized Plugins',
description,
url: canonicalUrl,
isPartOf: {
'@type': 'WebSite',
name: SITE_NAME,
url: canonicalUrl.replace(/\/plugins\/?$/, ''),
sameAs: REPOSITORY_URL,
},
mainEntity: {
'@type': 'ItemList',
name: 'AAS specialized plugin packs',
numberOfItems: pluginCount,
},
},
{
'@context': 'https://schema.org',
'@type': 'Organization',
'@id': `${REPOSITORY_URL}#organization`,
name: SITE_NAME,
url: REPOSITORY_URL,
},
],
};
}
function buildTopicLandingMeta({ page, imageUrl, canonicalUrl }) {
const catalogBaseUrl = canonicalUrl.replace(/\/topics\/[^/]+\/?$/, '');
const keywords = Array.isArray(page.keywords) ? page.keywords.join(', ') : '';
const sourceCodeEntity = {
'@context': 'https://schema.org',
'@type': 'SoftwareSourceCode',
name: SITE_NAME,
description: 'Installable GitHub library of agentic skills, specialized plugins, bundles, and workflows for AI coding assistants.',
url: REPOSITORY_URL,
sameAs: [
canonicalUrl,
HOSTED_CATALOG_URL,
'https://www.npmjs.com/package/antigravity-awesome-skills',
],
mainEntityOfPage: canonicalUrl,
codeRepository: REPOSITORY_URL,
applicationCategory: 'DeveloperApplication',
keywords: [
...(Array.isArray(page.keywords) ? page.keywords : []),
'specialized plugins',
'SKILL.md',
],
isAccessibleForFree: true,
programmingLanguage: {
'@type': 'ComputerLanguage',
name: 'Markdown',
url: 'https://en.wikipedia.org/wiki/Markdown',
},
license: `${REPOSITORY_URL}/blob/main/LICENSE`,
};
return {
title: page.title,
description: page.description,
canonicalUrl,
ogTitle: page.title,
ogDescription: page.description,
ogImage: imageUrl,
twitterCard: 'summary_large_image',
jsonLd: [
{
'@context': 'https://schema.org',
'@type': 'WebPage',
name: page.h1,
headline: page.h1,
description: page.description,
url: canonicalUrl,
isPartOf: {
'@type': 'WebSite',
name: SITE_NAME,
url: catalogBaseUrl,
sameAs: REPOSITORY_URL,
},
about: sourceCodeEntity,
keywords,
mainEntity: {
'@type': 'ItemList',
name: `${page.eyebrow} topics`,
itemListElement: Array.isArray(page.sections)
? page.sections.map((section, index) => ({
'@type': 'ListItem',
position: index + 1,
name: section.heading,
description: section.body,
}))
: [],
},
},
{
'@context': 'https://schema.org',
'@type': 'BreadcrumbList',
itemListElement: [
{
'@type': 'ListItem',
position: 1,
name: SITE_NAME,
item: HOSTED_CATALOG_URL,
},
{
'@type': 'ListItem',
position: 2,
name: page.h1,
item: canonicalUrl,
},
],
},
{
'@context': 'https://schema.org',
'@type': 'Organization',
'@id': `${REPOSITORY_URL}#organization`,
name: SITE_NAME,
url: REPOSITORY_URL,
sameAs: [
'https://x.com/AASkills_',
'https://www.npmjs.com/package/antigravity-awesome-skills',
HOSTED_CATALOG_URL,
],
},
{
'@context': 'https://schema.org',
'@type': 'WebSite',
name: SITE_NAME,
url: catalogBaseUrl,
sameAs: REPOSITORY_URL,
inLanguage: 'en',
potentialAction: {
'@type': 'SearchAction',
target: `${catalogBaseUrl}/?q={search_term_string}`,
'query-input': 'required name=search_term_string',
},
},
sourceCodeEntity,
],
};
}
function applySeoMeta(templateHtml, meta) {
let output = templateHtml;
const title = safeText(meta.title);
const description = safeText(meta.description);
const canonical = safeText(meta.canonicalUrl);
const ogTitle = safeText(meta.ogTitle || title);
const ogDescription = safeText(meta.ogDescription || description);
const ogImage = safeText(meta.ogImage);
output = setTitleTag(output, title);
output = setMetaTag(output, 'name', 'description', description);
output = setMetaTag(output, 'property', 'og:type', 'website');
output = setMetaTag(output, 'property', 'og:title', ogTitle);
output = setMetaTag(output, 'property', 'og:description', ogDescription);
output = setMetaTag(output, 'property', 'og:site_name', SITE_NAME);
output = setMetaTag(output, 'property', 'og:url', canonical);
output = setMetaTag(output, 'name', 'twitter:card', safeText(meta.twitterCard || 'summary'));
output = setMetaTag(output, 'name', 'twitter:title', ogTitle);
output = setMetaTag(output, 'name', 'twitter:description', ogDescription);
output = setMetaTag(output, 'name', 'twitter:image:alt', `${ogTitle} preview`);
output = setMetaTag(output, 'property', 'og:image', ogImage);
output = setMetaTag(output, 'name', 'twitter:image', ogImage);
output = setLinkTag(output, 'canonical', canonical);
output = setJsonLdTag(output, meta.jsonLd);
return output;
}
function writePrerenderedRoute(routePath, templateHtml, meta, fallbackHtml = '') {
const filePath = routeToFilePath(routePath);
const rendered = setRootFallback(applySeoMeta(templateHtml, meta), fallbackHtml);
const directory = path.dirname(filePath);
ensureDirectory(directory);
fs.writeFileSync(filePath, rendered, 'utf-8');
}
function readCatalog() {
if (!fs.existsSync(SKILLS_PATH)) {
throw new Error(`Skills catalog not found at ${SKILLS_PATH}`);
}
const raw = fs.readFileSync(SKILLS_PATH, 'utf-8');
const parsed = JSON.parse(raw);
if (!Array.isArray(parsed)) {
throw new Error('Skills catalog must be an array.');
}
return parsed;
}
function readSeoLandingPages() {
if (!fs.existsSync(SEO_LANDING_PAGES_PATH)) {
return [];
}
const raw = fs.readFileSync(SEO_LANDING_PAGES_PATH, 'utf-8');
const parsed = JSON.parse(raw);
if (!Array.isArray(parsed)) {
throw new Error('SEO landing pages data must be an array.');
}
return parsed;
}
function main() {
if (!fs.existsSync(TEMPLATE_PATH)) {
throw new Error(`Built index file not found at ${TEMPLATE_PATH}. Run npm run build before prerender.`);
}
const template = fs.readFileSync(TEMPLATE_PATH, 'utf-8');
const skills = readCatalog();
const landingPages = readSeoLandingPages();
const siteBaseUrl = getSiteBaseUrl();
const topCount = parseCount(process.env.PRERENDER_TOP_SKILL_COUNT || process.env.TOP_SKILL_COUNT, 40);
const topSkillPaths = selectTopSkillEntries(skills, topCount);
const skillMap = new Map(skills.map((skill) => [skill.id, skill]));
const topSkillSet = new Set(topSkillPaths.map((routePath) => routePath.replace(/^\/skill\//, '')));
const socialImage = `${siteBaseUrl.replace(/\/+$/, '')}/${PRERENDER_SOCIAL_IMAGE}`;
const homeCanonical = routeToUrl('/', siteBaseUrl);
const homeMeta = buildHomeMeta({
catalogCount: skills.length,
imageUrl: socialImage,
canonicalUrl: homeCanonical,
});
writePrerenderedRoute('/', template, homeMeta);
const pluginsCanonical = routeToUrl('/plugins', siteBaseUrl);
const pluginsMeta = buildPluginsMeta({
pluginCount: 15,
imageUrl: socialImage,
canonicalUrl: pluginsCanonical,
});
writePrerenderedRoute('/plugins', template, pluginsMeta);
for (const page of landingPages) {
if (!page?.slug) {
continue;
}
const routePath = `/topics/${encodeURIComponent(page.slug)}`;
const canonicalUrl = routeToUrl(routePath, siteBaseUrl);
const landingMeta = buildTopicLandingMeta({
page,
imageUrl: socialImage,
canonicalUrl,
});
writePrerenderedRoute(
routePath,
template,
landingMeta,
buildTopicFallback({ page, landingPages, siteBaseUrl }),
);
}
for (const skillRoute of topSkillPaths) {
const decodedId = decodeURIComponent(skillRoute.replace(/^\/skill\//, ''));
const skill = skillMap.get(decodedId);
if (!skill) {
continue;
}
const canonicalUrl = routeToUrl(skillRoute, siteBaseUrl);
const skillMeta = buildSkillMeta({
skill,
isPriority: topSkillSet.has(encodeURIComponent(decodedId)),
imageUrl: socialImage,
canonicalUrl,
});
writePrerenderedRoute(
skillRoute,
template,
skillMeta,
buildSkillFallback({ skill, landingPages, siteBaseUrl }),
);
}
}
main();