# CLAUDE.md - Ethereum.org Website ## Project Overview This is the official Ethereum.org website - a Next.js application that serves as the primary educational and community hub for Ethereum. The site is built with modern web technologies and focuses on accessibility, internationalization, and performance. ## Technology Stack ### Core Framework - **Next.js 16+** - React framework with App Router (Turbopack is the default bundler; webpack available via `pnpm dev:webpack` / `pnpm build:webpack`) - **React 19** - UI library - **TypeScript 5.5+** - Type safety and development experience - **Tailwind CSS 4+** - Utility-first CSS framework (CSS-first config in `src/styles/global.css`) ### Key Dependencies - **next-intl 4+** - Internationalization (i18n) with 25 languages - **next-mdx-remote 5.0+** - MDX content processing - **Motion 12+** (`motion` package, formerly Framer Motion) - Animations and transitions; import from `motion/react` - **Radix UI** - Accessible component primitives - **shadcn/ui** - Component library built on Radix UI - **Recharts** - Data visualization - **Viem/Wagmi** - Ethereum blockchain integration ### Development & Testing - **Storybook 10+** - Component development and testing - **Chromatic** - Visual regression testing - **ESLint** - Code linting with custom rules - **Prettier** - Code formatting - **Husky** - Git hooks - **PNPM** - Package manager ## Project Structure - **app/** - Next.js App Router pages - **[locale]/** - Internationalized routes - **src/** - **components/** - React components - **ui/** - Design system components - **icons/** - SVG icon components - **data/** - Static data and configurations - **hooks/** - Custom React hooks - **i18n/** - Internationalization config - **intl/** - Translation files (25 languages) - **layouts/** - Page layout components - **lib/** - Utility functions and types - **constants.ts** - App constants - **types.ts** - TypeScript type definitions - **utils/** - Utility functions - **styles/** - Global styles and design tokens - **public/** - Static assets - **content/** - Markdown content files - **images/** - Image assets - **docs/** - Development documentation - **solutions/** - Documented solutions to past problems, organized by category with YAML frontmatter (module, tags, problem_type) ## Code Conventions ### File Naming - **Components**: kebab-case (e.g., `button-group.tsx`) - **Utilities**: camelCase (e.g., `cn.ts`, `relativePath.ts`) - **Pages**: kebab-case following Next.js conventions - **Assets**: kebab-case (e.g., `eth-logo.png`) ### TypeScript Patterns - Use `interface` for object shapes, `type` for unions/intersections - Prefer explicit typing over `any` (ESLint enforces `fixToUnknown`) - **NEVER leave unused variables or parameters** - ESLint `unused-imports/no-unused-vars` will fail the Netlify build. The only allowed unused arg pattern is a single underscore `_`. Do NOT use `_prefixedNames` (e.g., `_foo`) - either use the variable or remove it from the signature entirely. - Use generic constraints for reusable components - Export types from dedicated files in `@/lib/types` ### Styling Conventions - **Primary approach**: Tailwind CSS utility classes - **Component variants**: Use `tailwind-variants` (`tv`) for new and refactored work. Existing `class-variance-authority` (`cva`) components don't need bulk migration -- swap to `tv` opportunistically when you're already touching the component for another reason. - **Dynamic classes**: Use `cn()` utility (clsx + tailwind-merge) - **Custom properties**: CSS variables in `src/styles/` for theme values - **Responsive design**: Mobile-first approach For UI work, see the **`design-system` skill** at `.claude/skills/design-system/`. It's the canonical knowledge base for component choices, design tokens, RTL/i18n, server/client boundaries, and the "use a variant, not a new component" pattern. ## Development Workflows ### Available Scripts ```bash # Development pnpm dev # Start development server (Turbopack) pnpm dev:webpack # Start development server with webpack pnpm build # Build for production (Turbopack) pnpm build:webpack # Build for production with webpack pnpm start # Start production server # Code Quality pnpm lint # Run ESLint pnpm lint:fix # Fix ESLint issues pnpm type-check # TypeScript type checking pnpm format # Format with Prettier # Testing pnpm test:unit # Playwright unit tests (unit project) pnpm test:e2e # Playwright end-to-end tests pnpm test:visual # Playwright + Chromatic full-page visual tests # Storybook pnpm storybook # Start Storybook dev server pnpm build-storybook # Build Storybook pnpm chromatic # Storybook Chromatic (needs CHROMATIC_STORYBOOK_TOKEN) # Content Management pnpm lint:md # Lint English markdown content pnpm lint:md:fix # Auto-fix header IDs and duplicates ``` ### Testing Strategy - **Unit Testing**: Playwright `unit` project (`pnpm test:unit`) — heaviest coverage is the intl-pipeline sanitizer - **E2E Testing**: Playwright (`pnpm test:e2e`), see `docs/e2e-testing.md` - **Visual Testing**: Storybook + Chromatic for component regression; Playwright + Chromatic for full pages (see the `page-visual-tests` skill) - **Type Safety**: TypeScript strict mode enabled - **Linting**: ESLint with custom rules for imports and TypeScript ## Content Management ### Internationalization - **25 languages** supported (canonical list: `i18n.config.json`); **RTL support** for Arabic, Urdu - JSON UI strings in `src/intl/[locale]/`; translated markdown content in `public/content/translations/[locale]/` - Non-English markdown is propagated by the **intl-pipeline** (`src/scripts/intl-pipeline/`, entry `main.ts`). **Do not hand-propagate English changes into non-English files** -- let the pipeline run, or trigger `intl-pipeline.yml` with `stamp_only: true` if manifests must catch up urgently (e.g. unblocking a build). Hand-fixing a translation error is fine when the English side hasn't moved, since the manifest mapping stays valid. Spec: `tests/specs/PIPELINE-SPEC.md`. - Glossary: base URL from `GLOSSARY_API_URL` env var; default in `src/scripts/intl-pipeline/config.ts`. ETHGlossary is authoritative for Ethereum term translations. For pipeline mechanics, recovery, manifests, ETHGlossary integration, and the `intl/pending-{base}` orchestration model, see the **`intl-pipeline` skill** at `.claude/skills/intl-pipeline/`. For translation-quality review (scoring rubric, language-group rules, ETHGlossary-as-authority policy, multi-agent role split), see the **`intl-review` skill** at `.claude/skills/intl-review/`. ### Markdown Content - Educational content stored in `public/content/` - Processed with `next-mdx-remote` - Custom MDX components for rich content - Automatic table of contents generation - **All h1-h4 headings require a custom `{#lower-kebab-id}`** -- enforced by markdownlint via pre-commit hook. Run `pnpm lint:md:fix` to auto-add missing IDs. Config: `.markdownlint-cli2.jsonc`, custom rules: `.markdownlint-rules/` ### Asset Management - Images optimized with Next.js Image component - SVGs loaded as React components via `@svgr/webpack` - Static assets served from `public/` - Placeholder generation for images ## SEO & Meta - Sitemap generation in `app/sitemaps/sitemap.ts` (one sitemap per locale, indexed by `app/sitemap.xml/route.ts`) - Meta tags and Open Graph optimization - Structured data for search engines - Security headers (X-Frame-Options: DENY) ## Development Guidelines ### When Working on Features 1. **Check existing patterns** - Look at similar components first 2. **Prioritize Server Components** - Use App Router and Server Components when possible 3. **Follow import order** - ESLint will enforce, but be proactive 4. **Use TypeScript strictly** - No `any` types, prefer `unknown` 5. **Test in Storybook** - Create stories for new components (filename pattern: `.stories.tsx`) 6. **Consider i18n** - All user-facing text should be translatable. Server components: `getTranslations` and `getLocale` from `next-intl/server`. Client components: `useTranslations` from `next-intl`, one namespace-bound function per namespace - to access a second namespace, bind another function (e.g. `const tCommon = useTranslations("common")`) rather than reaching across namespaces 7. **Mobile-first** - Design for mobile, enhance for desktop 8. **Accessibility** - Use Radix primitives, semantic HTML 9. **Use locale-aware formatting wrappers** - Use `numberFormat()` from `src/lib/utils/numbers.ts` instead of `new Intl.NumberFormat()`, and `dateTimeFormat()` from `src/lib/utils/date.ts` instead of `new Intl.DateTimeFormat()` / `.toLocaleDateString()` / `.toLocaleTimeString()`. Both enforce correct numbering systems and calendar for Urdu and Arabic locales. ### Component Development 1. Create component in appropriate `src/components/` subdirectory - Use `src/components/ui` for shadcn components or pure UI components 2. Add TypeScript types and proper props interface 3. Accept `ref` as a regular prop when needed (React 19); `forwardRef` only survives in legacy components 4. Add a Storybook story (see below) 5. Export from appropriate index file 6. Update documentation if adding new patterns ### Storybook stories **Location**: `__stories__/` for flat directories (`ui/`, `styles/`), co-located for foldered components. Story globs are recursive, so nesting works. **Filenames are kebab-case** (`app-card.stories.tsx`) even inside a PascalCase component directory -- enforced by the test below. **Title**: `Section / Group / Name`. The section is derived from the file path -- not a judgment call: | Path | Section | | ------------------------------------- | --------------- | | `src/styles/**` | `Design System` | | `src/components/ui/**` | `UI` | | `src/components/**` (everything else) | `Components` | | `src/layouts/**` | `Layouts` | | `app/**` | `Pages` | Use a second-level group only when an established one fits (Actions, Forms, Layout, Navigation, Overlays, Data Display, Cards, Heroes, Site Chrome, Content, Data Viz, Features). **Don't invent a group for a single component** -- a flat `Components / Morpher` is correct and preferred. `tests/unit/storybook/story-titles.spec.ts` enforces the section prefix, explicit titles, and title uniqueness (Storybook silently merges duplicate titles into one sidebar entry). **Don't add a story for a deprecated component or variant.** A rendered example reads as an endorsement whatever the caption says, so the showcase should only contain things that are available to use -- note the deprecation in prose instead (see `HR`). A component whose future is merely undecided can keep its story with a status blurb (see `Carousel`). Add `tags: ["autodocs"]`. Write the `parameters.docs.description.component` blurb for someone deciding whether to use the thing -- constraints and gotchas, not a restatement of the props table. **A green build does not mean a story renders.** `pnpm build-storybook` compiles stories without executing them, and every `UI /` story sets `chromatic: { disableSnapshot: true }`, so neither CI signal catches a story that throws. Open new stories in `pnpm storybook` before pushing. Async server components render via a `next-intl/server` shim (`.storybook/next-intl-server.tsx`). Components calling `usePathname` need `parameters.nextjs.navigation.pathname`. New i18n namespaces must be added to the `ns` array in `.storybook/next-intl.ts`, or strings render as raw key tails. ### Content Updates 1. Markdown files go in `public/content/` 2. Images in `public/images/` with descriptive names 3. Translation strings in appropriate `src/intl/` JSON files 4. Data files in `src/data/` with TypeScript types ### Type-Safe Chain Names This project enforces type-safe chain names via TypeScript. When working with layer 2 networks or wallet data: **Critical Files:** - `src/data/chains.ts` - Canonical source of all chain names (auto-updated weekly) - `src/lib/types.ts` - Defines `ChainName` type derived from chains.ts - `src/data/networks/networks.ts` - Uses `chainName: ChainName` - `src/data/wallets/wallet-data.ts` - Uses `supported_chains: ChainName[]` **Rules:** 1. **Always look up exact names** - Before adding `chainName` or `supported_chains`, search `chains.ts` for the exact `name` value 2. **Names are case-sensitive and exact** - e.g., use `"Zircuit Mainnet"` not `"Zircuit"`, use `"OP Mainnet"` not `"Optimism"` 3. **Run type checking** - Use `pnpm type-check` to verify chain names are valid before committing 4. **Non-EVM chains** - For Starknet and other non-EVM chains, use `NonEVMChainName` type **Common Mistakes:** - Using informal names: `"Optimism"` should be `"OP Mainnet"` - Missing "Mainnet" suffix: `"Zircuit"` should be `"Zircuit Mainnet"` - Wrong casing: `"zksync Mainnet"` should be `"zkSync Mainnet"` ## Key Dependencies to Know ### UI & Styling - `@radix-ui/*` - Accessible component primitives - `tailwind-variants` - Component variant patterns - `motion` - Animation library (formerly framer-motion; import from `motion/react`) - `lucide-react` - Icon library ### Content & Data - `gray-matter` - Frontmatter parsing - `recharts` - Data visualization ### Ethereum Integration - `viem` - Ethereum library - `wagmi` - React hooks for Ethereum - `@rainbow-me/rainbowkit` - Wallet connection ## A/B Testing The site uses a GDPR-compliant, cookie-less A/B testing system built on the Flags SDK precompute pattern, integrated with Matomo. The proxy assigns variants at the edge (header fingerprinting) and rewrites to signed, statically prerendered variant pages under `ab-code/[code]/` — experiments never force dynamic rendering. Experiment status, weights, and scheduling live in the Matomo dashboard (no deploys needed). Only the default locale is tested; other locales always get the original. Key gotcha: **variants are matched by array index, not names** — the `variants` array order must match the Matomo experiment order exactly, and the `testKey` must match the Matomo experiment name exactly. Full guide (architecture, step-by-step recipe, env vars): `docs/ab-testing.md`. Code: `proxy.ts`, `src/lib/ab-testing/`, `src/components/AB/`, `app/[locale]/ab-code/`. ## Site Search Search runs on a self-hosted Typesense instance, one collection per locale, crawled by DocSearch from production ethereum.org. Publishing is gated: a crawl goes to a staging collection and the live alias only moves if the new index passes size, field and relevance checks. Two things bite most often. **Ranking hints reach the index as `docsearch:*` meta tags** from `src/lib/utils/metadata.ts`, so a ranking change does nothing until the site is deployed and re-crawled. And **the networks offered as block explorer results come from `src/data/networks/networks.ts`** -- the same list `/layer-2/networks` renders -- so adding an L2 there is the only edit needed. Overview: `docs/site-search.md`. Runbook, secrets and recovery: `typesense/README.md`. Code: `src/components/Search/`, `src/scripts/typesense/`, `typesense/`. ## Deployment - **Platform**: Netlify (config in `netlify.toml`) - **Next.js Integration**: Uses `@netlify/plugin-nextjs` for seamless Netlify and Next.js compatibility - **Monitoring**: Matomo analytics integration ### Runtime constraints - **`public/content` is not in the Netlify server function** (CDN-only). A page that reads it at render time must stay static (`revalidate: false` getters like `getStaticAppsData`) or have an `outputFileTracingIncludes` entry in `next.config.js`. A finite-`revalidate` getter (e.g. `getEventsData`, 24h) turns the whole route into ISR. Details: `docs/solutions/integration-issues/netlify-isr-404-async-server-components.md`. ## Internal Infrastructure The following external-looking services are managed by the ethereum.org team: - **`s3-dcl1.ethquokkaops.io`** — S3-compatible object storage for app screenshots and media. Used by the data layer to serve images for the `/dapps` and app listing pages. Downtime here means broken images on the live site. - **Netlify Blobs** (`@netlify/blobs`) — Key-value store used by the data layer to cache API responses. Accessed via `src/data-layer/storage.ts`.