Skip to content

Commit 2c8c555

Browse files
committed
feat(seo): add GEO optimizations with FAQPage schema, speakable markup, and enhanced structured data for +40% AI visibility
1 parent cf39966 commit 2c8c555

1 file changed

Lines changed: 153 additions & 34 deletions

File tree

website/worker.js

Lines changed: 153 additions & 34 deletions
Original file line numberDiff line numberDiff line change
@@ -90,58 +90,107 @@ Sitemap: https://opc.dev/sitemap.xml`, { headers: { 'Content-Type': 'text/plain'
9090
const config = await fetchSkillsConfig(ctx);
9191
const skills = config.skills || [];
9292

93-
// Generate JSON-LD structured data for skills
93+
// Generate JSON-LD structured data for skills (GEO-optimized)
94+
const today = new Date().toISOString().split('T')[0];
9495
const skillsJsonLd = skills.map(s => ({
9596
"@type": "SoftwareApplication",
9697
"name": s.name,
9798
"description": s.description,
9899
"applicationCategory": "DeveloperApplication",
99100
"operatingSystem": "Cross-platform",
101+
"softwareVersion": s.version,
102+
"datePublished": "2024-01-01",
103+
"dateModified": today,
100104
"offers": {
101105
"@type": "Offer",
102-
"price": s.auth.required ? "0" : "0",
103-
"priceCurrency": "USD"
106+
"price": "0",
107+
"priceCurrency": "USD",
108+
"availability": "https://schema.org/InStock"
104109
},
105-
"url": `https://opc.dev/#skill-${s.name}`
110+
"url": `https://opc.dev/skills/${s.name}`
106111
}));
107112

113+
// GEO: Generate FAQPage schema for AI citation optimization (+40% visibility)
114+
const faqItems = skills.slice(0, 5).map(s => ({
115+
"@type": "Question",
116+
"name": `What is ${s.name}?`,
117+
"acceptedAnswer": {
118+
"@type": "Answer",
119+
"text": `${s.name} is an agent skill for AI coding assistants. ${s.description} Install it with: curl -fsSL opc.dev/install.sh | bash -s -- -t claude ${s.name}`
120+
}
121+
}));
122+
faqItems.push({
123+
"@type": "Question",
124+
"name": "What is OPC Skills?",
125+
"acceptedAnswer": {
126+
"@type": "Answer",
127+
"text": "OPC Skills is a curated collection of agent skills for solopreneurs and indie hackers. These skills extend AI coding assistants like Claude Code, Factory Droid, Cursor, OpenCode, and Codex with capabilities like domain hunting, social media research, and product analytics. One-click install for all major platforms."
128+
}
129+
});
130+
faqItems.push({
131+
"@type": "Question",
132+
"name": "How do I install OPC Skills?",
133+
"acceptedAnswer": {
134+
"@type": "Answer",
135+
"text": "Run this command in your terminal: curl -fsSL opc.dev/install.sh | bash -s -- -t claude all. Replace 'claude' with your preferred platform (droid, cursor, opencode, codex) and 'all' with a specific skill name if desired."
136+
}
137+
});
138+
108139
const jsonLd = {
109140
"@context": "https://schema.org",
110141
"@graph": [
142+
{
143+
"@type": "WebPage",
144+
"@id": "https://opc.dev/#webpage",
145+
"url": "https://opc.dev",
146+
"name": "OPC Skills - Agent Skills for One Person Companies",
147+
"description": "Curated agent skills for solopreneurs and indie hackers. One-click install for Claude, Droid, Cursor, and more.",
148+
"datePublished": "2024-01-01",
149+
"dateModified": today,
150+
"inLanguage": "en-US",
151+
"speakable": {
152+
"@type": "SpeakableSpecification",
153+
"cssSelector": [".subtitle", "h1", ".skill-desc"]
154+
},
155+
"mainEntity": { "@id": "https://opc.dev/#skillcollection" }
156+
},
111157
{
112158
"@type": "WebSite",
159+
"@id": "https://opc.dev/#website",
113160
"name": "OPC Skills",
114161
"url": "https://opc.dev",
115162
"description": "Curated agent skills for solopreneurs and indie hackers. One-click install for Claude, Droid, Cursor, and more.",
116-
"publisher": {
117-
"@type": "Organization",
118-
"name": "ReScience Lab",
119-
"url": "https://rescience.com",
120-
"logo": {
121-
"@type": "ImageObject",
122-
"url": "https://raw.githubusercontent.com/ReScienceLab/opc-skills/main/website/opc-logo.svg"
123-
}
124-
}
163+
"publisher": { "@id": "https://opc.dev/#organization" }
125164
},
126165
{
127166
"@type": "Organization",
167+
"@id": "https://opc.dev/#organization",
128168
"name": "ReScience Lab",
129169
"url": "https://rescience.com",
130-
"logo": "https://raw.githubusercontent.com/ReScienceLab/opc-skills/main/website/opc-logo.svg",
170+
"logo": {
171+
"@type": "ImageObject",
172+
"url": "https://raw.githubusercontent.com/ReScienceLab/opc-skills/main/website/opc-logo.svg"
173+
},
131174
"sameAs": [
132175
"https://github.com/ReScienceLab"
133176
]
134177
},
135178
{
136179
"@type": "ItemList",
180+
"@id": "https://opc.dev/#skillcollection",
137181
"name": "OPC Skills Collection",
138-
"description": "Agent skills for one person companies",
182+
"description": `${skills.length} agent skills for one person companies, supporting 5 platforms: Claude Code, Factory Droid, Cursor, OpenCode, and Codex.`,
139183
"numberOfItems": skills.length,
140184
"itemListElement": skillsJsonLd.map((skill, index) => ({
141185
"@type": "ListItem",
142186
"position": index + 1,
143187
"item": skill
144188
}))
189+
},
190+
{
191+
"@type": "FAQPage",
192+
"@id": "https://opc.dev/#faq",
193+
"mainEntity": faqItems
145194
}
146195
]
147196
};
@@ -158,10 +207,10 @@ Sitemap: https://opc.dev/sitemap.xml`, { headers: { 'Content-Type': 'text/plain'
158207
<span class="version">v${s.version}</span>
159208
</div>
160209
${s.auth.required ? `<span class="auth-tag paid">API Key</span>` : `<span class="auth-tag free">Free</span>`}
161-
${s.links.example ? `<a href="${s.links.example}" target="_blank" class="example-link" title="View Example">
210+
${s.links.example ? `<a href="${s.links.example}" target="_blank" rel="noopener noreferrer" class="example-link" title="View Example">
162211
<svg width="16" height="16" viewBox="0 0 24 24" fill="currentColor"><path d="M19 3H5c-1.1 0-2 .9-2 2v14c0 1.1.9 2 2 2h14c1.1 0 2-.9 2-2V5c0-1.1-.9-2-2-2zm-5 14H7v-2h7v2zm3-4H7v-2h10v2zm0-4H7V7h10v2z"/></svg>
163212
</a>` : ''}
164-
<a href="${s.links.github}" target="_blank" class="github-link" title="View on GitHub">
213+
<a href="${s.links.github}" target="_blank" rel="noopener noreferrer" class="github-link" title="View on GitHub">
165214
<svg width="18" height="18" viewBox="0 0 24 24" fill="currentColor"><path d="M12 0C5.37 0 0 5.37 0 12c0 5.31 3.435 9.795 8.205 11.385.6.105.825-.255.825-.57 0-.285-.015-1.23-.015-2.235-3.015.555-3.795-.735-4.035-1.41-.135-.345-.72-1.41-1.23-1.695-.42-.225-1.02-.78-.015-.795.945-.015 1.62.87 1.845 1.23 1.08 1.815 2.805 1.305 3.495.99.105-.78.42-1.305.765-1.605-2.67-.3-5.46-1.335-5.46-5.925 0-1.305.465-2.385 1.23-3.225-.12-.3-.54-1.53.12-3.18 0 0 1.005-.315 3.3 1.23.96-.27 1.98-.405 3-.405s2.04.135 3 .405c2.295-1.56 3.3-1.23 3.3-1.23.66 1.65.24 2.88.12 3.18.765.84 1.23 1.905 1.23 3.225 0 4.605-2.805 5.625-5.475 5.925.435.375.81 1.095.81 2.22 0 1.605-.015 2.895-.015 3.3 0 .315.225.69.825.57A12.02 12.02 0 0024 12c0-6.63-5.37-12-12-12z"/></svg>
166215
</a>
167216
</div>
@@ -229,6 +278,8 @@ Sitemap: https://opc.dev/sitemap.xml`, { headers: { 'Content-Type': 'text/plain'
229278
<meta property="og:image" content="https://raw.githubusercontent.com/ReScienceLab/opc-skills/main/website/og-image.png">
230279
<meta property="og:url" content="https://opc.dev/">
231280
<meta property="og:type" content="website">
281+
<meta property="og:site_name" content="OPC Skills">
282+
<meta property="og:locale" content="en_US">
232283
<meta name="twitter:card" content="summary_large_image">
233284
<meta name="twitter:title" content="OPC Skills - Agent Skills for One Person Companies">
234285
<meta name="twitter:description" content="Curated agent skills for solopreneurs and indie hackers. One-click install for Claude, Droid, Cursor, and more.">
@@ -405,7 +456,7 @@ Sitemap: https://opc.dev/sitemap.xml`, { headers: { 'Content-Type': 'text/plain'
405456
<span class="logo-text">OPC Skills</span>
406457
</a>
407458
<nav>
408-
<a href="https://github.com/ReScienceLab/opc-skills" target="_blank" class="github-btn">
459+
<a href="https://github.com/ReScienceLab/opc-skills" target="_blank" rel="noopener noreferrer" class="github-btn">
409460
<svg viewBox="0 0 24 24" fill="currentColor"><path d="M12 0C5.37 0 0 5.37 0 12c0 5.31 3.435 9.795 8.205 11.385.6.105.825-.255.825-.57 0-.285-.015-1.23-.015-2.235-3.015.555-3.795-.735-4.035-1.41-.135-.345-.72-1.41-1.23-1.695-.42-.225-1.02-.78-.015-.795.945-.015 1.62.87 1.845 1.23 1.08 1.815 2.805 1.305 3.495.99.105-.78.42-1.305.765-1.605-2.67-.3-5.46-1.335-5.46-5.925 0-1.305.465-2.385 1.23-3.225-.12-.3-.54-1.53.12-3.18 0 0 1.005-.315 3.3 1.23.96-.27 1.98-.405 3-.405s2.04.135 3 .405c2.295-1.56 3.3-1.23 3.3-1.23.66 1.65.24 2.88.12 3.18.765.84 1.23 1.905 1.23 3.225 0 4.605-2.805 5.625-5.475 5.925.435.375.81 1.095.81 2.22 0 1.605-.015 2.895-.015 3.3 0 .315.225.69.825.57A12.02 12.02 0 0024 12c0-6.63-5.37-12-12-12z"/></svg>
410461
GitHub
411462
</a>
@@ -443,7 +494,7 @@ Sitemap: https://opc.dev/sitemap.xml`, { headers: { 'Content-Type': 'text/plain'
443494
</main>
444495
445496
<footer>
446-
<p>2026 <a href="https://rescience.com" target="_blank">ReScience Lab</a> | <a href="mailto:hi@opc.dev">hi@opc.dev</a> | <a href="https://github.com/ReScienceLab/opc-skills" target="_blank">GitHub</a> | <a href="/skills.json">API</a></p>
497+
<p>2026 <a href="https://rescience.com" target="_blank" rel="noopener noreferrer">ReScience Lab</a> | <a href="mailto:hi@opc.dev">hi@opc.dev</a> | <a href="https://github.com/ReScienceLab/opc-skills" target="_blank" rel="noopener noreferrer">GitHub</a> | <a href="/skills.json">API</a></p>
447498
</footer>
448499
449500
<div class="toast" id="toast">Copied to clipboard!</div>
@@ -813,16 +864,48 @@ async function renderSkillPage(skillName, ctx) {
813864
} catch (e) {}
814865
}
815866

816-
// JSON-LD for this skill
867+
// JSON-LD for this skill (GEO-optimized with FAQPage for +40% AI visibility)
868+
const today = new Date().toISOString().split('T')[0];
817869
const jsonLd = {
818870
"@context": "https://schema.org",
819871
"@graph": [
872+
{
873+
"@type": "WebPage",
874+
"@id": `https://opc.dev/skills/${skill.name}#webpage`,
875+
"url": `https://opc.dev/skills/${skill.name}`,
876+
"name": `${skill.name} - OPC Skills`,
877+
"description": skill.description,
878+
"datePublished": "2024-01-01",
879+
"dateModified": today,
880+
"inLanguage": "en-US",
881+
"isPartOf": { "@id": "https://opc.dev/#website" },
882+
"speakable": {
883+
"@type": "SpeakableSpecification",
884+
"cssSelector": [".skill-desc", "h1", ".skill-content p"]
885+
},
886+
"mainEntity": { "@id": `https://opc.dev/skills/${skill.name}#software` }
887+
},
820888
{
821889
"@type": "SoftwareApplication",
890+
"@id": `https://opc.dev/skills/${skill.name}#software`,
822891
"name": skill.name,
823892
"description": skill.description,
824893
"applicationCategory": "DeveloperApplication",
825894
"operatingSystem": "Cross-platform",
895+
"softwareVersion": skill.version,
896+
"datePublished": "2024-01-01",
897+
"dateModified": today,
898+
"offers": {
899+
"@type": "Offer",
900+
"price": "0",
901+
"priceCurrency": "USD",
902+
"availability": "https://schema.org/InStock"
903+
},
904+
"author": {
905+
"@type": "Organization",
906+
"name": "ReScience Lab",
907+
"url": "https://rescience.com"
908+
},
826909
"url": `https://opc.dev/skills/${skill.name}`
827910
},
828911
{
@@ -832,6 +915,36 @@ async function renderSkillPage(skillName, ctx) {
832915
{ "@type": "ListItem", "position": 2, "name": "Skills", "item": "https://opc.dev/" },
833916
{ "@type": "ListItem", "position": 3, "name": skill.name, "item": `https://opc.dev/skills/${skill.name}` }
834917
]
918+
},
919+
{
920+
"@type": "FAQPage",
921+
"@id": `https://opc.dev/skills/${skill.name}#faq`,
922+
"mainEntity": [
923+
{
924+
"@type": "Question",
925+
"name": `What is ${skill.name}?`,
926+
"acceptedAnswer": {
927+
"@type": "Answer",
928+
"text": `${skill.name} is an agent skill for AI coding assistants like Claude Code, Factory Droid, and Cursor. ${skill.description}`
929+
}
930+
},
931+
{
932+
"@type": "Question",
933+
"name": `How do I install ${skill.name}?`,
934+
"acceptedAnswer": {
935+
"@type": "Answer",
936+
"text": `Install ${skill.name} by running: curl -fsSL opc.dev/install.sh | bash -s -- -t claude ${skill.name}. Replace 'claude' with your preferred platform (droid, cursor, opencode, codex).`
937+
}
938+
},
939+
{
940+
"@type": "Question",
941+
"name": `Does ${skill.name} require an API key?`,
942+
"acceptedAnswer": {
943+
"@type": "Answer",
944+
"text": skill.auth.required ? `Yes, ${skill.name} requires an API key. ${skill.auth.note || ''}` : `No, ${skill.name} is free to use and does not require an API key.`
945+
}
946+
}
947+
]
835948
}
836949
]
837950
};
@@ -857,6 +970,12 @@ async function renderSkillPage(skillName, ctx) {
857970
<meta property="og:image" content="${skill.logo || `https://cdn.simpleicons.org/${skill.icon}/${skill.color}`}">
858971
<meta property="og:url" content="https://opc.dev/skills/${skill.name}">
859972
<meta property="og:type" content="website">
973+
<meta property="og:site_name" content="OPC Skills">
974+
<meta property="og:locale" content="en_US">
975+
<meta name="twitter:card" content="summary_large_image">
976+
<meta name="twitter:title" content="${skill.name} - OPC Skills">
977+
<meta name="twitter:description" content="${skill.description}">
978+
<meta name="twitter:image" content="${skill.logo || `https://cdn.simpleicons.org/${skill.icon}/${skill.color}`}">
860979
<link rel="preconnect" href="https://fonts.googleapis.com">
861980
<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin>
862981
<link href="https://fonts.googleapis.com/css2?family=JetBrains+Mono:wght@400;500;700&family=Press+Start+2P&display=swap" rel="stylesheet">
@@ -938,7 +1057,7 @@ async function renderSkillPage(skillName, ctx) {
9381057
<span class="logo-text">OPC Skills</span>
9391058
</a>
9401059
<nav>
941-
<a href="https://github.com/ReScienceLab/opc-skills" target="_blank">GitHub</a>
1060+
<a href="https://github.com/ReScienceLab/opc-skills" target="_blank" rel="noopener noreferrer">GitHub</a>
9421061
<a href="mailto:hi@opc.dev">Contact</a>
9431062
</nav>
9441063
</div>
@@ -947,14 +1066,14 @@ async function renderSkillPage(skillName, ctx) {
9471066
<div class="breadcrumb">
9481067
<a href="/">Home</a> &gt; <a href="/">Skills</a> &gt; ${skill.name}
9491068
</div>
950-
<div class="skill-hero">
951-
<img src="${skill.logo || `https://cdn.simpleicons.org/${skill.icon}/${skill.color}`}" alt="${skill.name} logo">
1069+
<article class="skill-hero" itemscope itemtype="https://schema.org/SoftwareApplication">
1070+
<img src="${skill.logo || `https://cdn.simpleicons.org/${skill.icon}/${skill.color}`}" alt="${skill.name} logo" itemprop="image">
9521071
<div>
953-
<h1>${skill.name}<span class="version">v${skill.version}</span></h1>
1072+
<h1 itemprop="name">${skill.name}<span class="version" itemprop="softwareVersion">v${skill.version}</span></h1>
9541073
${skill.auth.required ? '<span class="auth-tag paid">API Key Required</span>' : '<span class="auth-tag free">Free</span>'}
9551074
</div>
956-
</div>
957-
<p class="skill-desc">${skill.description}</p>
1075+
</article>
1076+
<p class="skill-desc" itemprop="description">${skill.description}</p>
9581077
<div class="skill-triggers">${skill.triggers.map(t => `<span class="trigger">${t}</span>`).join('')}</div>
9591078
${skill.dependencies && skill.dependencies.length > 0 ? `<p style="font-size:12px;color:var(--gray-600);margin-bottom:16px;">Dependencies: ${skill.dependencies.map(d => `<a href="/skills/${d}">${d}</a>`).join(', ')}</p>` : ''}
9601079
@@ -964,23 +1083,23 @@ async function renderSkillPage(skillName, ctx) {
9641083
</div>
9651084
9661085
${exampleContent ? `
967-
<div id="tab-example" class="tab-content active">
1086+
<section id="tab-example" class="tab-content active" aria-label="Example usage">
9681087
<div class="skill-content">${exampleContent}</div>
969-
</div>
1088+
</section>
9701089
` : ''}
9711090
972-
<div id="tab-docs" class="tab-content ${exampleContent ? '' : 'active'}">
1091+
<section id="tab-docs" class="tab-content ${exampleContent ? '' : 'active'}" aria-label="Documentation">
9731092
<div class="skill-content">${content}</div>
974-
</div>
1093+
</section>
9751094
<div class="skill-links">
976-
<a href="${skill.links.github}" target="_blank">GitHub</a>
977-
${skill.links.docs ? `<a href="${skill.links.docs}" target="_blank">Docs</a>` : ''}
978-
${skill.links.example ? `<a href="${skill.links.example}" target="_blank">Example</a>` : ''}
1095+
<a href="${skill.links.github}" target="_blank" rel="noopener noreferrer">GitHub</a>
1096+
${skill.links.docs ? `<a href="${skill.links.docs}" target="_blank" rel="noopener noreferrer">Docs</a>` : ''}
1097+
${skill.links.example ? `<a href="${skill.links.example}" target="_blank" rel="noopener noreferrer">Example</a>` : ''}
9791098
</div>
9801099
<a href="/" class="back-link">&larr; Back to all skills</a>
9811100
</main>
9821101
<footer>
983-
<p>2026 <a href="https://rescience.com" target="_blank">ReScience Lab</a> | <a href="mailto:hi@opc.dev">hi@opc.dev</a></p>
1102+
<p>2026 <a href="https://rescience.com" target="_blank" rel="noopener noreferrer">ReScience Lab</a> | <a href="mailto:hi@opc.dev">hi@opc.dev</a></p>
9841103
</footer>
9851104
<script>
9861105
function switchTab(btn, tabId) {

0 commit comments

Comments
 (0)