A data-centric dashboard for the Sefaria developer ecosystem — a single place to search, filter, sort, and count the independent projects built by community members on top of Sefaria's data, API, and other digital assets.
Sefaria is more than a library. It's also the authoritative data layer powering much of Torah technology, via our API, Sefaria-Export, and more. The most compelling way to tell that story is through Powered by Sefaria — the independent projects the community has built on our data. They're the clearest demonstration of Sefaria's reach beyond the library website.
AI has made it easier than ever to build with our data — this project helps us tell the story.
A visual way to search, filter, sort, and count projects Powered by Sefaria.
A data-centric home for the developer ecosystem — one place to see the scale and diversity of what's been built on Sefaria's data, enabling deeper understanding of Sefaria as the authoritative data layer for Jewish text development in the age of AI.
- TypeScript (strict), React 19 + Vite 8,
react-routerv7 (library mode) - CSS Modules on top of semantic design tokens (
src/styles/tokens.css), Lucide icons rechartsfor the unlisted/chartspage (lazy-loaded, so it is not in the main bundle)- Vitest + React Testing Library +
vitest-axefor unit and component tests; Playwright +@axe-core/playwrightfor end to end - ESLint (typescript-eslint, react-hooks, jsx-a11y) and Prettier
The spec this implements is in docs/REDESIGN_SPEC.md.
This project uses Vite 8, which is built on rolldown (a native bundler) and requires Node.js 20.19+ or 22.12+. The repo includes an .nvmrc pinned to Node 22, so if you use nvm:
nvm use # picks up the version in .nvmrcnpm install
npm run dev # starts the dev server at http://localhost:5173npm run dev: dev servernpm run build: type-check and production build intodist/npm run preview: serve the production buildnpm run lint/npm run format: ESLint / Prettiernpm run typecheck:tsc -bnpm test: unit and component tests (Vitest)npm run e2e: Playwright (desktop 1440x900 and mobile 390x844; the project-page spec sets its own 1280, 834 and 390 viewports). It builds and serves the app itself and runs against a snapshot of the API insrc/lib/__fixtures__/projects.json, never the live API. First time only:npx playwright install chromium.npm run screenshots: writes screenshots of every screen at 360, 390, 768, 1024 and 1440px intoscreenshots/
All data comes from one request to https://www.sefaria.org/api/powered-by, made by src/lib/api.ts and exposed to components only through the useProjects hook. Everything else (filtering, sorting, the charts) happens client-side on the ~150 rows. Every published project is shown (consent_to_display is deliberately not checked).
| Route | Purpose |
|---|---|
/ |
Browse projects. Search, filters, sort and view live in the URL. |
/projects/:slug |
One project. The slug is ${id}-${name} (141-torah-mcp); only the id is used, a stale name redirects. |
/charts |
"Powered by Sefaria in numbers". Unlisted: noindex, nofollow, not linked anywhere. |
The build is a static single-page app, so the host must serve index.html for any unknown path (otherwise /charts and project links 404 on refresh). On Coolify, enable the SPA fallback, or use nginx. The category images under /category-art/ never change under the same name, so give them a long cache:
location /category-art/ {
add_header Cache-Control "public, max-age=31536000, immutable";
}
location / {
try_files $uri /index.html;
}An unknown project id renders a "Project not found" page and sets <meta name="prerender-status-code" content="404"> for prerendering services; a plain static host still answers 200 for it.
The brand purple is --brand-devportal (#653259, design-system purple-800), with #4e2544 (purple-900) on hover. The design system's DevPortal theme maps module-primary to purple-700 (#7c416f), which is lighter than the live Developer Portal, so we use purple-800 until the design system is updated. #7c416f stays as the AI & Apps category color only.
index.html loads Google's gtag.js for the GA4 property G-44RHV2ZWQ1, using the snippet from Sefaria's data analyst, unconditionally and as given. It is how the main site counts visitors. In particular:
- It sets Google's first-party analytics cookies and does not check Do Not Track or Global Privacy Control. The privacy promises in the next section are about that other code, not this tag.
- GA4 sends its own page views, including on browser-history changes (route changes), via Enhanced measurement in the GA4 data stream settings. Our code does not call
gtagand does not filter anything:/chartsis counted, and so is a project page's redirect to its canonical slug. - The page URL it reports includes the query string, so
?q=…search text reaches Google unlessqis redacted in GA4 (Admin → Data collection and modification → Data redaction → URL query parameters). - Tests block
googletagmanager.comandgoogle-analytics.com(e2e/support.ts), so they stay offline and send nothing to the property. - If the site's Content-Security-Policy is ever tightened, allow
https://www.googletagmanager.cominscript-srcandhttps://*.google-analytics.cominconnect-src.
src/lib/analytics.ts is separate from GA4 and does nothing unless VITE_ANALYTICS_DOMAIN is set at build time:
| Variable | Meaning |
|---|---|
VITE_ANALYTICS_DOMAIN |
The site's domain in the analytics account. Unset means analytics is off and the footer says nothing. |
VITE_ANALYTICS_SRC |
Script URL. Defaults to https://plausible.io/js/script.manual.js; it must be a manual variant, because the app sends its own page views on route changes (never for /charts). |
Do Not Track and Global Privacy Control switch it off. There are no cookies, storage ids or user ids, and events carry only ids and UI context, never search text. The events are project_outbound, project_source, featured_open, filter_change (facet name only), sort_change, view_change and search (has_query and a results bucket). When you turn it on, allow the script and API hosts in the site's Content-Security-Policy (script-src and connect-src); the app sets none itself.
The OG image is regenerated with node scripts/generate-og-image.mjs (needs network for the fonts).
Cannot find native binding / Cannot find module '@rolldown/binding-darwin-arm64'
This is usually caused by running an unsupported Node version (see Prerequisites above) and/or the npm optional-dependencies bug leaving the platform-specific rolldown binary uninstalled. Fix it with a clean reinstall on a supported Node version:
nvm use # or otherwise switch to Node 20.19+ / 22.12+
rm -rf node_modules package-lock.json
npm installNote: ESLint 10 requests Node 22.13+, so on Node 22.12 you may see a harmless
EBADENGINEwarning during install. It doesn't affectnpm run dev. To clear it, install a newer Node 22 (e.g.nvm install 22.13).