FBT P
⌘K
Frontend Master Reference

FRONTEND DEVELOPMENTMASTER REFERENCE

Comprehensive reference for modern frontend development. Next.js 15, React 19, TypeScript, Tailwind, and best practices for scalable, performant, accessible applications.

25SECTIONS
120+TECHNIQUES
60+STANDARDS
React 19FRAMEWORK
Next.js 15React 19TypeScript 5Tailwind v4shadcn/uiZustandVitestPlaywright
📋

Core Principles

SOLID applied to frontend development. DRY, KISS, YAGNI, SoC, LoD minimize complexity. Composition over inheritance, colocation improve maintainability.

PrincipleDescriptionExampleBenefits
Single Responsibility PrincipleEach component should have a single reason to change. Components should do one thing well.Separate `UserCard` (display) from `useUserData` (logic)Easy to test, Reusable logic, Clear purpose
Open/Closed PrincipleComponents should be open for extension but closed for modification. Use composition and props.Button with variant prop instead of ButtonPrimary, ButtonSecondaryExtensible, Prevents breaking changes, Flexible
Liskov Substitution PrincipleDerived components should be substitutable for their parents without breaking the UI.Custom button can replace native button seamlesslyType-safe swaps, Polymorphism, Predictable behavior
Interface Segregation PrincipleClients should not depend on interfaces they do not use. Pass only needed props.Headless component API with minimal required propsFlexibility, Fewer breaking changes, Clear contracts
Dependency Inversion PrincipleDepend on abstractions, not concrete implementations. Inject dependencies.useApi(client) instead of hardcoding fetch()Testable, Pluggable, Decoupled
DRY (Don't Repeat Yourself)Extract common logic into reusable hooks, components, utilities to avoid duplication.Custom hook for form validation used across multiple formsSingle source of truth, Easy updates, Less code
KISS (Keep It Simple, Stupid)Simplicity should be a primary design goal. Choose clarity over cleverness.Explicit conditional rendering instead of complex ternary chainsReadability, Maintenance, Fewer bugs
YAGNI (You Aren't Gonna Need It)Don't add functionality until it's actually needed. Avoid over-engineering.Build pagination when users request it, not speculativelyLess code, Faster delivery, Easier pivots
Separation of ConcernsKeep display, logic, and data separate. UI concerns separate from business logic.Data in custom hooks, rendering in presentational componentsTestable, Maintainable, Reusable
Law of DemeterComponents should only communicate with their direct dependencies. Avoid deep prop drilling.Use context for cross-cutting data instead of passing through 5 levelsLoose coupling, Flexibility, Fewer prop changes
Command Query SeparationSeparate commands (mutations) from queries (reads). Use Server Actions for mutations.useQuery() for reads, useServerAction() for writesClear intent, Caching friendly, Type-safe
Convention over ConfigurationEstablish patterns so developers don't configure basic behavior. Let conventions guide.File-based routing, standard component folder structureConsistency, Less boilerplate, Clear patterns
Composition over InheritanceUse component composition instead of class inheritance. React favors this naturally.Wrap Button with context provider instead of extending Button classFlexible, No inheritance chains, Easier reasoning
ColocationPlace code as close as possible to where it's used. Keep related code together.Store component CSS/utils in same folder as componentEasy discovery, Easy deletion, Self-contained
Single Source of TruthStore data in one canonical place. Derived state should be computed, not stored.Store list, compute filtered/sorted views with useMemoNo sync bugs, Easier updates, Consistent data
Progressive EnhancementBuild core functionality that works without JavaScript, enhance with JS.Forms that submit to Server Actions even without JSResilience, Better perception, Works everywhere
📋

TypeScript Standards

Strict mode enabled. No implicit any. Use discriminated unions for exhaustive checking. Leverage generics and utility types for reusable, type-safe abstractions.

Strict Mode

Enable all strict type checks in tsconfig.json for maximum safety.

{
  "compilerOptions": {
    "strict": true,
    "noImplicitAny": true,
    "strictNullChecks": true,
    "strictFunctionTypes": true
  }
}
No Any

Avoid implicit any. When any is needed, use explicit types with justification.

// Bad
const data: any = fetchData();

// Good
const data: Promise = fetchData();

// Justified any
const data: any = JSON.parse(unknownString); // TODO: validate with Zod
Discriminated Unions

Use literal types in discriminator field to create exhaustive type checking.

type Success = { status: 'success'; data: User };
type Error = { status: 'error'; message: string };
type Result = Success | Error;

const handle = (result: Result) => {
  if (result.status === 'success') {
    console.log(result.data); // data is available
  }
};
Generics

Write generic functions and components that work with any type while preserving type safety.

function useApi(endpoint: string): Promise {
  return fetch(endpoint).then(r => r.json() as T);
}

const users = await useApi('/api/users');
Utility Types

Use TypeScript built-in utility types: Partial, Pick, Omit, Record, Readonly, Extract, Exclude.

type UserPreview = Pick;
type UserUpdate = Partial;
type Status = 'pending' | 'success' | 'error';
type StatusRecord = Record;
Type Inference

Let TypeScript infer types when possible. Explicit types where inference cannot work.

// Inferred
const items = [1, 2, 3]; // number[]
const config = { debug: true, maxRetries: 3 }; // inferred

// Explicit when needed
const items: (string | number)[] = [];
function parse(input: string): User { }
📋

Programming Paradigms

Functional programming with pure functions and immutability. Reactive patterns for automatic state updates. Component-driven development with declarative UI.

Functional Programming

Prefer pure functions, immutability, and function composition. Avoid side effects in rendering.

// Pure function
const filterUsers = (users: User[], role: string): User[] =>
  users.filter(u => u.role === role);

// In components
const visible = useMemo(
  () => filterUsers(users, selectedRole),
  [users, selectedRole]
);
Reactive Programming

Model UIs as streams of data changes. React to state changes automatically.

const query = useQuery(['users'], fetchUsers);

useEffect(() => {
  if (query.data) {
    console.log('Data updated:', query.data);
  }
}, [query.data]);
Component-Driven Development

Build UIs as isolated, composable components. Design in Storybook first.

export function Button(props: ButtonProps) {
  return 
Declarative UI

Describe what the UI should be, not how to build it. Let React handle updates.

// Declarative - describe state
{loading && }
{error && }
{data && }

// Not imperative DOM manipulation
Event-Driven Architecture

Components communicate through events and callbacks. Decouple with event emitters or pubsub.

const emit = useEventBus();

const handleClick = () => {
  emit('item:selected', item);
};
Atomic Design

Organize components in hierarchy: atoms → molecules → organisms → templates → pages.

atoms/Button.tsx
molecules/FormField.tsx
organisms/UserForm.tsx
templates/AuthLayout.tsx
pages/LoginPage.tsx
📋

Component Architecture

Atomic Design for hierarchy. Islands Architecture for progressive enhancement. Compound Components for flexible APIs. Composition patterns maximize reusability.

Atomic Design

Hierarchical component organization from atoms (Button) to pages. Establishes clear naming and composition patterns.

Islands Architecture

Ship isolated, interactive components on otherwise static HTML. Reduces JS payload and enables progressive enhancement.

Compound Components

Components that work together with shared state. Example: Tabs with Tab, TabList, TabPanel. Flexible API.

Composition Patterns

Build complex UIs by composing simple components. Props, children, slots, and context for flexible APIs.

Container/Presentational

Separate data fetching (Container) from rendering (Presentational). Easier testing and reuse.

Render Props

Component accepts function as child to customize rendering. Flexible but verbose alternative to hooks.

📋

Design Patterns

Proven patterns for React development. Compound Components, Custom Hooks, Render Props, HOCs, Headless Components, Provider Pattern. Hooks generally preferred over HOCs.

Compound Components

Components that work together with implicit shared state via context.


  Open
  
    Title
    Content
  
Custom Hooks

Extract component logic into reusable hooks. Share state and side effects.

function useUserData(userId: string) {
  const [user, setUser] = useState(null);
  useEffect(() => {
    fetchUser(userId).then(setUser);
  }, [userId]);
  return user;
}
Render Props

Pass function as prop to customize child rendering. More flexible than children.


  {({ data, loading, error }) => (
    <>
      {loading && }
      {data && }
    
  )}
Higher-Order Components

Function that takes component and returns enhanced component. Use sparingly, hooks preferred.

const withAuth = (Component) => (props) => {
  const { user } = useAuth();
  if (!user) return ;
  return ;
};
Headless Components

Unstyled, composable components that are logic-only. Style with your CSS solution.

import { Command } from 'cmdk';


  
  
    Item 1
  
Provider Pattern

Use context providers to share state globally without prop drilling.


  


// In any component
const theme = useTheme();
📋

State Management

Zustand for client state. React Query v5 for server state. Jotai for atomic state. useReducer+Context for medium complexity. Clear separation of concerns.

Zustand

Lightweight state management. Simple API, good DevTools, minimal boilerplate.

const useStore = create((set) => ({
  count: 0,
  increment: () => set((state) => ({ count: state.count + 1 })),
}));

// In components
const count = useStore((state) => state.count);
React Query v5

Server state management. Handles caching, refetching, background updates, pagination.

const { data, isLoading, error } = useQuery({
  queryKey: ['users'],
  queryFn: () => fetch('/api/users').then(r => r.json()),
  staleTime: 5 * 60 * 1000, // 5 minutes
});
Jotai

Atomic state management. Atoms as minimal units, great for complex state dependencies.

const countAtom = atom(0);
const doubledAtom = atom((get) => get(countAtom) * 2);

function Counter() {
  const [count, setCount] = useAtom(countAtom);
  return ;
}
useReducer + Context

Built-in React API for complex state logic. No external dependency, good for medium complexity.

const [state, dispatch] = useReducer(reducer, initialState);

function reducer(state, action) {
  switch (action.type) {
    case 'INCREMENT':
      return { ...state, count: state.count + 1 };
  }
}
📋

Next.js 15 App Router

File-based routing with page.tsx, layout.tsx, loading.tsx, error.tsx. Special files for conventions. Automatic code splitting and optimizations.

page.tsx

File convention for route segments. Exported React component renders the page UI.

// app/posts/page.tsx
export default function PostsPage() {
  return 

Posts

; } // Accessible at /posts
layout.tsx

Shared UI wrapping child routes. Persists across navigation, maintains state.

// app/layout.tsx
export default function RootLayout({ children }) {
  return (
    
      
        
{children}
); }
loading.tsx

Instant loading fallback. Suspense boundary replacement for route segments.

// app/posts/loading.tsx
export default function Loading() {
  return ;
}
error.tsx

Error boundary for route segment. Catches errors and displays fallback.

'use client';
export default function Error({ error, reset }) {
  return (
    <>
      

Something went wrong!

); }
not-found.tsx

Renders when notFound() is called or route doesn't exist. Segment-specific 404.

// app/posts/[id]/not-found.tsx
export default function NotFound() {
  return 

Post not found

; }
route.ts

API route handler. Exports GET, POST, PUT, DELETE functions.

// app/api/posts/route.ts
export async function GET(request: Request) {
  return Response.json({ posts: [] });
}
📋

RSC & Server Actions

React Server Components render on server, streams to client. Server Actions for safe mutations with zero client code.

Server Components Rules

By default in app/, components run on server. Can access databases, secrets, large dependencies.

// app/posts/page.tsx - Server Component by default
import { db } from '@/lib/db';

export default async function Posts() {
  const posts = await db.posts.findMany();
  return ;
}
Client Components

Use "use client" directive for interactivity. State, hooks, browser APIs.

'use client';
import { useState } from 'react';

export default function Counter() {
  const [count, setCount] = useState(0);
  return ;
}
Composition Patterns

Interleave server and client. Server fetches, client handles interaction.

// Server Component
export default async function Page() {
  const posts = await db.posts.findMany();
  return (
    <>
      
      
    
  );
}
Server Actions

Async functions marked "use server". Called from Client Components securely.

'use server';
export async function createPost(data: FormData) {
  const post = await db.posts.create({
    title: data.get('title'),
  });
  revalidatePath('/posts');
  return post;
}

// In Client Component
'use client';
export default function NewPost() {
  return (
    
); }
📋

Routing Patterns

Dynamic segments, navigation, middleware, parallel and intercepting routes for advanced routing.

Dynamic Segments

[param] and [...slug] for dynamic routes.

// app/posts/[id]/page.tsx
export default function Post({ params }: { params: { id: string } }) {
  return 

Post {params.id}

; } // app/docs/[...slug]/page.tsx catches /docs/a/b/c export default function Docs({ params }: { params: { slug: string[] } }) { return

{params.slug.join('/')}

; }
Navigation Methods

<Link>, useRouter, redirect, notFound.

import Link from 'next/link';
import { useRouter } from 'next/navigation';

export default function Page() {
  const router = useRouter();
  return (
    <>
      Static Link
      
    
  );
}
Middleware Auth

middleware.ts for authentication and authorization checks.

// middleware.ts
import { NextRequest, NextResponse } from 'next/server';

export function middleware(request: NextRequest) {
  const token = request.cookies.get('auth')?.value;
  if (!token && request.nextUrl.pathname.startsWith('/dashboard')) {
    return NextResponse.redirect(new URL('/login', request.url));
  }
}

export const config = {
  matcher: ['/dashboard/:path*'],
};
Parallel Routes

@folder convention for independent route segments.

// app/dashboard/@sidebar/page.tsx
// app/dashboard/@main/page.tsx
// Both render in parallel in dashboard layout
Intercepting Routes

(..)folder pattern to intercept child routes without navigation.

// app/photos/[id]/page.tsx intercepted by
// app/photos/(.)_[id]/modal.tsx
📋

Data Fetching Strategies

SSR for dynamic content, SSG for static pages, ISR for hybrid approach. CSR for client-specific data. Streaming with Suspense. On-demand revalidation for cache control.

Server-Side Rendering (SSR)

Render page on each request. Dynamic, always fresh, good for personalization.

// app/posts/page.tsx
export const dynamic = 'force-dynamic';

export default async function Posts() {
  const posts = await fetch('https://api.example.com/posts', {
    cache: 'no-store', // Don't cache
  });
  return ;
}
Static Generation (SSG)

Pre-render at build time. Fastest, best for SEO, reusable across requests.

export const revalidate = 3600; // ISR: revalidate every hour

export default async function Post() {
  const post = await fetch('https://api.example.com/posts/1', {
    next: { revalidate: 3600 },
  });
  return ;
}
Incremental Static Regeneration (ISR)

Regenerate static pages on-demand or on schedule. Best of SSR and SSG.

// Revalidate every 60 seconds
export const revalidate = 60;

// Or revalidate on-demand
import { revalidatePath } from 'next/cache';

export async function POST() {
  revalidatePath('/posts');
  return Response.json({ revalidated: true });
}
Client-Side Rendering (CSR)

Fetch in browser with useQuery. For interactive, user-specific content.

'use client';
import { useQuery } from '@tanstack/react-query';

export default function Dashboard() {
  const { data: user } = useQuery({
    queryKey: ['user'],
    queryFn: () => fetch('/api/user').then(r => r.json()),
  });
  return 
{user?.name}
; }
Streaming

Stream HTML incrementally as Suspense boundaries resolve.

import { Suspense } from 'react';

export default function Page() {
  return (
    <>
      
}> ); }
On-Demand Revalidation

Manually trigger cache revalidation from Server Actions or API routes.

'use server';
import { revalidatePath, revalidateTag } from 'next/cache';

export async function publishPost(id: string) {
  await db.posts.update(id, { published: true });
  revalidatePath('/posts');
  revalidateTag('posts');
}
📋

Styling Approaches

Tailwind v4 as default. CSS Modules for scoped styles. vanilla-extract for type-safe CSS. CVA for component variants. Custom properties for theming.

Tailwind v4

Utility-first CSS framework. Composable, performant, built-in dark mode and container queries.

Name

Description

CSS Modules

Scoped CSS. Prevents naming collisions, explicit dependencies.

// Button.module.css
.button {
  padding: 0.5rem 1rem;
  border-radius: 0.25rem;
}
.primary {
  background: blue;
}

// Button.tsx
import styles from './Button.module.css';
export function Button() {
  return 
vanilla-extract

Type-safe CSS-in-JS. Zero-runtime, generates CSS at build time.

import { style } from '@vanilla-extract/css';

export const button = style({
  padding: '0.5rem 1rem',
  borderRadius: '0.25rem',
  ':hover': { opacity: 0.8 },
});
CVA (Class Variants Authority)

Type-safe component API with variant composition.

import { cva } from 'class-variance-authority';

const button = cva('px-4 py-2 rounded', {
  variants: {
    variant: {
      primary: 'bg-blue-600 text-white',
      secondary: 'bg-gray-200 text-gray-900',
    },
  },
});

Custom Properties

CSS variables for dynamic theming. Scoped, cascading, runtime-changeable.

/* tokens.css */
:root {
  --color-primary: #0066cc;
  --spacing-unit: 0.25rem;
}

[data-theme="dark"] {
  --color-primary: #0088ff;
}

/* Usage */
Dark Mode

Tailwind dark mode with class or system preference strategy.

// tailwind.config.ts
export default {
  darkMode: 'class',
  theme: {
    extend: {},
  },
};

// Usage
Content
📋

Design System

Design tokens, Storybook for component documentation, shadcn/ui for accessible components.

Design Tokens

Centralized, semantic color, spacing, typography values. Single source of truth.

// tokens.ts
export const colors = {
  primary: '#0066cc',
  success: '#10b981',
  error: '#ef4444',
  warning: '#f59e0b',
} as const;

export const spacing = {
  xs: '0.25rem', // 4px
  sm: '0.5rem',  // 8px
  md: '1rem',    // 16px
  lg: '1.5rem',  // 24px
} as const;
Storybook 8

Interactive component documentation and testing. Define stories as use cases.

// Button.stories.tsx
import { Button } from './Button';

export default {
  title: 'Button',
  component: Button,
};

export const Primary = {
  args: { variant: 'primary', children: 'Click me' },
};

export const Disabled = {
  args: { disabled: true, children: 'Disabled' },
};
shadcn/ui

Accessible, unstyled component library. Copy-paste components, customize freely.

import { Button } from '@/components/ui/button';
import { Dialog, DialogContent, DialogTrigger } from '@/components/ui/dialog';

export function MyDialog() {
  return (
    
      
        
      
      Content
    
  );
}
Typography Scale

Semantic size progression. H1-H6 and body text scales for hierarchy.

/* Typography scale */
--text-xs: 0.75rem;   /* 12px */
--text-sm: 0.875rem;  /* 14px */
--text-base: 1rem;    /* 16px */
--text-lg: 1.125rem;  /* 18px */
--text-xl: 1.25rem;   /* 20px */
--text-2xl: 1.5rem;   /* 24px */
Spacing (4px Grid)

4px base unit for consistency. Multiply for larger scales.

/* 4px grid */
--space-1: 0.25rem;  /* 4px */
--space-2: 0.5rem;   /* 8px */
--space-3: 0.75rem;  /* 12px */
--space-4: 1rem;     /* 16px */
--space-6: 1.5rem;   /* 24px */
--space-8: 2rem;     /* 32px */
Icon System

Lucide React for consistent, scalable SVG icons.

import { Search, ChevronDown, AlertCircle } from 'lucide-react';

export function Header() {
  return (
    
); }
📋

Responsive Design

Mobile-first approach with Tailwind breakpoints, container queries, fluid typography.

Tailwind Breakpoints

Mobile-first breakpoints. Use sm:, md:, lg: prefixes.

/* Default (mobile): 320px+
   sm: 640px
   md: 768px
   lg: 1024px
   xl: 1280px
   2xl: 1536px
*/

Responsive text size
Responsive grid
Container Queries

Query parent container size instead of viewport. Better component encapsulation.

@container (min-width: 400px) {
  .card {
    display: grid;
    grid-template-columns: 1fr 1fr;
  }
}

// In Tailwind
Component adapts to container, not viewport
Fluid Typography

Scales text smoothly between viewport sizes using clamp().

/* Font size scales from 18px at 320px to 32px at 1280px */
font-size: clamp(1.125rem, 2.5vw, 2rem);

/* Line height scales similarly */
line-height: clamp(1.5, 5vw, 1.8);

// Tailwind with clamp
className="text-[clamp(1.125rem,2.5vw,2rem)]"
Next/Image

Optimized images with automatic format, size, and lazy loading.

import Image from 'next/image';

Description
Aspect Ratio

Maintain ratio across responsive sizes. Prevent layout shift.

// Tailwind
// CSS .video-container { aspect-ratio: 16 / 9; overflow: hidden; }
📋

Animation & Motion

Framer Motion for complex animations, View Transitions API, CSS animations, reduced motion.

Framer Motion

Production animation library. Declarative API, springs, layouts, gestures.

import { motion } from 'framer-motion';


  Content fades in and slides up



  Interactive button
View Transitions API

Smooth transitions between DOM updates without JS animation code.

'use client';
import { useRouter } from 'next/navigation';
import { startTransition } from 'react';

const router = useRouter();

const handleClick = () => {
  startTransition(() => {
    document.startViewTransition(() => {
      router.push('/new-page');
    });
  });
};
CSS Animations

Native CSS for simple, performant animations. Use when no library needed.

@keyframes fadeIn {
  from { opacity: 0; }
  to { opacity: 1; }
}

.fade-in {
  animation: fadeIn 0.3s ease-out forwards;
}

/* With Tailwind */
@layer utilities {
  @keyframes fadeIn { /* ... */ }
  .animate-fade-in { animation: fadeIn 0.3s ease-out; }
}
prefers-reduced-motion

Respect user preference for reduced motion. Disable animations for accessibility.

@media (prefers-reduced-motion: reduce) {
  * {
    animation-duration: 0.01ms !important;
    animation-iteration-count: 1 !important;
    transition-duration: 0.01ms !important;
  }
}

// In Framer Motion
📋

Forms & Validation

4-layer validation: browser, client, server, database. React Hook Form + Zod.

React Hook Form + Zod

Type-safe form handling with client-side validation schema.

import { useForm } from 'react-hook-form';
import { zodResolver } from '@hookform/resolvers/zod';
import { z } from 'zod';

const schema = z.object({
  email: z.string().email('Invalid email'),
  password: z.string().min(8, 'Min 8 characters'),
});

function LoginForm() {
  const { register, handleSubmit, formState: { errors } } = useForm({
    resolver: zodResolver(schema),
  });

  return (
    
{errors.email && {errors.email.message}}
); }
Server Actions Forms

Form submission to Server Actions. Server validation, revalidation.

'use server';
import { redirect } from 'next/navigation';

export async function createUser(formData: FormData) {
  const email = formData.get('email');
  const schema = z.object({ email: z.string().email() });

  try {
    const validated = schema.parse({ email });
    await db.users.create(validated);
    redirect('/users');
  } catch (error) {
    return { error: 'Invalid email' };
  }
}

// In Client Component
'use client';
import { createUser } from './actions';

export function NewUserForm() {
  const [error, setError] = useState('');

  async function handleSubmit(formData: FormData) {
    const result = await createUser(formData);
    if (result?.error) setError(result.error);
  }

  return (
    
{error &&

{error}

}
); }
4-Layer Validation

Browser (HTML), client (JS), server (Action), database (constraints).

/* Layer 1: HTML */


/* Layer 2: Client Zod */
const schema = z.object({
  email: z.string().email().max(100),
});

/* Layer 3: Server Action */
export async function createUser(formData: FormData) {
  const validated = schema.parse(Object.fromEntries(formData));
  // Server action layer
}

/* Layer 4: Database */
CREATE TABLE users (
  email VARCHAR(100) NOT NULL UNIQUE,
  CHECK (email ~ '^[^@]+@[^@]+$')
);
📋

Testing Strategies

Testing Trophy: 50% integration, 20% unit, 20% E2E, 8% visual, 2% perf. Vitest, Playwright, MSW.

Testing Trophy Distribution

Invest most in integration tests. Balance unit and E2E. Small visual and perf budgets.

50% Integration: Test components with dependencies
20% Unit: Test pure functions and custom hooks
20% E2E: Critical user journeys
8% Visual: Component appearance across browsers
2% Performance: Core Web Vitals targets
Vitest

Fast unit and integration testing. Vite-native, great DX, React testing library integration.

import { describe, it, expect } from 'vitest';
import { render, screen } from '@testing-library/react';
import { Button } from './Button';

describe('Button', () => {
  it('renders with text', () => {
    render();
    expect(screen.getByRole('button')).toHaveTextContent('Click me');
  });

  it('handles click', async () => {
    const onClick = vi.fn();
    render();
    await userEvent.click(screen.getByRole('button'));
    expect(onClick).toHaveBeenCalled();
  });
});
Playwright

E2E testing for critical user flows. Cross-browser testing, visual regression.

import { test, expect } from '@playwright/test';

test('user can login', async ({ page }) => {
  await page.goto('/login');
  await page.fill('[name="email"]', 'user@example.com');
  await page.fill('[name="password"]', 'password123');
  await page.click('[type="submit"]');

  await expect(page).toHaveURL('/dashboard');
  await expect(page.getByRole('heading', { name: 'Dashboard' })).toBeVisible();
});
MSW (Mock Service Worker)

Intercept network requests in tests. Consistent mocking across unit and E2E tests.

import { http, HttpResponse } from 'msw';
import { setupServer } from 'msw/node';

const server = setupServer(
  http.get('/api/users', () => {
    return HttpResponse.json([{ id: 1, name: 'Alice' }]);
  })
);

beforeAll(() => server.listen());
afterEach(() => server.resetHandlers());
afterAll(() => server.close());
📋

Accessibility (A11y)

WCAG 2.2 AA compliance. Keyboard navigation, color contrast, ARIA, screen reader testing.

WCAG 2.2 AA

Industry standard. Minimum contrast 4.5:1 (text), all functionality keyboard accessible.

/* Contrast Checker Target */
Foreground: #000 (black)
Background: #fff (white)
Ratio: 21:1 ✓ (AAA)

Foreground: #0066cc (blue)
Background: #fff (white)
Ratio: 8.6:1 ✓ (AAA)
Keyboard Navigation

All interactive elements accessible via Tab key. Visible focus indicators.

/* Focus indicator */
:focus-visible {
  outline: 2px solid #0066cc;
  outline-offset: 2px;
}

/* Tab order */
Third (Tab 3)
/* Skip links for keyboard users */ Skip to main content
ARIA Attributes

Semantic HTML first, ARIA when needed. aria-label, aria-live, roles.


Confirm Action

Are you sure?

{message}
Screen Reader Testing

Test with NVDA, JAWS, VoiceOver. Verify heading structure, alt text, labels.


Header

Article Title

Descriptive alt text
Footer
Focus Management

Trap focus in modals, restore focus on close, announce dynamic changes.

'use client';
import { useEffect, useRef } from 'react';

export function Modal({ isOpen, onClose, children }) {
  const modalRef = useRef(null);

  useEffect(() => {
    if (isOpen) {
      const firstButton = modalRef.current?.querySelector('button');
      firstButton?.focus();
    }
  }, [isOpen]);

  return isOpen ? (
    
{children}
) : null; }
📋

Frontend Security

XSS prevention, CSRF protection, clickjacking defense, CSP, env validation, HttpOnly cookies.

XSS Prevention

Never use dangerouslySetInnerHTML. Sanitize user input with DOMPurify.

import DOMPurify from 'dompurify';

// Bad - XSS vector
// Good - sanitize
{DOMPurify.sanitize(userInput)}
// Or better - don't use HTML
{userInput}
CSRF Protection

Next.js handles CSRF tokens in Server Actions automatically.

// Server Action automatically protected
'use server';
export async function updateProfile(formData: FormData) {
  // CSRF token validated by Next.js
  await db.users.update(userId, formData);
}
Clickjacking Defense

Set X-Frame-Options header. Prevents embedding in iframes.

// next.config.ts
export default {
  headers: async () => [
    {
      source: '/:path*',
      headers: [
        { key: 'X-Frame-Options', value: 'DENY' },
        { key: 'X-Content-Type-Options', value: 'nosniff' },
      ],
    },
  ],
};
Content Security Policy

CSP header restricts resource loading. Prevents inline scripts.

// next.config.ts
headers: [
  {
    source: '/:path*',
    headers: [
      {
        key: 'Content-Security-Policy',
        value: "default-src 'self'; script-src 'self' 'nonce-{random}'; style-src 'self'",
      },
    ],
  },
],
Environment Validation

Validate env vars at startup. Use next.js .env.local for secrets.

import { z } from 'zod';

const envSchema = z.object({
  NEXT_PUBLIC_API_URL: z.string().url(),
  DATABASE_URL: z.string().url(),
  API_SECRET: z.string().min(1),
});

const env = envSchema.parse(process.env);
HttpOnly Cookies

Auth tokens in HttpOnly cookies, inaccessible to JS. Prevents token theft.

// Server Action setting auth
'use server';
import { cookies } from 'next/headers';

export async function login(email: string, password: string) {
  const token = await authenticate(email, password);

  (await cookies()).set('auth', token, {
    httpOnly: true,
    secure: true,
    sameSite: 'strict',
    maxAge: 7 * 24 * 60 * 60, // 7 days
  });
}
📋

Core Web Vitals

FCP <1.5s, LCP <2.5s, INP <200ms, CLS <0.1, TTFB <600ms. FBT-specific targets.

Largest Contentful Paint (LCP)

Time when largest content element renders. Target: <2.5s.

// Strategies for LCP
1. Prioritize LCP element (image/heading)
2. Optimize image: use Next/Image, modern formats
3. Defer non-critical CSS/fonts
4. Use CDN for assets
5. Preload critical resources


Interaction to Next Paint (INP)

Responsiveness to user input. Target: <200ms.

// Reduce long tasks with useTransition
'use client';
import { useTransition } from 'react';

export function SearchResults() {
  const [isPending, startTransition] = useTransition();

  const handleSearch = (query: string) => {
    startTransition(async () => {
      await searchResults(query);
    });
  };
}
Cumulative Layout Shift (CLS)

Visual stability. Target: <0.1. Reserve space for lazy-loaded content.


}>
First Contentful Paint (FCP)

First pixel painted. Target: <1.5s.

// Improve FCP
1. Inline critical CSS
2. Avoid parser-blocking resources
3. Optimize fonts (system fonts preferred)
4. Minimize JS in 

/* Critical styles inline */