I've been building web3-discover — a hand-vetted, scam-flagged directory of currently-claimable web3 airdrops — and I wanted to share the static-site + agent-driven pipeline behind it, because the architecture is unusual: there's no backend, no database, no editor login. Everything is git-tracked Markdown, every deploy is a static build, and content is refreshed daily by an autonomous workflow rather than human curators.
Live URL: https://web3-discover.vercel.app
The wedge (why "fewer entries" is the feature)
The crypto-airdrop site space is dominated by SEO-juiced aggregators listing 300+ "airdrops" — most of which are expired, scams, or paid placements pretending to be listings. The wedge is the opposite: ~30 hand-vetted entries, each with a concrete action, cost floor, deadline, risk flag, and a lastChecked date. If we can't verify an entry, we drop it.
The stack (and why each piece)
Astro 5 — chosen specifically because content collections give you a typed schema for markdown frontmatter, and the output is dead-static HTML. No client JS in the critical path. Every entry page is a 9KB document.
// src/content/config.ts
const airdrops = defineCollection({
type: 'content',
schema: z.object({
title: z.string(),
chain: z.enum(['Ethereum', 'Solana', 'Base', 'Arbitrum', /* ... */]),
action: z.string(),
costFloor: z.string(), // "$0", "$50", "gas-only"
deadline: z.string(), // ISO date or "ongoing"
riskFlag: z.enum(['verified', 'unverified', 'suspected-scam']),
lastChecked: z.string(),
officialUrl: z.string().url(),
addedOn: z.string(),
})
});
When an entry breaks schema, the build fails loudly. That's the whole content-CI layer — no DB migrations to run.
Vercel for hosting — vercel --prod from CLI. Free tier handles plenty of traffic. Sitemap pinned to @astrojs/sitemap@3.2.1 because newer versions broke our Astro 5 build chain.
JSON-LD for structured data — Organization + WebSite on every page (in Base.astro layout), ItemList on the airdrops index page, Article schema on each entry page (with datePublished from frontmatter addedOn, publisher logo, mainEntityOfPage, dateModified sourced from lastChecked). Verified live via curl — the JSON parses cleanly with python3 -m json.tool.
RSS at /rss.xml using @astrojs/rss. Each item carries the full action/effort/cost/deadline/risk block — readable in a feed reader without clicking through. GUIDs are entry URLs (canonical).
IndexNow for crawler push notifications — three endpoints pinged on every content update:
-
https://api.indexnow.org/indexnow→ 202 -
https://www.bing.com/indexnow→ 200 -
https://yandex.com/indexnow→ 202 ({"success":true})
The verification key is a file at https://web3-discover.vercel.app/<hex>.txt. Don't delete that file — Bing re-validates on subsequent pings. I learned that the hard way trying to "clean up" the public dir.
GoatCounter for analytics. Privacy-friendly, no cookies, no consent banner needed. Embed is a 5-line <script>. The dashboard lives at <workspace>.goatcounter.com. Counts pageviews + referrers, exactly what I need to see whether a distribution channel actually works.
The agent-refresh loop (the unusual part)
The site is maintained by an autonomous workflow (Claude Code agent ticks running on a 30-second heartbeat). Every wake the agent reads the current Markdown entries, checks lastChecked against Date.now() - 7d, and queues stale entries for re-verification:
- Fetch the project's official Twitter or docs page.
- Re-confirm the deadline string.
- Re-confirm the action steps still match what the project requires.
- If anything has changed: update the frontmatter, bump
lastChecked. - If the project has TGE'd (token generation event passed) or the airdrop is over: remove the entry from
src/content/airdrops/entirely. - Re-build, redeploy, ping IndexNow.
In the first sweep (22 → 32 entries) the agent caught three of my own false assumptions: Monad / MegaETH / Plasma are post-TGE, not pre-TGE. Sonic Labs Season 2 ended November 2025 — dropped. The point isn't that the agent is smarter than me; it's that running the verification loop on a 7-day cycle instead of "when I feel like it" surfaces drift fast.
Per-entry conversion pivot (a real lesson)
Initial v1 had a /tools/swap page with Jupiter Plugin (Solana) and Jumper deeplink (EVM) as a monetization path — integrator-fees on swaps. Every airdrop entry page had a "Sell when claimed" CTA pointing at /tools/swap?token=X&chain=Y.
The bug: the URL params were read by NO code. Every sell-CTA from 32 entry pages dumped users on a USDC-default swap. The single biggest pre-traffic conversion gap, sitting in production for ~24 hours before I caught it.
Fix: a slug→target registry mapping each entry's chain + symbol to a concrete token address. For Solana, that's the SPL mint passed into Jupiter.formProps.initialInputMint. For EVM, ?fromChain=…&fromToken=… on the Jumper deeplink. 12 verified Solana mints + 20 EVM token addresses are now hardcoded into a TypeScript const. Building this took ~25 minutes. The fact that an agent let it ship undetected for 24h is the cautionary tale — verification-before-completion is non-negotiable when your reviewer-of-last-resort is the same process that shipped the change.
Guides pillar (SEO + risk-awareness)
Three evergreen guides live at /guides/scams, /guides/taxes, /guides/wallet-hygiene. Every airdrop entry internally links to two risk-matched guides (a suspected-scam entry links to /guides/scams; a high-deadline entry links to /guides/wallet-hygiene). Indexable surface went from 35 to 39 URLs, every entry page gains internal-link equity.
The choice was deliberate: I'd rather have 3 deep guides than 30 shallow ones. Crawlers reward dwell time, and a 1500-word scams guide with concrete patterns ("if the claim URL doesn't match the project's official Twitter pinned tweet, walk away") does that better than 30x 200-word listicles.
What I'd tell anyone building in this category
Anti-features are the differentiation. "No wallet connect, no claim links, no paid listings" is not a copy gimmick — it's the reason I trust my own site, and it's the reason a user might. The competitive moat in any junk-flooded category is being the one that says "no" to the easy money.
Static + agent-refresh > headless CMS. A headless CMS gives editors a UI. I don't have editors. The git tree is the database; the deploy is the publish step; the agent is the editor. If your contributor list is 1, this collapses to dramatically less infra than "Strapi + Postgres + worker queue".
JSON-LD is free SEO money. Article schema with
dateModifiedsourced fromlastCheckedtells Google "this is a fresh page". Cost: 12 lines per layout file. Benefit: every entry shows the freshness signal in SERPs.IndexNow ≠ Google Search Console. GSC verification is an interactive OAuth flow with no zero-account path I could find. IndexNow accepts a static key file and gets you Bing + Yandex (the latter is non-trivial for crypto SEO, where Russian and Eastern European traffic is real). Don't burn hours on GSC; ship IndexNow and move on.
Try it
Site: https://web3-discover.vercel.app — currently 32 active entries, deadline-sorted, refreshed today.
If you spot an entry that's wrong, broken, or scammy I should flag harder — open an issue at the repo (linked in the footer). The whole content tree is public Markdown — feedback round-trips fast.
Tags: #astro, #web3, #staticsite, #seo, #crypto
Top comments (0)