As a solo founder, my most limited resource is time. When I decided to build aiforcode.io, a directory of AI coding tools, I knew I couldn't afford to manually create and maintain hundreds of individual HTML pages. The content would become stale, inconsistent, and impossible to manage.
I needed a system that was scalable, maintainable, and optimized for search from day one. The solution was to build a simple, yet powerful, content generation engine with Node.js that turns a single tools.json
file into an entire static website, complete with structured data and dynamic comparison pages.
This article shows how I did it.
The Foundation: A Single Source of Truth
The entire system is built around one central file: tools.json
. This file contains an array of objects, where each object represents a tool and holds all of its associated data—name, description, features, pricing URLs, technical specifications, and more.
This approach has several advantages:
- Consistency: All data is in one place, ensuring it's uniform.
- Maintainability: To update a tool, I edit one entry. To add a new tool, I add a new object.
- Scalability: The system's workload doesn't increase with more tools; the data file just gets longer.
Here is a simplified example of a tool's data structure in tools.json
:
{
"id": "cursor",
"name": "Cursor",
"description": "Cursor is an AI-first code editor designed for pair-programming with a powerful AI.",
"score": 96,
"url": "https://cursor.sh/",
"pricingUrl": "https://cursor.sh/pricing",
"features": [
"AI-powered code completion",
"Multi-file project context",
"Codebase-wide chat and edits"
],
"bestFor": ["AI Native Development", "Multi-file Projects"],
"Technical Specifications": {
"Core AI Model(s)": "Proprietary models, plus access to GPT-4 and Claude Sonnet 4",
"Context Window": "Understands the entire codebase",
"Offline Mode": "Yes, with offline model support"
},
"editorsNote": "Cursor provides a deeply integrated AI experience that understands the entire codebase, making it highly effective for complex, multi-file projects."
}
My build script, build.js
, reads this file and uses it to generate every page on the site.
Part 1: Automating Rich Snippets with JSON-LD
One of the most effective ways to improve search visibility is by providing structured data. JSON-LD (JavaScript Object Notation for Linked Data) is a format that allows you to embed structured metadata directly into your HTML, helping search engines understand the content and context of your page. This is what powers the rich snippets you see in search results—review stars, pricing, FAQs, and more.
Writing this by hand for hundreds of tools would be impossible. Instead, I wrote a function, generateJsonLD(tool)
, that programmatically creates this data for each tool.
The function constructs multiple schema types (SoftwareApplication
, Review
, and Organization
) by pulling data directly from the tool's object in tools.json
.
Here’s a look at the logic inside build.js
:
// build.js
function generateJsonLD(tool) {
const baseUrl = 'https://aiforcode.io';
// Generate comprehensive SoftwareApplication schema
const softwareApplicationSchema = {
"@context": "https://schema.org",
"@type": "SoftwareApplication",
"name": tool.name,
"description": tool.description,
"url": tool.url,
"applicationCategory": "DeveloperTool",
"operatingSystem": "Windows, macOS, Linux", // Simplified for this example
"aggregateRating": {
"@type": "AggregateRating",
"ratingValue": tool.score / 10, // Convert score to 10-point scale
"bestRating": "10",
"ratingCount": "1" // My review
},
"offers": {
"@type": "AggregateOffer",
"url": tool.pricingUrl,
"priceCurrency": "USD"
}
};
// Generate comprehensive Review schema
const reviewSchema = {
"@context": "https://schema.org",
"@type": "Review",
"itemReviewed": {
"@type": "SoftwareApplication",
"name": tool.name
},
"reviewRating": {
"@type": "Rating",
"ratingValue": tool.score,
"bestRating": "100",
"worstRating": "0"
},
"author": {
"@type": "Person",
"name": "Kaushik Rajan",
"url": "https://kaushikrajan.me"
},
"reviewBody": tool.editorsNote
};
// The function returns both schemas, which are then embedded in the page
return {
softwareApplication: softwareApplicationSchema,
review: reviewSchema
};
}
When the script generates a page for a tool, it calls this function and injects the resulting JSON-LD into the <head>
of the HTML.
<script type="application/ld+json">
{
"@context": "https://schema.org",
"@type": "SoftwareApplication",
"name": "Cursor",
"description": "Cursor is an AI-first code editor designed for pair-programming with a powerful AI.",
"url": "https://cursor.sh/",
...
}
</script>
<script type="application/ld+json">
{
"@context": "https://schema.org",
"@type": "Review",
"itemReviewed": {
"@type": "SoftwareApplication",
"name": "Cursor"
},
"reviewRating": { ... },
...
}
</script>
This automated process ensures every single tool page is rich with structured data, giving Google exactly what it needs to display rich snippets in search results.
Here's a snip of the actual directory:
Part 2: Generating Head-to-Head Comparison Pages
"Tool A vs. Tool B" searches are some of the highest-intent queries a user can make. They are actively comparing options and are close to making a decision. Creating pages that target these queries is a proven programmatic SEO strategy.
My build script automates this by generating comparison pages for every unique pair of the top 10 tools in my directory.
The generateComparisonPages()
function iterates through the sorted list of tools and creates pairs.
// build.js
function generateComparisonPages() {
// Get top 10 tools (already sorted by score)
const top10Tools = tools.slice(0, 10);
// Generate all unique pair combinations
for (let i = 0; i < top10Tools.length; i++) {
for (let j = i + 1; j < top10Tools.length; j++) {
const toolA = top10Tools[i];
const toolB = top10Tools[j];
// Generate comparison page content from a template
const comparisonContent = generateComparisonContent(toolA, toolB);
// Create a URL-friendly filename like "cursor-vs-github-copilot.html"
const slugA = generateSlug(toolA.name);
const slugB = generateSlug(toolB.name);
const comparisonSlug = `${slugA}-vs-${slugB}`;
// Write the comparison page to a file
const outputPath = path.join(toolsOutputDir, `${comparisonSlug}.html`);
fs.writeFileSync(outputPath, comparisonContent, 'utf8');
}
}
}
For each pair, generateComparisonContent(toolA, toolB)
populates a dedicated comparison template with data from both tools, creating a detailed side-by-side analysis. This includes features, use cases, technical specifications, and a final verdict.
This process automatically generates 45 high-value comparison pages ((10 * 9) / 2
), plus any other curated pairs I define, without me ever having to write a single line of HTML for them.
Here's a screenshot of the actual directory:
Conclusion: Build Systems, Not Just Pages
By investing time upfront to create a simple content engine, I've built a directory that largely runs itself.
- It's maintainable: Updates happen in one place.
- It's scalable: The site can grow to thousands of tools with no extra effort per tool.
- It's optimized: Every page is built for SEO from the ground up with structured data and targeted keywords.
This programmatic approach is a powerful strategy for any solo developer or small team. Instead of getting bogged down in the repetitive work of manual page creation, you can focus on what matters: collecting high-quality data and building a better product.
The full build script is more complex, with additional logic for generating category pages, sitemaps, and more, but the core principles are the ones I've outlined here. It's a testament to how a bit of automation can create a significant, scalable asset.
Questions? Feedback? I'm always looking to improve the directory and the data behind it. Feel free to leave a comment below. Thank you for reading!
Top comments (0)