React / Next.js - Entorno de Desarrollo¶
Guía completa para configurar un entorno de desarrollo React que permita a Claude Code trabajar eficazmente.
Requisitos del Sistema¶
| Componente | Versión Mínima | Recomendada |
|---|---|---|
| Node.js | 18.x | 22.x LTS |
| React | 18.x | 19.x |
| Next.js | 14.x | 15.x |
| TypeScript | 5.0 | 5.4+ |
Crear Proyectos¶
Next.js (Recomendado para producción)¶
# Crear proyecto Next.js
pnpm create next-app@latest my-app
# Opciones recomendadas:
# ✓ TypeScript: Yes
# ✓ ESLint: Yes
# ✓ Tailwind CSS: Yes
# ✓ src/ directory: Yes
# ✓ App Router: Yes
# ✓ Import alias: @/*
cd my-app
pnpm dev
Vite + React (SPAs)¶
# Crear proyecto con Vite
pnpm create vite@latest my-app -- --template react-ts
cd my-app
pnpm install
pnpm dev
Create React App (Legacy)¶
Estructura de Proyecto¶
Next.js 15 (App Router)¶
my-app/
├── src/
│ ├── app/ # App Router
│ │ ├── layout.tsx # Root layout
│ │ ├── page.tsx # Home page
│ │ ├── globals.css
│ │ ├── (auth)/ # Route group
│ │ │ ├── login/
│ │ │ │ └── page.tsx
│ │ │ └── register/
│ │ │ └── page.tsx
│ │ ├── dashboard/
│ │ │ ├── layout.tsx # Nested layout
│ │ │ ├── page.tsx
│ │ │ └── settings/
│ │ │ └── page.tsx
│ │ └── api/ # API Routes
│ │ └── users/
│ │ └── route.ts
│ ├── components/
│ │ ├── ui/ # Reusable UI components
│ │ │ ├── Button.tsx
│ │ │ ├── Input.tsx
│ │ │ └── index.ts
│ │ └── features/ # Feature-specific components
│ │ └── auth/
│ │ └── LoginForm.tsx
│ ├── lib/ # Utilities
│ │ ├── utils.ts
│ │ └── api.ts
│ ├── hooks/ # Custom hooks
│ │ └── useAuth.ts
│ ├── types/ # TypeScript types
│ │ └── index.ts
│ └── styles/
│ └── tailwind.css
├── public/
├── tests/
│ ├── components/
│ └── e2e/
├── next.config.ts
├── tailwind.config.ts
├── tsconfig.json
├── package.json
└── .env.local
Vite + React¶
my-app/
├── src/
│ ├── main.tsx # Entry point
│ ├── App.tsx
│ ├── components/
│ ├── hooks/
│ ├── lib/
│ ├── pages/ # Si usas React Router
│ ├── types/
│ └── index.css
├── public/
├── tests/
├── vite.config.ts
├── tsconfig.json
└── package.json
Dependencias Esenciales¶
UI Components¶
# shadcn/ui (Recomendado para Next.js)
pnpm dlx shadcn@latest init
pnpm dlx shadcn@latest add button input card dialog
# Radix UI (Base de shadcn)
pnpm add @radix-ui/react-dialog @radix-ui/react-dropdown-menu
# Headless UI
pnpm add @headlessui/react
# Material UI
pnpm add @mui/material @emotion/react @emotion/styled
# Chakra UI
pnpm add @chakra-ui/react @emotion/react @emotion/styled
State Management¶
# Zustand (Recomendado - simple)
pnpm add zustand
# Jotai (Atómico)
pnpm add jotai
# TanStack Query (Server state)
pnpm add @tanstack/react-query
# Redux Toolkit (Complejo)
pnpm add @reduxjs/toolkit react-redux
Forms¶
# React Hook Form (Recomendado)
pnpm add react-hook-form
# Con Zod para validación
pnpm add zod @hookform/resolvers
Routing (para Vite/SPA)¶
# React Router
pnpm add react-router-dom
# TanStack Router (type-safe)
pnpm add @tanstack/react-router
Animaciones¶
Data Fetching¶
# TanStack Query (Recomendado)
pnpm add @tanstack/react-query @tanstack/react-query-devtools
# SWR
pnpm add swr
# Axios
pnpm add axios
Configuración de TypeScript¶
tsconfig.json¶
{
"compilerOptions": {
"target": "ES2022",
"lib": ["dom", "dom.iterable", "ES2022"],
"allowJs": true,
"skipLibCheck": true,
"strict": true,
"noEmit": true,
"esModuleInterop": true,
"module": "esnext",
"moduleResolution": "bundler",
"resolveJsonModule": true,
"isolatedModules": true,
"jsx": "preserve",
"incremental": true,
"plugins": [
{ "name": "next" }
],
"paths": {
"@/*": ["./src/*"]
}
},
"include": ["next-env.d.ts", "**/*.ts", "**/*.tsx", ".next/types/**/*.ts"],
"exclude": ["node_modules"]
}
Testing¶
Vitest + Testing Library¶
# Instalar
pnpm add -D vitest @testing-library/react @testing-library/jest-dom @testing-library/user-event jsdom @vitejs/plugin-react
vitest.config.ts:
import { defineConfig } from 'vitest/config'
import react from '@vitejs/plugin-react'
export default defineConfig({
plugins: [react()],
test: {
environment: 'jsdom',
globals: true,
setupFiles: './src/test/setup.ts',
include: ['**/*.{test,spec}.{js,mjs,cjs,ts,mts,cts,jsx,tsx}'],
},
})
src/test/setup.ts:
Ejemplo de test:
import { render, screen } from '@testing-library/react'
import userEvent from '@testing-library/user-event'
import { describe, it, expect } from 'vitest'
import { Button } from '@/components/ui/Button'
describe('Button', () => {
it('renders correctly', () => {
render(<Button>Click me</Button>)
expect(screen.getByRole('button', { name: /click me/i })).toBeInTheDocument()
})
it('handles click', async () => {
const user = userEvent.setup()
const handleClick = vi.fn()
render(<Button onClick={handleClick}>Click me</Button>)
await user.click(screen.getByRole('button'))
expect(handleClick).toHaveBeenCalledTimes(1)
})
})
Playwright (E2E)¶
# Instalar
pnpm create playwright
# playwright.config.ts
import { defineConfig, devices } from '@playwright/test'
export default defineConfig({
testDir: './tests/e2e',
fullyParallel: true,
webServer: {
command: 'pnpm dev',
url: 'http://localhost:3000',
reuseExistingServer: !process.env.CI,
},
use: {
baseURL: 'http://localhost:3000',
},
projects: [
{ name: 'chromium', use: { ...devices['Desktop Chrome'] } },
{ name: 'firefox', use: { ...devices['Desktop Firefox'] } },
{ name: 'webkit', use: { ...devices['Desktop Safari'] } },
],
})
# Ejecutar
pnpm playwright test
pnpm playwright test --ui
Storybook¶
# Instalar
pnpm dlx storybook@latest init
# Ejecutar
pnpm storybook
# Ejemplo de story
// Button.stories.tsx
import type { Meta, StoryObj } from '@storybook/react'
import { Button } from './Button'
const meta: Meta<typeof Button> = {
title: 'UI/Button',
component: Button,
tags: ['autodocs'],
}
export default meta
type Story = StoryObj<typeof Button>
export const Primary: Story = {
args: {
children: 'Button',
variant: 'primary',
},
}
export const Secondary: Story = {
args: {
children: 'Button',
variant: 'secondary',
},
}
IDE y Extensiones¶
Visual Studio Code¶
# Extensiones obligatorias
code --install-extension dbaeumer.vscode-eslint
code --install-extension esbenp.prettier-vscode
code --install-extension bradlc.vscode-tailwindcss
code --install-extension ms-vscode.vscode-typescript-next
# Recomendadas para React
code --install-extension dsznajder.es7-react-js-snippets
code --install-extension styled-components.vscode-styled-components
code --install-extension vitest.explorer
code --install-extension ms-playwright.playwright
# Utilidades
code --install-extension christian-kohler.path-intellisense
code --install-extension usernamehw.errorlens
code --install-extension formulahendry.auto-rename-tag
settings.json:
{
"editor.defaultFormatter": "esbenp.prettier-vscode",
"editor.formatOnSave": true,
"editor.codeActionsOnSave": {
"source.fixAll.eslint": "explicit",
"source.organizeImports": "explicit"
},
"typescript.preferences.importModuleSpecifier": "non-relative",
"typescript.updateImportsOnFileMove.enabled": "always",
"emmet.includeLanguages": {
"typescriptreact": "html"
},
"tailwindCSS.includeLanguages": {
"typescriptreact": "html"
}
}
React 19 Features¶
Server Components (Next.js)¶
// app/users/page.tsx - Server Component by default
async function UsersPage() {
const users = await fetch('https://api.example.com/users').then(r => r.json())
return (
<ul>
{users.map((user: User) => (
<li key={user.id}>{user.name}</li>
))}
</ul>
)
}
export default UsersPage
Server Actions¶
// app/actions.ts
'use server'
import { revalidatePath } from 'next/cache'
export async function createUser(formData: FormData) {
const name = formData.get('name') as string
await db.user.create({ data: { name } })
revalidatePath('/users')
}
// app/users/new/page.tsx
import { createUser } from '../actions'
export default function NewUserPage() {
return (
<form action={createUser}>
<input name="name" required />
<button type="submit">Create User</button>
</form>
)
}
use() Hook¶
import { use } from 'react'
function UserProfile({ userPromise }: { userPromise: Promise<User> }) {
const user = use(userPromise) // Suspends until resolved
return <div>{user.name}</div>
}
useActionState (Forms)¶
'use client'
import { useActionState } from 'react'
import { createUser } from './actions'
function CreateUserForm() {
const [state, formAction, isPending] = useActionState(createUser, null)
return (
<form action={formAction}>
<input name="name" required />
<button type="submit" disabled={isPending}>
{isPending ? 'Creating...' : 'Create'}
</button>
{state?.error && <p className="error">{state.error}</p>}
</form>
)
}
Comandos que Claude Code Ejecutará¶
# Desarrollo
pnpm dev
pnpm next dev
pnpm vite
# Build
pnpm build
pnpm next build
# Preview
pnpm preview
pnpm next start
# Testing
pnpm test
pnpm vitest
pnpm vitest run
pnpm playwright test
# Linting
pnpm lint
pnpm next lint
pnpm eslint src/ --fix
# Type checking
pnpm tsc --noEmit
# Storybook
pnpm storybook
pnpm build-storybook
# Shadcn components
pnpm dlx shadcn@latest add <component>
Docker para React¶
Dockerfile (Next.js)¶
FROM node:22-alpine AS base
RUN corepack enable && corepack prepare pnpm@latest --activate
FROM base AS deps
WORKDIR /app
COPY package.json pnpm-lock.yaml ./
RUN pnpm install --frozen-lockfile
FROM base AS builder
WORKDIR /app
COPY --from=deps /app/node_modules ./node_modules
COPY . .
RUN pnpm build
FROM base AS runner
WORKDIR /app
ENV NODE_ENV=production
RUN addgroup --system --gid 1001 nodejs
RUN adduser --system --uid 1001 nextjs
COPY --from=builder /app/public ./public
COPY --from=builder --chown=nextjs:nodejs /app/.next/standalone ./
COPY --from=builder --chown=nextjs:nodejs /app/.next/static ./.next/static
USER nextjs
EXPOSE 3000
ENV PORT=3000
CMD ["node", "server.js"]
next.config.ts para Docker:
import type { NextConfig } from 'next'
const config: NextConfig = {
output: 'standalone',
}
export default config
Troubleshooting¶
Hydration mismatch¶
// Usar useEffect para contenido dinámico del cliente
'use client'
import { useState, useEffect } from 'react'
function ClientOnlyComponent() {
const [mounted, setMounted] = useState(false)
useEffect(() => {
setMounted(true)
}, [])
if (!mounted) return null
return <div>{/* contenido del cliente */}</div>
}