Frontend Development Guide¶
This guide covers the day-to-day development workflow for the 2Sigma frontend (ai-tutor-ui), a Next.js 16 application.
Prerequisites¶
Before starting, ensure you've completed the Getting Started Guide.
Daily Workflow¶
# 1. Pull latest changes
git pull origin main
# 2. Install dependencies (if package.json changed)
npm install
# 3. Start dev server
npm run dev
The app runs at http://localhost:3000.
Hot Reload¶
Next.js Fast Refresh is automatic. The browser updates instantly when you save a file, preserving component state. No manual restart needed.
Restart required for env changes
Changes to .env.local require stopping and restarting the dev server. Fast Refresh doesn't pick up environment variable changes.
Environment Variables¶
All client-side variables must start with NEXT_PUBLIC_. Without that prefix, they're invisible to the browser.
Config file: .env.local (never committed, listed in .gitignore). Copy .env.local.example to get started:
| Variable | Required | Description |
|---|---|---|
NEXT_PUBLIC_API_BASE_URL |
Yes | Backend API URL (e.g., http://localhost:9898/api/v1) |
NEXT_PUBLIC_APP_NAME |
No | Application display name (default: "2Sigma") |
Code Organization & Naming Conventions¶
Project Structure¶
ai-tutor-ui/
├── app/ # Next.js App Router pages
│ ├── layout.tsx # Root layout
│ ├── page.tsx # Home route
│ ├── globals.css # Global styles + CSS custom properties
│ ├── dashboard/
│ ├── courses/
│ ├── signup-onboarding/
│ └── chat-debug/
│
├── components/ # Shared React components
│ ├── ui/ # shadcn/ui primitives
│ └── [feature]/ # Feature-specific components
│
├── hooks/ # Custom React hooks
├── lib/
│ ├── api/ # API client functions
│ ├── validations/ # Zod schemas for forms
│ ├── authClient.ts # apiFetch + token management
│ └── utils.ts # Shared utilities
│
├── types/ # TypeScript type definitions
├── tests/ # Test setup and utilities
│ ├── setup.ts
│ ├── test-utils.tsx
│ └── mocks/ # MSW request handlers
│
└── public/ # Static assets
Naming Conventions¶
Pages/Routes — kebab-case directories under app/:
Components — PascalCase .tsx files:
Hooks — use- prefix, kebab-case files:
API files — kebab-case matching the resource:
Types — kebab-case files in types/:
Validations — kebab-case in lib/validations/:
lib/validations/auth.ts
lib/validations/course.ts
lib/validations/module.ts
lib/validations/content.ts
lib/validations/onboarding.ts
Test co-location — __tests__/ directories adjacent to source files:
Adding a New Page¶
- Create a directory under
app/(e.g.,app/my-feature/) - Add
page.tsxas the route component - Optionally add
layout.tsx,loading.tsx, orerror.tsxalongside it - Create feature components in
components/my-feature/if the page needs them
app/my-feature/
├── page.tsx # Required — the route
├── layout.tsx # Optional — wraps this route's subtree
├── loading.tsx # Optional — shown while page suspends
└── error.tsx # Optional — error boundary for this route
App Router conventions
Next.js uses the App Router. Every file named page.tsx becomes a route. Files named layout.tsx, loading.tsx, and error.tsx are special and handled automatically by the framework.
Adding a New API Resource¶
Follow these steps to wire up a new backend resource end-to-end.
1. Create type definitions in types/my-resource.ts
2. Create API functions in lib/api/my-resource.ts using apiFetch:
// lib/api/my-resource.ts
import { apiFetch } from "../authClient"
export interface MyResource {
id: number
name: string
}
export async function listMyResources(): Promise<MyResource[]> {
return apiFetch<MyResource[]>("/my-resources/")
}
export async function createMyResource(data: { name: string }): Promise<MyResource> {
return apiFetch<MyResource>("/my-resources/", {
method: "POST",
body: data,
})
}
apiFetch handles auth headers, JSON serialization, and error parsing automatically. It reads the token from localStorage and throws an ApiError on non-2xx responses.
3. Create Zod validation schemas in lib/validations/my-resource.ts if the resource involves forms:
// lib/validations/my-resource.ts
import { z } from "zod"
export const myResourceSchema = z.object({
name: z.string().min(1, "Name is required"),
})
export type MyResourceFormData = z.infer<typeof myResourceSchema>
Adding a shadcn/ui Component¶
shadcn/ui components live in components/ui/. They're not installed as a package dependency — the CLI copies the source directly into your project so you can customize freely.
For example:
Each component uses Radix UI primitives for accessibility, Tailwind for styling, and class-variance-authority (cva) for variant management. Global theme tokens (colors, radius, etc.) are defined as CSS custom properties in app/globals.css.
Testing¶
Framework¶
- Vitest — test runner (fast, Vite-native)
- React Testing Library — component rendering and queries
- MSW (Mock Service Worker) — intercepts
fetchcalls at the network level
Commands¶
| Command | Description |
|---|---|
npm run test |
Watch mode — re-runs on file changes |
npm run test:run |
Single run — for CI |
npm run test:coverage |
With coverage report (v8 provider) |
npm run test:ui |
Visual Vitest UI in the browser |
Test Setup¶
tests/setup.ts runs before every test file. It:
- Mocks
next/navigation(router, pathname, search params) - Mocks
localStoragewith an in-memory store - Starts the MSW server before all tests and resets handlers after each test
tests/test-utils.tsx re-exports React Testing Library's render wrapped with app providers. Always import from here instead of @testing-library/react directly:
tests/mocks/ contains MSW request handlers that intercept API calls during tests. Add handlers here to mock specific endpoints.
Example Test¶
import { render, screen } from '@/tests/test-utils'
import { MyComponent } from '../MyComponent'
describe('MyComponent', () => {
it('renders correctly', () => {
render(<MyComponent />)
expect(screen.getByText('Expected text')).toBeInTheDocument()
})
})
Test file location
Place test files in a __tests__/ directory adjacent to the source file being tested. Name them *.test.ts or *.test.tsx.
Linting¶
ESLint is configured with next/core-web-vitals, which includes React, accessibility, and Next.js-specific rules.
Run this before committing. The CI pipeline will catch lint errors, but it's faster to fix them locally.
Docker¶
The Dockerfile uses a two-stage build:
- Builder (
node:20-alpine) — installs dependencies and runsnext build - Runner (
node:20-alpine) — copies only the standalone output, no dev dependencies
output: 'standalone' in next.config.js produces a minimal self-contained build. The image runs on port 3000.
Build args are baked in at image build time (not runtime):
docker build \
--build-arg NEXT_PUBLIC_APP_NAME="2Sigma" \
--build-arg NEXT_PUBLIC_API_BASE_URL="https://api.example.com/api/v1" \
-t ai-tutor-ui .
Build-time env vars
NEXT_PUBLIC_* variables are inlined into the JavaScript bundle during next build. Changing them requires rebuilding the image — you can't override them at container runtime.
Troubleshooting¶
CORS errors¶
The backend must include http://localhost:3000 in its BACKEND_CORS_ORIGINS setting. Check your backend .env:
Hydration mismatch¶
This usually means you're reading localStorage, window, or another browser-only API during server-side rendering. Wrap the access in a useEffect:
Module not found¶
Run npm install first. If the error persists, check the import path. The @/ alias maps to the project root, so @/components/ui/button resolves to ./components/ui/button.
Git Workflow¶
Branch Naming¶
- Features:
feature/short-description - Bug fixes:
fix/short-description - Docs:
docs/short-description
Commit Messages¶
Follow conventional commits:
feat: add course enrollment page
fix: resolve token refresh loop
docs: update frontend dev guide
refactor: simplify apiFetch error handling
test: add unit tests for CourseDialog
Pre-Commit Checklist¶
- Lint:
npm run lint - Tests:
npm run test:run - Build:
npm run build - Update docs if needed