# General improvements
- Use ZOD for input/return value parsing in endpoints when results are being returned, or input validation when api endpoints are being called with input (see appendix A)
- Unsafe JSON Parsing `.json()` calls without try/catch blocks. If response isn't valid JSON, unhandled errors crash the route. Example: `pages/api/score/[address].tsx:67,76,85.` Fix: Wrap all .json() calls in try/catch or use a safe JSON parser utility.
- Implement react error boundaries to prevent crashing of entire app in certain cases. Fix: Add error boundaries at route/layout level and around critical components.
- Missing React.memo Optimizations. Location: Multiple components (e.g., components/DelegatingWidget/Input.tsx)
- Inconsistent Error Response Format in API routes. Problem: Some routes return `res.status(500).end("message")`, others return `res.status(500).json(null)`, some return `res.status(500).json({ error: … })`.
- There are more data hooks that could be prefetched, such as `useAllScoreData()`.
# NextJS specific
- Using server components will require rewriting to app router. A non-trivial rewrite that will touch many components. Personal recommendation is to stick to the pages router. With the update to nextjs 16 performance seems to have already significantly improved.
# Performance
- All results in `/lib/api/contracts` can be cached, as they probably change infrequently and are called very often.
- Move `useChartData()` fetching to `getStaticProps()`
- Move independent calls to `Promise.all()`, instead of doing them sequentially.
- Re-enable ENS pre-fetching in apollo queries (or at least investigate why this was disabled/commented out)
- Add dynamic imports for OrchestratorList, ExplorerChart. these are big components with dependencies that will then be loaded when scrolled into view. This significantly reduces initial JS payload.
# Appendix A: Zod Validation Opportunities
This document identifies areas where Zod schemas can be used to validate:
1. **API endpoint inputs** (query parameters, request bodies)
2. **API endpoint return values** (response data)
3. **External API responses** (third-party API data)
## Current State
- Zod is already installed (`zod@^4.1.12` in package.json)
- Zod is currently used in `pages/api/usage.tsx` to validate external API responses (`DayDataSchema`)
- Basic address validation exists via `isValidAddress()` helper, but could be enhanced with Zod
---
## 1. API Endpoint Input Validation
### 1.1 Address-based Endpoints
These endpoints accept an `address` query parameter that should be validated:
#### `/api/account-balance/[address].tsx`
- **Input**: `address` query param
- **Current validation**: `isValidAddress(address)` - basic string check
- **Zod opportunity**: Create `AddressSchema = z.string().regex(/^0x[a-fA-F0-9]{40}$/)` for stricter validation
#### `/api/ens-data/[address].tsx`
- **Input**: `address` query param
- **Current validation**: `isValidAddress(address)` + blacklist check
- **Zod opportunity**: Same as above, plus validate blacklist separately
#### `/api/score/[address].tsx`
- **Input**: `address` query param
- **Current validation**: `isValidAddress(address)`
- **Zod opportunity**: Address validation schema
#### `/api/pending-stake/[address].tsx`
- **Input**: `address` query param
- **Current validation**: `isValidAddress(address)`
- **Zod opportunity**: Address validation schema
#### `/api/l1-delegator/[address].tsx`
- **Input**: `address` query param
- **Current validation**: `isValidAddress(address)`
- **Zod opportunity**: Address validation schema
### 1.2 Treasury/Proposal Endpoints
#### `/api/treasury/proposal/[proposalId]/state.tsx`
- **Input**: `proposalId` query param (string)
- **Current validation**: Basic existence check `if (!proposalId)`
- **Zod opportunity**:
```typescript
const ProposalIdSchema = z.string().regex(/^\d+$/) // numeric string
```
#### `/api/treasury/proposal/[proposalId]/votes/[address].tsx`
- **Input**:
- `proposalId` query param
- `address` query param
- **Current validation**: Basic checks
- **Zod opportunity**: Combined schema for both params
#### `/api/treasury/votes/[address]/index.tsx`
- **Input**: `address` query param
- **Current validation**: `isValidAddress(address)`
- **Zod opportunity**: Address validation schema
#### `/api/treasury/votes/[address]/registered.tsx`
- **Input**: `address` query param
- **Current validation**: `isValidAddress(address)`
- **Zod opportunity**: Address validation schema
### 1.3 POST Endpoints with Request Bodies
#### `/api/upload-ipfs.tsx`
- **Input**: `req.body` (JSON object)
- **Current validation**: None - directly passes `req.body` to external API
- **Zod opportunity**:
```typescript
const IpfsUploadSchema = z.object({
// Define expected structure of IPFS upload data
// This depends on what data is actually being uploaded
})
```
#### `/api/generateProof.tsx`
- **Input**: `req.body` with `{ account, delegate, stake, fees }`
- **Current validation**: None - directly destructures from `req.body`
- **Zod opportunity**:
```typescript
const GenerateProofSchema = z.object({
account: z.string().regex(/^0x[a-fA-F0-9]{40}$/),
delegate: z.string().regex(/^0x[a-fA-F0-9]{40}$/),
stake: z.string(), // or z.bigint() if needed
fees: z.string(), // or z.bigint() if needed
})
```
### 1.4 Query Parameters with Optional Values
#### `/api/pipelines/index.tsx`
- **Input**: Optional `region` query param
- **Current validation**: None
- **Zod opportunity**:
```typescript
const PipelinesQuerySchema = z.object({
region: z.string().optional(),
})
```
#### `/api/score/index.tsx`
- **Input**: Optional `pipeline` and `model` query params
- **Current validation**: None
- **Zod opportunity**:
```typescript
const ScoreQuerySchema = z.object({
pipeline: z.string().optional(),
model: z.string().optional(),
})
```
---
## 2. API Endpoint Return Value Validation
All API endpoints return typed data, but there's no runtime validation. Adding Zod schemas would catch contract/API changes early.
### 2.1 Account/Balance Endpoints
#### `/api/account-balance/[address].tsx`
- **Return type**: `AccountBalance`
- **Zod opportunity**:
```typescript
const AccountBalanceSchema = z.object({
balance: z.string(),
allowance: z.string(),
})
```
#### `/api/pending-stake/[address].tsx`
- **Return type**: `PendingFeesAndStake`
- **Zod opportunity**:
```typescript
const PendingFeesAndStakeSchema = z.object({
pendingStake: z.string(),
pendingFees: z.string(),
})
```
#### `/api/l1-delegator/[address].tsx`
- **Return type**: `L1Delegator`
- **Zod opportunity**:
```typescript
const UnbondingLockSchema = z.object({
id: z.number(),
amount: z.string(),
withdrawRound: z.string(),
})
const L1DelegatorSchema = z.object({
delegateAddress: z.string(),
pendingStake: z.string(),
pendingFees: z.string(),
transcoderStatus: z.enum(["not-registered", "registered"]),
unbondingLocks: z.array(UnbondingLockSchema),
activeLocks: z.array(UnbondingLockSchema),
})
```
### 2.2 ENS Endpoints
#### `/api/ens-data/[address].tsx`
- **Return type**: `EnsIdentity`
- **Zod opportunity**:
```typescript
const EnsIdentitySchema = z.object({
id: z.string(),
idShort: z.string(),
avatar: z.string().nullable().optional(),
name: z.string().nullable().optional(),
url: z.string().nullable().optional(),
twitter: z.string().nullable().optional(),
github: z.string().nullable().optional(),
description: z.string().nullable().optional(),
})
```
### 2.3 Round/Protocol Endpoints
#### `/api/current-round.tsx`
- **Return type**: `CurrentRoundInfo`
- **Zod opportunity**:
```typescript
const CurrentRoundInfoSchema = z.object({
id: z.number(),
startBlock: z.number(),
initialized: z.boolean(),
currentL1Block: z.number(),
currentL2Block: z.number(),
})
```
### 2.4 Performance/Score Endpoints
#### `/api/score/[address].tsx`
- **Return type**: `PerformanceMetrics`
- **Zod opportunity**:
```typescript
const RegionalValuesSchema = z.record(z.string(), z.number())
const ScoreSchema = z.object({
value: z.number(),
region: z.string(),
model: z.string(),
pipeline: z.string(),
orchestrator: z.string(),
})
const PerformanceMetricsSchema = z.object({
successRates: RegionalValuesSchema,
roundTripScores: RegionalValuesSchema,
scores: RegionalValuesSchema,
pricePerPixel: z.number(),
topAIScore: ScoreSchema,
})
```
#### `/api/score/index.tsx`
- **Return type**: `AllPerformanceMetrics`
- **Zod opportunity**:
```typescript
const AllPerformanceMetricsSchema = z.record(
z.string(),
PerformanceMetricsSchema
)
```
### 2.5 Treasury Endpoints
#### `/api/treasury/proposal/[proposalId]/state.tsx`
- **Return type**: `ProposalState`
- **Zod opportunity**:
```typescript
const ProposalStateSchema = z.object({
id: z.string(),
state: z.enum([
"Pending",
"Active",
"Canceled",
"Defeated",
"Succeeded",
"Queued",
"Expired",
"Executed",
"Unknown",
]),
quota: z.string(),
quorum: z.string(),
totalVoteSupply: z.string(),
votes: z.object({
against: z.string(),
for: z.string(),
abstain: z.string(),
}),
})
```
#### `/api/treasury/votes/[address]/index.tsx`
- **Return type**: `VotingPower`
- **Zod opportunity**:
```typescript
const VotingPowerSchema = z.object({
proposalThreshold: z.string(),
self: z.object({
address: z.string().regex(/^0x[a-fA-F0-9]{40}$/),
votes: z.string(),
}),
delegate: z.object({
address: z.string().regex(/^0x[a-fA-F0-9]{40}$/),
votes: z.string(),
}).optional(),
})
```
#### `/api/treasury/votes/[address]/registered.tsx`
- **Return type**: `RegisteredToVote`
- **Zod opportunity**:
```typescript
const RegisteredToVoteSchema = z.object({
registered: z.boolean(),
delegate: z.object({
address: z.string().regex(/^0x[a-fA-F0-9]{40}$/),
registered: z.boolean(),
}),
})
```
#### `/api/treasury/proposal/[proposalId]/votes/[address].tsx`
- **Return type**: `ProposalVotingPower`
- **Zod opportunity**:
```typescript
const ProposalVotingPowerSchema = z.object({
self: z.object({
address: z.string().regex(/^0x[a-fA-F0-9]{40}$/),
votes: z.string(),
hasVoted: z.boolean(),
}),
delegate: z.object({
address: z.string().regex(/^0x[a-fA-F0-9]{40}$/),
votes: z.string(),
hasVoted: z.boolean(),
}).optional(),
})
```
### 2.6 Pipeline/Region Endpoints
#### `/api/pipelines/index.tsx`
- **Return type**: `AvailablePipelines`
- **Zod opportunity**:
```typescript
const PipelineSchema = z.object({
id: z.string(),
models: z.array(z.string()),
regions: z.array(z.string()),
})
const AvailablePipelinesSchema = z.object({
pipelines: z.array(PipelineSchema),
})
```
#### `/api/regions/index.ts`
- **Return type**: `Regions`
- **Zod opportunity**:
```typescript
const RegionSchema = z.object({
id: z.string(),
name: z.string(),
type: z.enum(["transcoding", "ai"]),
})
const RegionsSchema = z.object({
regions: z.array(RegionSchema),
})
```
### 2.7 Usage/Chart Endpoints
#### `/api/usage.tsx`
- **Return type**: `HomeChartData`
- **Current validation**: Already uses `DayDataSchema` for external API response
- **Zod opportunity**: Create schema for the full `HomeChartData` return type
#### `/api/upload-ipfs.tsx`
- **Return type**: `AddIpfs`
- **Zod opportunity**:
```typescript
const AddIpfsSchema = z.object({
hash: z.string(),
})
```
---
## 3. External API Response Validation
These endpoints fetch data from external APIs and should validate responses before using them.
### 3.1 Metrics/AI Server Responses
#### `/api/score/[address].tsx`
- **External APIs**:
1. `NEXT_PUBLIC_AI_METRICS_SERVER_URL/api/top_ai_score` → `ScoreResponse`
2. `NEXT_PUBLIC_METRICS_SERVER_URL/api/aggregated_stats` → `MetricsResponse`
3. Pricing URL → `PriceResponse`
- **Current validation**: None - directly uses `await response.json()`
- **Zod opportunity**:
```typescript
const ScoreResponseSchema = z.object({
value: z.number(),
region: z.string(),
model: z.string(),
pipeline: z.string(),
orchestrator: z.string(),
})
const MetricSchema = z.object({
success_rate: z.number(),
round_trip_score: z.number(),
score: z.number(),
})
const MetricsResponseSchema = z.record(
z.string(),
z.record(z.string(), MetricSchema).optional()
)
const PriceResponseSchema = z.array(z.object({
Address: z.string(),
ServiceURI: z.string(),
LastRewardRound: z.number(),
RewardCut: z.number(),
FeeShare: z.number(),
DelegatedStake: z.string(),
ActivationRound: z.number(),
DeactivationRound: z.string(),
Active: z.boolean(),
Status: z.string(),
PricePerPixel: z.number(),
UpdatedAt: z.number(),
}))
```
#### `/api/score/index.tsx`
- **External APIs**: Same as above
- **Current validation**: None
- **Zod opportunity**: Same schemas as above
#### `/api/pipelines/index.tsx`
- **External API**: `NEXT_PUBLIC_AI_METRICS_SERVER_URL/api/pipelines`
- **Current validation**: None - directly uses `await response.json()`
- **Zod opportunity**: Use `AvailablePipelinesSchema` (defined in section 2.6)
#### `/api/regions/index.ts`
- **External APIs**:
- `NEXT_PUBLIC_METRICS_SERVER_URL/api/regions`
- `NEXT_PUBLIC_AI_METRICS_SERVER_URL/api/regions`
- **Current validation**: None
- **Zod opportunity**: Use `RegionsSchema` (defined in section 2.6)
### 3.2 Livepeer.com API
#### `/api/usage.tsx`
- **External API**: `https://livepeer.com/data/usage/query/total`
- **Current validation**: ✅ Already uses `DayDataSchema` with `safeParse()`
- **Status**: Good example of proper validation
### 3.3 Pinata IPFS API
#### `/api/upload-ipfs.tsx`
- **External API**: `https://api.pinata.cloud/pinning/pinJSONToIPFS`
- **Current validation**: None - directly uses `await fetchResult.json()`
- **Zod opportunity**:
```typescript
const PinataResponseSchema = z.object({
IpfsHash: z.string(),
// Add other fields if Pinata returns more
})
```
### 3.4 ENS Provider Responses
#### `lib/api/ens.ts` → `getEnsForAddress()`
- **External API**: Ethereum provider ENS lookups
- **Current validation**: None - directly uses provider responses
- **Zod opportunity**: Validate ENS resolver text records and avatar URLs
---
## 4. SSR/Client-Side API Calls
### 4.1 Server-Side Rendering
#### `lib/api/ssr.ts` → `getEnsIdentity()`
- **Internal API call**: `/api/ens-data/${address}`
- **Current validation**: None - directly uses `await response.json()`
- **Zod opportunity**: Use `EnsIdentitySchema` to validate the response
---
## 5. Recommended Implementation Strategy
### Phase 1: Shared Schemas
1. Create `lib/api/schemas/` directory
2. Create shared schemas for common types:
- `address.ts` - Address validation
- `common.ts` - Common types (strings, numbers, etc.)
- `ens.ts` - ENS-related schemas
- `treasury.ts` - Treasury/proposal schemas
- `performance.ts` - Performance metrics schemas
### Phase 2: Input Validation
1. Add input validation to all endpoints with query params
2. Add request body validation to POST endpoints
3. Return proper 400 errors with validation details
### Phase 3: Output Validation
1. Add return value validation to all endpoints
2. Log validation errors (don't fail in production, but log for monitoring)
3. Consider failing in development mode to catch issues early
### Phase 4: External API Validation
1. Validate all external API responses
2. Use `safeParse()` to handle validation errors gracefully
3. Add retry logic or fallback values where appropriate
### Example Implementation Pattern
```typescript
// lib/api/schemas/address.ts
import { z } from "zod";
export const AddressSchema = z.string().regex(/^0x[a-fA-F0-9]{40}$/);
// lib/api/schemas/account-balance.ts
import { z } from "zod";
export const AccountBalanceSchema = z.object({
balance: z.string(),
allowance: z.string(),
});
// pages/api/account-balance/[address].tsx
import { AddressSchema } from "@lib/api/schemas/address";
import { AccountBalanceSchema } from "@lib/api/schemas/account-balance";
const handler = async (req: NextApiRequest, res: NextApiResponse) => {
// Validate input
const addressResult = AddressSchema.safeParse(req.query.address);
if (!addressResult.success) {
return res.status(400).json({ error: "Invalid address", details: addressResult.error });
}
// ... fetch data ...
// Validate output
const result = AccountBalanceSchema.safeParse(accountBalance);
if (!result.success) {
console.error("Account balance validation failed:", result.error);
// In production, might still return the data, but log the error
// In development, could return 500 to catch issues early
}
return res.status(200).json(accountBalance);
};
```
---
## Summary
**Total Opportunities:**
- **Input Validation**: ~15 endpoints need query param/body validation
- **Output Validation**: ~20 endpoints need return value validation
- **External API Validation**: ~8 external API calls need response validation
**Priority:**
1. **High**: External API responses (can cause runtime errors)
2. **Medium**: POST request bodies (security/data integrity)
3. **Medium**: Return value validation (catch contract changes)
4. **Low**: Query parameter validation (basic checks exist)
**Estimated Impact:**
- Better error messages for invalid inputs
- Early detection of API contract changes
- Protection against malformed external API responses
- Improved type safety at runtime
# Appendix B: Top candidates for server components
1. **`Stat`** — Pure presentational; no hooks; just displays props
2. **`MarkdownRenderer`** — Mostly presentational; `useMemo` can be moved to server
3. **`AccountCell`** — Fetches ENS via `useEnsData`; move fetch to server and pass data as props
4. **`Profile`** — Mostly presentational; only needs client for copy-to-clipboard
5. **`RoundStatus`** — Fetches round data; move to server, keep theme logic client-side
6. **`InactiveWarning`** — Uses GraphQL query; fetch on server and pass data as props
7. **`TreasuryProposalRow`** — Likely displays fetched data; move fetch to server
8. **`TransactionsList`** — Uses `useEnsData`; fetch ENS on server and pass resolved data
## Components to keep as client components
- `OrchestratorList` — Interactive filters (time horizon, delegation amount)
- `Table` — Pagination and sorting
- `Logo` — Hover animations
- `CodeBlock` — Copy functionality
- `DelegatingWidget`, `VotingWidget` — Interactive forms/actions
- `Search` — Interactive search
**Pattern**: Move data fetching to server components; keep interactive UI as client components.