StateStats is a Next.js (App Router) + Prisma project for exploring U.S. state-level metrics. It ships with a typed domain model, a Supabase/PostgreSQL schema, ingestion pipelines, and interactive map/graph experiences.
- Next.js (App Router) + TypeScript
- Tailwind CSS
- Prisma 6.19.0 + Supabase Postgres
- Recharts for line charts
- d3-geo + topojson-client for the choropleth map
- Node.js 18.17+ (20+ recommended)
- pnpm
- PostgreSQL with a
DATABASE_URLconnection string
pnpm install
cp .env.example .env # configure DATABASE_URL
pnpm db:migrate # creates tables
pnpm db:generate # generates the Prisma clientnpm run ingest:income– median household income (Census ACSB19013_001E)npm run ingest:population– total population (Census ACSB01003_001E)npm run ingest:median-age– median age (Census ACSB01002_001E)npm run ingest:home-value– median home value (Census ACSB25077_001E)npm run ingest:age– alias for median age ingestionnpm run ingest:unemployment– unemployment rate annual average (BLS LAUS)npm run ingest:all– run all ingestions sequentiallynpm run ingest:verify– print latest run metadata and metric coverage summary
Real API ingestion is used when keys are set:
CENSUS_API_KEYBLS_API_KEY
If either key is missing, that metric falls back to deterministic synthetic data and is labeled with a synthetic fallback data source in the database.
Each ingestion writes an IngestionRun row with status, counts, and warning/error details.
ingest:all logs a preflight summary (Node version, DB host/port, and API key presence) before running.
Admin trigger endpoint:
POST /api/admin/ingest- Auth:
x-admin-ingest-secretheader orAuthorization: Bearer <token> - Env:
ADMIN_INGEST_SECRETmust be configured
/Map: Choropleth by metric + year, legend, tooltip, pinned state, accessible table./graphCompare: Multi-state line chart over time with metric selector, year range, normalization./data-sourcesProvenance: Data sources, last successful ingestions, metrics catalog./aboutOverview, usage tips, caveats.
git clone <repo-url>
cd StateStats
pnpm install
cp .env.example .env # set DATABASE_URL (Supabase)
pnpm db:migrate
pnpm db:generate
pnpm ingest:median-income
pnpm devOpen http://localhost:3000 to view the app.
app/– App Router pages + API routes (admin ingestion, graph data)components/– Map and graph UI componentslib/– shared types, Prisma client helper, state list, and metric definitionsprisma/schema.prisma– PostgreSQL schema and enumsscripts/ingestion/– provider-backed ingestion pipelines and helpers
pnpm dev– run Next.js locallypnpm lint– Next.js lintpnpm db:migrate/pnpm db:generate– Prisma toolingpnpm ingest:income– run the median household income importerpnpm ingest:unemployment– run the unemployment rate importerpnpm ingest:all– run all metrics ingestionpnpm ingest:verify– verify latest run + coverage
- Ensure
DATABASE_URLis set in Vercel project settings (server-side). - Run
pnpm buildlocally to verify. - Deploy via Vercel (Git integration or
vercelCLI). The app uses App Router defaults; no special vercel.json required.
- Prereqs: Supabase Postgres (Session Pooler URI) set as
DATABASE_URLin Vercel project env vars. - Set ingestion env vars in Vercel:
CENSUS_API_KEY,BLS_API_KEY,ADMIN_INGEST_SECRET. - Vercel will run
npm install(or pnpm),npm run build, andnpm start. - Prisma client is generated automatically via
postinstall(prisma generate). Migrations are not run automatically; apply them yourself before deploy (e.g.,npx prisma migrate deployagainst Supabase or runpnpm db:migratelocally thendb:generate). - Ingestion scripts are manual/cron-only; do not wire them into Vercel build/runtime.
- Optional: schedule a Vercel Cron job that calls
POST /api/admin/ingestwithx-admin-ingest-secret.
- Core tables: State, Metric, Observation, DataSource, IngestionRun.
- Metrics are linked to canonical real sources (
census_acs,bls_laus) during real ingestion and fallback sources only when API keys are missing. - Ingestion scripts normalize legacy source IDs and keep source/run metadata consistent.
- Latest successful ingestion is surfaced in the global banner and Data Sources page.