Building a directory site sounds trivial until you find yourself wiring up databases, caching layers, and reactive front-end frameworks just to display what is essentially a curated table of contents. I recently built Social Tools List, a site for comparing social media software. My goal was to get it online quickly, keep it fast, and avoid maintaining infrastructure for content that only changes when I add or update a tool. I settled on a static-first stack: Astro for site generation, TypeScript for structured data, and Cloudflare Workers for deployment. The result is a site that loads instantly, costs almost nothing to host, and requires no database administration.
Why Static-First Makes Sense for a Directory
Many web apps default to server rendering or single-page architectures because they feel like safe, modern choices. But not every site receives dynamic user input on every request. Social Tools List is a read-heavy resource. The comparison data changes when I push an update, not when a visitor hits refresh. Rendering HTML ahead of time removes the need for database queries at the edge, template compilation on the fly, or hydration overhead in the browser. I generate the site at build time, deploy the static files, and let a lightweight worker handle the wrapper. This keeps response times low and eliminates an entire class of runtime failures.
Store Data in TypeScript, Not a Database
I do not use a database. Every tool in the directory is defined as a TypeScript object with a slug, name, domain, and an array of supported workflows. A typical entry looks like this:
{
slug: 'buffer',
name: 'Buffer',
domain: 'buffer.com',
workflows: ['scheduling', 'analytics']
}
Storing data in the same language that builds the site has two immediate benefits. First, pull requests become content reviews. When I add a tool, the diff shows the exact fields and values, and a teammate can spot a typo or a wrong domain without learning a CMS interface. Second, the TypeScript compiler enforces the shape of every record. If I forget to include a slug or misspell a workflow key, the build fails before the bad data ever reaches a page.
A database would introduce migrations, connection strings, caching strategies, and backup routines. For a directory with a few hundred entries that I maintain manually, that overhead is pure drag. Static data in TypeScript modules is the cheapest correct choice for this model. The "correct" part matters. It is not just about saving money; it is about removing abstraction layers that solve problems I do not have.
Let Astro Own the Routing and Rendering
Astro generates one HTML page per tool from a single dynamic route. I define one layout component, and Astro stamps out the metadata, headings, and structured data for every entry automatically. Because the same dataset drives the main index, the workflow category hubs, and the individual detail pages, there is no chance that a card on the home page shows a different description than the detail page itself. In traditional CMS setups, you often see drift: the API returns one version, the cache another, and a client-side render a third. Static generation from a single source of truth prevents that.
Astro’s island architecture also makes it easy to add small interactive pieces without poisoning the whole page with JavaScript. The site ships as static HTML, and only the filtering script hydrates its specific corner of the DOM. There is no framework runtime wrapping the entire document. Astro also treats page metadata as a first-class concern. Each tool page gets its own title tag and meta description derived directly from the TypeScript record, so I do not need a separate plugin or a head management library.
Filtering Without a Framework
Search and filtering often trigger developers to install React, Vue, or a heavy state management library. I resisted that. Astro renders the full list as plain HTML on the server. A small vanilla JavaScript script, less than a kilobyte, runs in the browser and toggles the display property of list items based on a workflow tag or a text match.
Serving the complete markup sounds inefficient if you come from an API-driven background. But consider the overhead of a typical dynamic approach. The browser downloads a JavaScript bundle, hydrates a component tree, calls an endpoint, waits for JSON, and then renders rows. For a directory that lists fewer than one hundred tools, that ritual is slower and less reliable than hiding divs that are already in the document. My script attaches event listeners to filter buttons, reads a data-workflow attribute on each row, and sets non-matches to hidden. The operation takes milliseconds.
Because the list is present in the initial HTML, the site is usable without JavaScript. Search engine crawlers see every link and every description. Users on slow networks or with script blockers still get the full directory. The filtering is an enhancement, not a gate.
Sitemaps and Robots as Code
Sitemaps and robots.txt are not afterthoughts written by hand. They are Astro routes that consume the same dataset and URL helpers as
