DEV Community

Cover image for Building a systematic AI tool guide for developers with Astro content collections, zod, and an AI agent
Xavier Enciso
Xavier Enciso

Posted on AI-assisted

Building a systematic AI tool guide for developers with Astro content collections, zod, and an AI agent

I built Wayoflife, a systematic AI tool that helps developers pick the right tool for each job. It has 21 use cases and 165 tools, and each use case is a short ranked list with a one-line verdict per pick. This post covers the parts that might be useful in your own projects: modeling rankings as validated data, keeping sponsorship out of the ordering in code, making a static site readable by chatbots, and letting an AI agent ship changes without breaking things.

The problem with "AI directories"

Most directories are catalogs: thousands of tools per category, no order, and often paid placement at the top. As a developer I didn't want a catalog. I wanted "for this job, use this, and here's why in one line." That's a data-modeling problem before it's a UI problem.

Rankings as data: Astro content collections + zod

Everything lives in src/content/ as JSON: one file per tool, one per use case, one per release. The schema (trimmed):

// src/content.config.ts
const tools = defineCollection({
  loader: glob({ pattern: '*.json', base: './src/content/tools' }),
  schema: z.object({
    name: z.string(),
    url: z.string().url(),
    affiliateUrl: z.union([z.string().url(), z.literal('')]).optional().default(''),
    // Paid featured listing: adds a labeled badge. NEVER changes ranking order.
    featured: z.boolean().optional().default(false),
    pricing: z.enum(['free', 'freemium', 'paid']),
    description: z.string(),
    pros: z.array(z.string()).default([]),
    cons: z.array(z.string()).default([]),
    lastReviewed: z.string(),
    addedAt: z.string(),
  }),
});

// One JSON file per use case; `rankings` order IS the editorial rank.
const useCases = defineCollection({
  loader: glob({ pattern: '*.json', base: './src/content/use-cases' }),
  schema: z.object({
    name: z.string(),
    summary: z.string(),
    order: z.number().default(99),
    lastUpdated: z.string(),
    rankings: z.array(z.object({
      tool: reference('tools'),
      verdict: z.string(),
      bestFor: z.string(),
    })),
  }),
});
Enter fullscreen mode Exit fullscreen mode

A use case then looks like this (real excerpt from coding-agents.json):

{
  "name": "Coding agents",
  "rankings": [
    { "tool": "claude-code", "verdict": "The strongest all-round agent for real codebases; start here if you can live in a terminal.", "bestFor": "Large refactors and multi-file features" },
    { "tool": "cursor", "verdict": "The best AI editor for most developers: fast autocomplete plus a capable agent.", "bestFor": "Everyday coding in a familiar IDE" },
    { "tool": "openai-codex", "verdict": "Great for firing off many tasks in parallel and reviewing PRs later.", "bestFor": "Async, parallel tasks" }
  ]
}
Enter fullscreen mode Exit fullscreen mode

Two things fall out of this design:

  1. Rank is the array index, and nothing else. No score field to fiddle with and no sort function that could quietly take sponsorship into account.
  2. reference('tools') makes broken links a build error. If a slug doesn't match a tool file, astro build fails. That matters a lot once an agent is editing content (more below).

Keeping money out of the order, in code

Sponsorship is a boolean on the tool, while the order lives on the use case. The page renders the ranked list exactly as stored, then a separate, labeled "Featured" box below it. Affiliate links (when set) get rel="sponsored noopener":

export const outbound = (t: Tool) => t.data.affiliateUrl || t.data.url;
export const outboundRel = (t: Tool) => (t.data.affiliateUrl ? 'sponsored noopener' : 'noopener');
Enter fullscreen mode Exit fullscreen mode

Because the two concerns are in different files, a reviewer (human or agent) can see from the diff that a sponsorship change can't touch ordering.

Making a static site readable by chatbots

People increasingly ask an assistant "what's the best coding agent?" instead of searching. So the site is built for both:

  • /llms.txt and /llms-full.txt, generated at build time by an Astro endpoint from the same collections:
// src/pages/llms.txt.ts (trimmed)
for (const u of useCases) {
  const top = u.data.rankings.slice(0, 3)
    .map((r, i) => `#${i + 1} ${tm.get(r.tool.id)!.data.name}`).join(', ');
  lines.push(`- [Best AI tools for ${u.data.name.toLowerCase()}](${abs(`/use-cases/${u.id}/`)}): ${top}`);
}
Enter fullscreen mode Exit fullscreen mode
  • ItemList JSON-LD with position on every ranking page, so the order is explicit in structured data, not just implied by the HTML.
  • robots.txt that explicitly allows GPTBot, ClaudeBot, PerplexityBot, Google-Extended and friends, plus a sitemap and IndexNow pings after deploys.

Letting an AI agent ship without breaking things

An AI agent writes most of the code and content updates. What made that safe:

  • The schema is the contract. The agent can add 20 tools in one go, and a wrong enum value or missing field fails the build.
  • Build + link check before every deploy. astro build then a link checker over the output. In practice this caught more agent mistakes than reading diffs did.
  • Deploy from a clean tree. Vercel blocked production deploys whose git commit author wasn't on the team. The fix was to deploy from git archive HEAD | tar -x -C /tmp/deploy (no .git), which also guarantees only committed files ship.

What I'd do differently

  • Buy a domain on day one. You can't verify a *.vercel.app subdomain with email providers, so newsletter sending waits on that.
  • Model "last reviewed" per ranking entry, not just per tool, so stale verdicts are easier to find.

Try it, and tell me what's wrong

If you're a developer choosing between coding agents, agent frameworks or vibe-coding builders, the ranked lists are here: wayoflife-ai.vercel.app. It's free, has no paid ranks, and adds a "New this week" feed of releases. I'd love to hear which ranking you'd change.

This article was written with AI assistance and checked against the site's source code.

Top comments (0)