Saltar a contenido

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)

# No recomendado para nuevos proyectos
# Usar Vite o Next.js en su lugar

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

# Framer Motion
pnpm add framer-motion

# React Spring
pnpm add @react-spring/web

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:

import '@testing-library/jest-dom'

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>
}

Module not found

# Limpiar cache
rm -rf .next node_modules/.cache
pnpm install

TypeScript errors en Next.js

# Regenerar tipos
pnpm next build
# Los tipos se generan en .next/types

Recursos