A single-page web application to track real-time election results for DSA nationally endorsed candidates.
- ClojureScript - Application language
- Reagent - React wrapper for ClojureScript
- Re-frame - State management
- Shadow-CLJS - Build tool and dev server
- Bulma CSS - UI framework
- Airtable - Real-time data backend
- R2 - caching
- Github Actions - Deployment, caching updates
- Node.js (v16 or higher)
- Java (for ClojureScript compiler)
npm install- Follow the instructions in
AIRTABLE_SCHEMA.mdto set up your Airtable base - Create a
.envfile in the project root:
AIRTABLE_API_KEY=your_api_key_here
AIRTABLE_BASE_ID=your_base_id_here
AIRTABLE_TABLE_NAME=Races
Builds to Github Pages via Github Actions. Tokens scope-limited to custom domains/read access. DNS records and CNAME set in a typical GH pages flow.
Airtable limits us to 5 requests/second per base so we can't use it publicly on election night. To resolve this, we poll the Airtable and cache the results in data.json and store it on R2. Users load R2's data.json first and only fallback to Airtable's API if it fails. This is a quick cache hack because I didn't want to ssh into a server and setup an actual caching proxy or DB.
-
Data Flow:
- GitHub Actions fetches latest data from Airtable every 5 minutes (free tier means scheduled runs could take 15-30 minutes)
- Data is uploaded to Cloudflare R2 bucket as
data.json - The app fetches from R2 (or local
/data.jsonin development) - App auto-refreshes data every 30 seconds
-
Local Development:
# Fetch latest data from Airtable to local cache npm run fetch-airtable # Start dev server (uses local /data.json) npm start
-
Production Setup:
The app is configured to fetch from R2 in production via the
R2_DATA_URLenvironment variable:- R2 Bucket:
2025-election - Public URL:
https://pub-83c6936810744c479bd0abe7c3146c24.r2.dev/data.json(developer link, rate-limited and should use a custom domain to remove rate-limit) - Update Frequency: Every 5 minutes via GitHub Actions (free tier means scheduled runs could take 15-30 minutes)
- R2 Bucket:
-
GitHub Secrets Required:
Add these secrets to your repository (Settings → Secrets and variables → Actions):
R2_ACCESS_KEY_ID- R2 API access keyR2_SECRET_ACCESS_KEY- R2 API secret keyR2_ENDPOINT_URL- R2 endpoint URLR2_BUCKET_NAME- R2 bucket nameAIRTABLE_API_KEY- Airtable API keyAIRTABLE_BASE_ID- Airtable base ID
Start the development server:
npm startThe app will be available at http://localhost:8080
Hot reload is enabled - changes to ClojureScript files will update automatically.
Build the optimized production bundle:
npm run buildThe compiled output will be in public/js/main.js