I recently built Social Tools List, a curated directory for comparing social media software by workflow. The implementation is deliberately small: Astro renders the directory, TypeScript modules hold the content, and a Cloudflare Worker serves the generated site.
That stack has been a good fit for a directory where most pages change during editorial updates rather than on every request. Here are the decisions that mattered most.
1. Keep the source of truth boring
The directory data lives in TypeScript modules. Each tool has a stable slug, workflows, platforms, fit notes, and source URLs. That makes the content reviewable in pull requests and lets the compiler catch missing fields.
A simplified shape looks like this:
type Tool = {
slug: string;
name: string;
domain: string;
workflows: string[];
platforms: string[];
bestFor: string;
sourceUrl: string;
};
A database would add runtime flexibility, but it would also add migrations, query code, caching, and failure modes before the project needs them. Static data is not a permanent rule; it is the cheapest correct architecture for the current editing model.
2. Generate detail pages from the same data
Astro's file-based routing makes it straightforward to generate one page per tool while keeping the shared layout and metadata consistent.
---
import { softwareProfiles } from "../../data/software";
export function getStaticPaths() {
return softwareProfiles.map((tool) => ({
params: { slug: tool.slug },
props: { tool },
}));
}
const { tool } = Astro.props;
---
<h1>{tool.name}</h1>
<p>{tool.bestFor}</p>
The same dataset also powers the main software index and workflow hubs. This avoids a common directory problem: a card says one thing, the detail page says another, and the sitemap quietly points somewhere else.
3. Separate crawlable structure from browser convenience
Search and workflow filters are useful, but they do not need a client framework. Astro renders the initial list as HTML, then a small browser script hides or shows rows.
The important distinction is that filtering is an enhancement. Every profile and hub still has a normal URL and an ordinary anchor element. A crawler, a screen reader, or a user with JavaScript disabled can still reach the content.
For a small featured list, client-side filtering is faster and simpler than sending a request on every keystroke. If the inventory grows into the thousands, that tradeoff can change.
4. Treat technical SEO as build output
The sitemap and robots file are Astro routes, so they are generated from the same URL rules as the pages. Canonicals and social metadata sit with the templates that own them.
That means a broken slug is much more likely to fail during development or review than to become a mysterious production indexing issue.
I also prefer linking users to official vendor sources when a pricing or platform-support claim is decision-critical. A directory should help people verify a claim, not trap them inside the directory.
5. Keep analytics explicit
The site uses named events for actions such as search, filtering, profile views, and outbound vendor clicks. The HTML carries stable data-* attributes, while a small adapter forwards events to the configured analytics provider.
This keeps the event vocabulary independent from the vendor and avoids broad autocapture. More data is not automatically better data.
6. Deploy static files through a tiny Worker
The production command builds Astro first, then generates the Worker bundle:
{
"scripts": {
"build": "astro build",
"build:worker": "astro build && node scripts/build-worker.mjs",
"deploy": "pnpm build:worker && wrangler deploy"
}
}
The Worker is an operational wrapper around static output, not an excuse to move rendering back to request time. That keeps the public pages fast and makes the deployment model easy to reason about.
The practical rule
Static-first is not the same as static forever. It means starting with generated HTML and adding runtime systems only when a real requirement earns their cost.
For editorial directories, documentation sites, and curated catalogs, that default gives you a surprisingly long runway: fast pages, inspectable content changes, stable URLs, and very little production machinery.
Top comments (0)