Modern Frontend
Ecosystem Field Guide
A reference for engineers returning to the JS ecosystem — covering build tools, frameworks, state management, TypeScript, and toolchain conventions as of 2025.
updated 2025
stack React / Vite / TypeScript
target re-entry reference
Build Tools & Bundlers
vite Default Pick
The modern dev server + bundler for web apps
ToolRoleWhen to use
Vite app bundler New React/Vue/Svelte apps. Best DX, fast HMR.
Webpack app bundler Legacy projects only. Module Federation if needed.
esbuild low-level Library/CLI bundling. Used inside Vite/Bun.
Rollup lib bundler Publishing npm packages. Better tree-shaking than esbuild.
Bun runtime + tools Replace Node + npm + bundler with one binary.
Vite's trick: Uses esbuild for dev (fast) and Rollup for prod (optimized). You get the best of both without configuration.
What changed since ~2019
pnpm is now widely preferred over npm/yarn for monorepos — uses content-addressable storage, saves disk space, and is significantly faster.
Quick Setup
# Start a new project
npm create vite@latest my-app -- --template react-ts
cd my-app && npm install && npm run dev

# Or with Bun (faster)
bun create vite my-app --template react-ts
bun dev
vite.config.ts anatomy
import { defineConfig } from 'vite'
import react from '@vitejs/plugin-react'

export default defineConfig({
  plugins: [react()],
  server: { port: 3000, proxy: {
    '/api': 'http://localhost:8080'
  }},
  build: { outDir: 'dist', sourcemap: true }
})
Package Manager Cheat
# npm equivalents with pnpm
npm install       → pnpm install
npm install pkg   → pnpm add pkg
npm run dev       → pnpm dev
npx vite          → pnpm dlx vite

# Workspace (monorepo) filter
pnpm --filter my-app add lodash
Frameworks & Meta-Frameworks
React 19 Stable
Still dominant, but significantly evolved
Class components are legacy. Everything is hooks. React 18 concurrent features (Suspense, transitions) are now standard.
Next.js 15 Go-to for React
Meta-framework with SSR, SSG, RSC built in
App Router paradigm shift: Components are server by default. Add 'use client' at the top only when you need interactivity or browser APIs.
Vue 3 / Nuxt 3 Stable
If you want less ceremony than React
Good fit if building internal dashboards or tooling UIs — less opinionated, gentler learning curve than React 19's RSC model.
Svelte 5 / SvelteKit Runes
Compiler-based — no virtual DOM
Worth knowing about but not the go-to unless you specifically want tiny bundles or enjoy the compiler-first approach.
Next.js App Router File Conventions
app/
├── layout.tsx        # Root shell, persists across routes
├── page.tsx          # Route component → /
├── loading.tsx       # Suspense fallback
├── error.tsx         # Error boundary ('use client')
├── not-found.tsx     # 404
├── route.ts          # API route (GET, POST handlers)
└── dashboard/
    ├── layout.tsx    # Nested layout
    └── [id]/
        └── page.tsx  # Dynamic route → /dashboard/:id
Server vs Client Components
// Server Component (default) — runs on server
// Can: async/await, DB access, env vars (secure)
// Cannot: useState, useEffect, event handlers
async function Page() {
  const data = await fetchFromDB()
  return <div>{data}</div>
}

// Client Component — runs in browser
// Can: useState, useEffect, event handlers
'use client'
import { useState } from 'react'
function Counter() {
  const [n, setN] = useState(0)
  return <button onClick={() => setN(n+1)}>{n}</button>
}
State Management
Server State — the big shift
Most "state" in apps is really async server data. Don't put it in Redux.
TanStack Query
Fetching, caching, invalidation, background refresh. The go-to.
Default
SWR
Simpler alternative by Vercel. Good for Next.js projects.
Good
RTK Query
Redux Toolkit's server state layer. Only if you already use RTK.
Ok
const { data, isPending, error } = useQuery({
  queryKey: ['todos', userId],
  queryFn: () => fetch(`/api/todos/${userId}`).then(r => r.json())
})
Client State — much lighter now
Redux was over-used. Most client state can be local or URL-based.
useState / useReducer
Component-local state. Sufficient for 80% of cases.
Start here
Zustand
Minimal global store. No boilerplate. Replaces Redux for most apps.
Preferred
Jotai / Recoil
Atomic state. Fine-grained reactivity. Jotai > Recoil now.
Atomic
Redux Toolkit
Redux done right. Only reach for it on large team projects.
Large apps
URL & Form State
Often overlooked — serialize state into URL params
nuqs
Type-safe URL search params as React state. Incredible DX.
New
React Hook Form
Forms with minimal re-renders. Works great with Zod validation.
Default
Zod
TypeScript-first schema validation. Use everywhere: forms, API responses.
Essential
Pattern: Zod schema → infer TypeScript type → feed into React Hook Form resolver. Type-safe forms with zero manual typing.
TypeScript — Now Mandatory
Essential Patterns
// 1. Infer types from Zod schemas
import { z } from 'zod'
const UserSchema = z.object({
  id: z.string(),
  name: z.string().min(1),
  role: z.enum(['admin', 'user'])
})
type User = z.infer<typeof UserSchema>

// 2. Discriminated unions for component variants
type Status =
  | { state: 'loading' }
  | { state: 'error'; message: string }
  | { state: 'success'; data: User[] }

// 3. Satisfies — validate without widening type
const config = {
  theme: 'dark', port: 3000
} satisfies Config // still infers exact type

// 4. Template literal types
type EventName = `on${Capitalize<string>}`
tsconfig.json — modern defaults
{
  "compilerOptions": {
    "target": "ES2022",
    "lib": ["dom", "dom.iterable", "ES2022"],
    "module": "ESNext",
    "moduleResolution": "bundler", // NEW
    "strict": true,              // always
    "noUncheckedIndexedAccess": true, // catches arr[i]
    "exactOptionalPropertyTypes": true,
    "verbatimModuleSyntax": true,  // NEW in 5.x
    "paths": { "@/*": ["./src/*"] }
  }
}
moduleResolution: "bundler" is new in TS 5 — handles the way Vite/esbuild resolve imports. Use this instead of "node16".
Styling Approaches
Tailwind CSS Dominant
Utility-first. Write styles inline in JSX.
// clsx + tailwind-merge pattern
import { clsx } from 'clsx'
import { twMerge } from 'tailwind-merge'
const cn = (...inputs) => twMerge(clsx(inputs))
CSS Modules Safe
Scoped CSS. Co-located with components.
/* Button.module.css */
.btn { padding: 8px 16px; }
.primary { background: blue; }

// Button.tsx
import s from './Button.module.css'
const Button = () => (
  <button className={`${s.btn} ${s.primary}`}>
    Click
  </button>
)
Good when Tailwind is too opinionated or you need complex animations.
CSS-in-JS Declining
Styled-components / Emotion
RSC incompatibility is the killer — styled-components requires React context which only works in client components.
Component Libraries
shadcn/ui
Copy-paste components into your repo. Not a dep. Tailwind-based.
Radix UI
Unstyled accessible primitives. Base for shadcn.
MUI / Ant Design
Full component suites. Heavier but comprehensive.
Headless UI
Tailwind Labs' unstyled components. Good with Tailwind.
Testing
Vitest Replaces Jest
Jest-compatible API, Vite-native, massively faster
// vitest.config.ts
import { defineConfig } from 'vitest/config'
export default defineConfig({
  test: { environment: 'jsdom', globals: true,
          setupFiles: ['./src/test-setup.ts'] }
})

// test file — same API as Jest
import { describe, it, expect, vi } from 'vitest'
describe('add', () => {
  it('sums numbers', () => {
    expect(add(1, 2)).toBe(3)
  })
})
Testing Library Standard
Test components as users interact with them
import { render, screen, userEvent }
  from '@testing-library/react'

it('increments counter', async () => {
  render(<Counter />)
  await userEvent.click(screen.getByRole('button'))
  expect(screen.getByText('1')).toBeInTheDocument()
})

// Prefer: getByRole, getByLabelText, getByText
// Avoid:  getByTestId (couples tests to impl)
Playwright E2E Standard
Replaces Cypress for most new projects
import { test, expect } from '@playwright/test'

test('login flow', async ({ page }) => {
  await page.goto('/login')
  await page.fill('[name=email]', 'user@test.com')
  await page.click('button[type=submit]')
  await expect(page).toHaveURL('/dashboard')
})
Playwright vs Cypress: Playwright supports multiple browsers, is faster, and has better async handling. It's the default choice for new projects.
Ecosystem Quick Reference
Routing
TanStack Router
Type-safe client-side routing. File-based or code-based. Replaces React Router for SPAs.
New Default
React Router v7
Now a full-stack framework (merged with Remix). Good if you need Remix-style loaders.
Stable
Next.js App Router
File-system routing built into Next.js. Not standalone.
Next only
Data Fetching Patterns
REST (axios/fetch)
vs
tRPC (type-safe RPC)
Client fetch on mount
vs
Server Component fetch
GraphQL (Apollo/URQL)
vs
tRPC (if full-stack TS)
tRPC is compelling: define procedures on server, call them from client with full type inference, no codegen. Works great with Next.js.
Animation
Motion (Framer)
The standard React animation library. Declarative, layout animations, gestures.
Go-to
React Spring
Physics-based animations. More control, more complexity.
Alternative
CSS Transitions
View Transitions API (native browser) now good enough for page transitions.
Native
Dev Tooling
ESLint + Prettier
Linting + formatting. Now using flat config (eslint.config.js).
Standard
Biome
ESLint + Prettier replacement in one Rust tool. 10-100x faster.
Emerging
Storybook
Component workshop. Still the standard for design systems.
Mature
Deployment
Vercel
First-class Next.js hosting. Fastest DX for full-stack React.
Easiest
Cloudflare Pages
Edge-native. Free tier. Great for static + Workers.
Edge
Netlify / Railway
Good alternatives. Railway is great for full-stack with DB.
Alternatives
2025 Trends to Know
React Server Components

The biggest paradigm shift in React since hooks. Components run on the server, stream HTML to the client, and only the interactive parts ship JS. Fundamentally changes how you think about data fetching and component boundaries.

Impact: Much less client-side JS, faster TTI, simpler data fetching — but new mental model to learn.
Islands Architecture

Astro popularized this: render everything as static HTML, then hydrate only interactive "islands" independently. RSC achieves a similar result within React. Key insight: most content doesn't need JS.

Astro RSC Qwik 11ty
Edge Runtime

Run server code at CDN edge nodes globally instead of a single region. Cloudflare Workers, Vercel Edge Functions, Deno Deploy. Subset of Node.js APIs available — web standards only (fetch, Request, Response, etc.).

Gotcha: Many npm packages don't work at the edge (use fs, crypto, etc.). Check compatibility carefully.
AI-First Patterns

Streaming LLM responses via ReadableStream, Vercel AI SDK for React hooks around streaming, generative UI (server streams React components to client). Entire new category of UI patterns emerging.

Vercel AI SDK ReadableStream useChat
Recommended Stack — Re-Entry Starting Point
The T3 Stack (+ variations) — sensible defaults for 2025
Framework
Next.js 15
App Router, RSC, full-stack in one repo
Language
TypeScript 5
strict mode, satisfies, verbatimModuleSyntax
Styling
Tailwind v4
+ shadcn/ui for components
Validation
Zod
shared between client + server
Server State
TanStack Query
or Next.js fetch + RSC
Client State
Zustand
minimal, no boilerplate
Forms
React Hook Form
+ Zod resolver
Testing
Vitest + Playwright
unit + E2E coverage
Package Mgr
pnpm
faster, disk-efficient
ORM
Drizzle
or Prisma — both TypeScript-first
Auth
Auth.js (NextAuth v5)
or Clerk for managed auth
Deploy
Vercel
or Cloudflare Pages
create.t3.gg scaffolds Next.js + TypeScript + Tailwind + tRPC + Prisma + NextAuth in one command  |  pnpm create t3-app@latest