ethereum-org-website

GitHub

Ethereum.org is a primary online resource for the Ethereum community.

RAW Rules

AGENTS.md

# 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/sitemap.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/`.

## 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

## 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`.