SolarpunkList is a web directory of solarpunk intentional communities and regenerative land projects — think "Nomad List for ecovillages." It features an AI-powered discovery pipeline that uses semantic web search (Exa API) and LLM analysis (Anthropic Claude) to automatically find, profile, and score communities based on sustainability dimensions (energy, land, tech, governance, community, circularity). The directory presents rich community profiles with filterable/sortable cards, detailed pages with score breakdowns, tech stacks, and joining information.
No user authentication in V1. The app is a public-facing directory with an admin discovery endpoint.
Preferred communication style: Simple, everyday language.
- Framework: React 18 with Vite (not Next.js — despite the original spec mentioning Next.js, the implementation uses Vite + Express)
- Routing: Wouter (lightweight client-side router)
- State/Data Fetching: TanStack React Query with a custom
apiRequesthelper andgetQueryFnfactory - UI Components: shadcn/ui (new-york style) with Radix UI primitives, Tailwind CSS, class-variance-authority
- Fonts: Nunito (body/headings) and Lora (serif accent), loaded via Google Fonts
- Pages: DirectoryPage (filterable grid), CommunityDetailPage (full profile), AboutPage, DirectoryManagerPage (admin bulk-import from external directories), NotFound
- Path aliases:
@/→client/src/,@shared/→shared/,@assets/→attached_assets/
- Framework: Express.js running on Node with tsx (TypeScript execution)
- Architecture: Single Express server serves both the API and the Vite dev server (in development) or static built files (in production)
- API Design: RESTful JSON endpoints under
/api/prefixGET /api/communities— list all published communities with relationsGET /api/communities/:slug— single community detailGET /api/stats— total community countPOST /api/admin/discover— trigger AI discovery pipelinePOST /api/submit-community— public endpoint: accepts{ url }, runs Exa + Claude pipeline for a single URL, adds to directory (SSRF-protected)POST /api/subscribe— email subscription (stores toemail_subscriberstable)POST /api/track-visit— log a page visit (accepts{ path })GET /api/visit-stats— returns{ totalVisits, monthlyAverage, userSubmissions }for footer displayPOST /api/admin/scrape-directory— accepts{ url }, uses Exa + Claude to extract community listings from an external directory page, returns annotated entries with duplicate detectionPOST /api/admin/bulk-research— accepts{ entries: [{ name, url }] }, runsresearchFromUrlsequentially on each entry, returns per-entry success/error results- Chat/conversation endpoints under
/api/conversations
- Build: Custom build script (
script/build.ts) using Vite for client and esbuild for server, outputting todist/
- Database: PostgreSQL (required, connection via
DATABASE_URLenvironment variable) - ORM: Drizzle ORM with
drizzle-zodfor schema validation - Schema location:
shared/schema.ts(shared between client and server) - Migration tool: Drizzle Kit (
drizzle-kit pushvianpm run db:push) - Key tables:
communities— main entity with name, slug, location, scores, tech stack (JSONB), markdown content fieldscommunity_tags— tags associated with communitiescommunity_links— external links per communitycommunity_images— images per communityemail_subscribers— email newsletter subscriptionsdiscovery_runs— tracking discovery pipeline executionsrefresh_runs— tracking refresh pipeline executionspage_visits— visitor tracking (path + timestamp) for analytics display in footerconversations/messages— chat integration tables
- Session store: connect-pg-simple (PostgreSQL-backed sessions)
- Storage pattern:
IStorageinterface implemented byDatabaseStorageclass, accessed via singletonstorageexport
- Search: Exa API (semantic/neural web search) to discover communities matching solarpunk-related queries
- LLM Processing: Anthropic Claude (via
@anthropic-ai/sdk) to analyze search results and generate structured community profiles - Scheduling: node-cron runs weekly discovery (Mondays at 3 AM UTC) and monthly refresh (1st of month at 4 AM UTC)
- Refresh Pipeline:
server/refresh.tsre-researches existing communities monthly via Exa + Claude to detect changes, dormancy, stage transitions - Seed Data:
server/seed.tscontains hardcoded initial communities for bootstrapping - Image Pipeline:
server/image-fetcher.tsuses Exa API to find real photos from community websites and web search results. Images are validated via HEAD request (content-type + minimum size), filtered against known bad patterns (logos, icons, GIFs, placeholders), and stored incommunity_imagestable. Backfill endpoint:POST /api/admin/backfill-images.
- AI-generated fallback images for every community are stored at
client/public/images/communities/{slug}.png - Frontend fallback: Both CommunityCard and CommunityDetailPage use
onErrorhandlers on<img>tags to swap to/images/communities/{slug}.pngif external hero images fail to load - Hero image selection: During discovery, the first verified image becomes
heroImageUrl. If no verified images are found, the frontend automatically falls back to the AI-generated image via the slug-based path convention - Photo gallery: Detail pages display all community images in a grid with lightbox. Gallery images that fail to load are hidden individually
Communities are scored on a 0-100 "Solarpunk Score" composed of six dimensions (each 0-10):
- Energy, Land, Tech, Governance, Community, Circularity
- Provider: Resend (via Replit connector integration)
- Module:
server/email-notifications.ts— HTML + text email templates, batched sending via Resend API - Trigger:
notifySubscribers()is called (fire-and-forget) whenever a new community is added — both from user submissions (researchFromUrl) and auto-discovery (runDiscovery) - Template: Green-branded announcement email with community name, tagline, location, stage, score, and "View Full Profile" CTA button
- Batching: Sends in batches of 50 with per-recipient failure logging
server/replit_integrations/chat/— Chat functionality with Anthropic Claudeserver/replit_integrations/batch/— Batch processing utilities for Anthropic API calls- Resend connector for transactional email (subscriber notifications)
- Vite plugins:
@replit/vite-plugin-runtime-error-modal,@replit/vite-plugin-cartographer,@replit/vite-plugin-dev-banner
- PostgreSQL Database — Primary data store. Must set
DATABASE_URLenvironment variable. Provisioned via Replit's database service. - Anthropic Claude API — Used for AI-powered community profile generation and chat. Requires
AI_INTEGRATIONS_ANTHROPIC_API_KEYandAI_INTEGRATIONS_ANTHROPIC_BASE_URLenvironment variables. - Exa Search API — Semantic web search for discovering new communities. Requires
EXA_API_KEYenvironment variable. Optional — discovery degrades gracefully without it.
drizzle-orm+drizzle-kit— Database ORM and migrations@anthropic-ai/sdk— Anthropic Claude integrationexpress+express-session— HTTP server and sessionsconnect-pg-simple— PostgreSQL session storenode-cron— Scheduled task execution@tanstack/react-query— Client-side data fetchingwouter— Client-side routingzod+drizzle-zod— Runtime schema validationp-limit+p-retry— Concurrency and retry control for API calls- Full shadcn/ui component library (Radix UI primitives)