Saltar al contenido principal

📘 Manual de Prácticas para el Desarrollo Frontend con NextJS

Estas son las convenciones que vamos a utilizar para nuestro código NextJS.

1. 🧱 Arquitectura del Proyecto

🎯 Objetivo

Organizar el proyecto para escalar de forma sostenible, mantener claridad en los contextos funcionales y facilitar el trabajo colaborativo. Una arquitectura limpia y predecible reduce el tiempo de onboarding y la deuda técnica.

📁 Estructura recomendada

bash
CopiarEditar
app/ # Enrutamiento por carpetas (Next.js 13+ con App Router)

├── layout.tsx # Layout raíz (persistente)
├── page.tsx # Página principal
├── dashboard/ # Subruta (ej. /dashboard)
│ ├── layout.tsx
│ └── page.tsx

components/ # Componentes reutilizables
hooks/ # Custom Hooks (useForm, useAuth, etc.)
lib/ # Lógica de negocio (formateo, validación, helpers)
services/ # Servicios para consumir APIs
types/ # Tipos y contratos globales
styles/ # Archivos CSS, Tailwind, variables
middleware.ts # Middleware para rutas protegidas
.env.local # Variables de entorno
next.config.js # Configuración global de Next.js
tsconfig.json # Configuración de TypeScript

Esta estructura separa responsabilidades, mejora la escalabilidad, y facilita la implementación de prácticas como rutas protegidas, layouts anidados y separación de dominios.


2. 📦 Organización de Componentes

🎯 Objetivo

Diseñar componentes que sean reutilizables, desacoplados y centrados en una única responsabilidad.

🧼 Buenas prácticas

  • Evitar componentes monolíticos con múltiples responsabilidades.
  • Dividir en presentacionales y contenedores cuando aplique.
  • Usar props tipadas con interfaces.
  • Evitar side-effects dentro del renderizado.

Ejemplo avanzado

tsx
CopiarEditar
// components/UserCard.tsx
type UserCardProps = {
name: string;
email: string;
};

export function UserCard({ name, email }: UserCardProps) {
return (
<div className="rounded border p-4 shadow-sm">
<h2 className="text-lg font-bold">{name}</h2>
<p className="text-sm text-gray-600">{email}</p>
</div>
);
}

La separación clara de responsabilidad y el uso explícito de tipos mejora la reutilización y el testeo de los componentes.


3. 💅 Estilos y Diseño (Tailwind / CSS Modules / Styled)

🎯 Objetivo

Mantener un estilo coherente, desacoplado del contenido y fácil de mantener a medida que el proyecto crece.

Opciones comunes:

  • Tailwind CSS: utilidades atómicas con clases.
  • CSS Modules: scoped CSS por componente.
  • Styled Components: estilos en JS (para proyectos React clásicos).

Buenas prácticas con Tailwind

  • Usar clases utilitarias en vez de crear componentes anidados innecesariamente.
  • Definir colores y fuentes en tailwind.config.js para centralización.
  • Usar @layer components para componer estilos repetitivos.

Ejemplo con Tailwind

tsx
CopiarEditar
<button className="bg-blue-600 hover:bg-blue-700 text-white font-semibold py-2 px-4 rounded">
Guardar
</button>

Tailwind permite escalar el diseño sin sobrescribir reglas y mejora la legibilidad en UIs altamente personalizadas.


4. 🔄 Gestión de Estado

🎯 Objetivo

Manejar correctamente el estado local, compartido y remoto sin sobrecargar la app con lógica innecesaria o mal distribuida.

Alternativas modernas

  • Zustand: para estados simples y globales.
  • React Context: útil para temas, auth, settings.
  • React Query (TanStack): para datos remotos cacheados.
  • Redux Toolkit: cuando el proyecto escala masivamente.

Buenas prácticas

  • Mantener la UI separada del estado global.
  • Evitar que múltiples componentes lean y escriban el mismo estado sin control.
  • Usar useMemo, useCallback, React.memo donde haya computación pesada.

Ejemplo con Zustand

ts
CopiarEditar
// stores/useUserStore.ts
import { create } from 'zustand';

type UserStore = {
user: { name: string } | null;
setUser: (user: { name: string }) => void;
};

export const useUserStore = create<UserStore>((set) => ({
user: null,
setUser: (user) => set({ user }),
}));

Zustand es ligero, reactivo y permite escalabilidad sin el boilerplate de Redux.


5. 📲 Llamadas a APIs y Consumo de Servicios

🎯 Objetivo

Abstraer la lógica de comunicación con backends o servicios externos para mantener los componentes limpios y testear fácilmente.

Herramientas comunes

  • fetch o axios para peticiones HTTP.
  • React Query para manejo de estado remoto (loading, caching, retries).
  • SWR si prefieres un enfoque hook-first simple.

Buenas prácticas

  • Centralizar servicios en lib/ o services/.
  • Manejar errores con try/catch y mostrar estados de carga.
  • No colocar lógica de red dentro de componentes.

Ejemplo con React Query

ts
CopiarEditar
// services/userService.ts
export const fetchUsers = async () => {
const res = await fetch('/api/users');
if (!res.ok) throw new Error('Error al cargar usuarios');
return res.json();
};

// app/page.tsx
import { useQuery } from '@tanstack/react-query';

const Page = () => {
const { data, isLoading, error } = useQuery(['users'], fetchUsers);

if (isLoading) return <div>Cargando...</div>;
if (error) return <div>Error</div>;

return <ul>{data.map(user => <li key={user.id}>{user.name}</li>)}</ul>;
};

React Query separa la lógica de datos de la presentación y ofrece manejo automático de errores, caché y reintentos.


6. 🔐 Autenticación y Autorización

🎯 Objetivo

Proteger rutas, manejar sesiones de forma segura y mostrar interfaces específicas según el rol o estado del usuario.

Alternativas de autenticación en Next.js

  • next-auth (la más popular y versátil).
  • JWT manual (custom) usando middleware y cookies firmadas.
  • Clerk/Auth0 como soluciones externas gestionadas.

Buenas prácticas

  • Usar next-auth para OAuth/JWT, fácil de integrar y seguro.
  • Proteger rutas desde el middleware (middleware.ts) y desde el servidor (getServerSideProps).
  • Usar SessionProvider y useSession para render condicional en cliente.

Ejemplo con next-auth

tsx
CopiarEditar
// middleware.ts
import { withAuth } from 'next-auth/middleware';

export default withAuth({
pages: {
signIn: '/login',
},
});

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

tsx
CopiarEditar
// app/dashboard/page.tsx
import { getServerSession } from 'next-auth';

export default async function DashboardPage() {
const session = await getServerSession();
if (!session) redirect('/login');
return <div>Bienvenido {session.user?.name}</div>;
}

Esto asegura que el acceso a rutas críticas sea controlado tanto en cliente como en servidor.


7. 🧭 Enrutamiento y Navegación

🎯 Objetivo

Aprovechar el sistema de rutas automáticas de Next.js para una navegación clara, escalable y con soporte para layouts, páginas protegidas y dynamic routing.

Buenas prácticas

  • Usar app/ y page.tsx (Next.js 13+) para rutas modernas.
  • Separar lógica de layout (layout.tsx) de contenido (page.tsx).
  • Usar rutas dinámicas con slug o id y generateMetadata() para SEO.
  • Navegación con next/link, no con a.

Ejemplo de estructura

bash
CopiarEditar
app/
├── layout.tsx # Layout principal
├── page.tsx # Home
├── dashboard/
│ ├── layout.tsx
│ └── page.tsx
├── users/
│ ├── [id]/
│ │ └── page.tsx

tsx
CopiarEditar
// app/users/[id]/page.tsx
export default async function UserDetail({ params }: { params: { id: string } }) {
const user = await fetchUser(params.id);
return <div>{user.name}</div>;
}

Con app/, cada carpeta representa una ruta y se aprovechan layouts anidados para construir dashboards complejos o zonas públicas/privadas.


8. ⚙️ Server Side Rendering (SSR), Static Site Generation (SSG) y Incremental Static Regeneration (ISR)

🎯 Objetivo

Elegir el método de renderizado más adecuado para cada tipo de contenido según sus necesidades de actualización, SEO y tiempo de respuesta.

Opciones:

TécnicaCuándo usarMétodo
SSRDatos dinámicos y privadosgetServerSideProps
SSGContenido público inmutablegenerateStaticParams
ISRDatos públicos que cambian con frecuenciarevalidate

Buenas prácticas

  • Evitar SSR para contenido que puede ser estático o cacheado.
  • Para datos públicos, usar fetch directo en los nuevos page.tsx async.
  • Usar revalidate: N para ISR automáticamente.

Ejemplo (ISR)

tsx
CopiarEditar
export async function generateStaticParams() {
const users = await fetchAllUsers();
return users.map(u => ({ id: u.id }));
}

export async function generateMetadata({ params }) {
const user = await fetchUser(params.id);
return { title: `Perfil de ${user.name}` };
}

export const revalidate = 60; // Regenerar cada 60 segundos

export default async function UserPage({ params }: { params: { id: string } }) {
const user = await fetchUser(params.id);
return <div>{user.name}</div>;
}

ISR ofrece la eficiencia del contenido estático con la frescura del dinámico.


9. 📄 Formularios y Manejo de Inputs

🎯 Objetivo

Construir formularios accesibles, con validación robusta, sin lógica duplicada ni problemas de renderizado.

Herramientas populares

  • react-hook-form: validación declarativa, performance.
  • zod, yup, joi: para validación de esquemas.
  • formik: más tradicional, pero menos performante.

Buenas prácticas

  • Centralizar reglas de validación.
  • Manejar estados como isSubmitting, errors, touched.
  • Evitar repetir lógica en JSX (usar register()).

Ejemplo con react-hook-form + zod

ts
CopiarEditar
const schema = z.object({
email: z.string().email(),
password: z.string().min(8),
});

type FormData = z.infer<typeof schema>;

const { register, handleSubmit, formState } = useForm<FormData>({
resolver: zodResolver(schema),
});

return (
<form onSubmit={handleSubmit(onSubmit)}>
<input {...register('email')} />
{formState.errors.email && <p>{formState.errors.email.message}</p>}
<button type="submit" disabled={formState.isSubmitting}>Enviar</button>
</form>
);

react-hook-form mejora el rendimiento evitando renders innecesarios, y se combina perfectamente con validadores de esquema.


10. ✅ Validaciones de Datos

🎯 Objetivo

Validar tanto en frontend como en backend para prevenir errores, evitar inputs maliciosos y garantizar integridad de datos.

Dónde validar

  • Frontend: experiencia del usuario, feedback inmediato.
  • Backend (API): seguridad y consistencia.

Herramientas recomendadas

  • zod o yup para validación de esquema.
  • react-hook-form + zodResolver para validaciones de input.
  • @hapi/joi, superstruct si trabajas en backend/API (fullstack).

Ejemplo de validación centralizada con zod

ts
CopiarEditar
// lib/schemas/user.ts
import { z } from 'zod';

export const createUserSchema = z.object({
name: z.string().min(3),
email: z.string().email(),
age: z.number().int().min(18),
});

export type CreateUserInput = z.infer<typeof createUserSchema>;

Centralizar validaciones permite compartir lógica entre cliente y servidor en aplicaciones fullstack (Next.js API routes o backend separado).


13. 🌍 Internacionalización (i18n)

🎯 Objetivo

Soportar múltiples idiomas y formatos regionales para mejorar la accesibilidad y expandir el alcance de la app.

Herramientas comunes

  • next-intl (recomendado).
  • next-i18next (más clásico).
  • react-intl, formatjs (más verboso).

Buenas prácticas

  • Centralizar los textos en archivos .json organizados por idioma.
  • Evitar usar strings hardcoded en JSX.
  • Adaptar formatos como fechas, monedas y números según la región.

Ejemplo con next-intl

tsx
CopiarEditar
// app/[locale]/page.tsx
import { useTranslations } from 'next-intl';

export default function HomePage() {
const t = useTranslations('Home');
return <h1>{t('title')}</h1>;
}

// locales/es.json
{
"Home": {
"title": "Bienvenido"
}
}

Internacionalizar permite personalizar la experiencia por país o segmento de mercado sin duplicar lógica.


14. 🧪 Testing

🎯 Objetivo

Asegurar el correcto funcionamiento de la UI, hooks, servicios y flujos de usuario mediante pruebas automatizadas.

Tipos de pruebas

  • Unitarias: funciones, hooks.
  • Componentes: vistas y comportamiento.
  • E2E: flujo completo desde navegador (ej. login → dashboard).

Herramientas recomendadas

Ejemplo: test de componente

tsx
CopiarEditar
// __tests__/UserCard.test.tsx
import { render, screen } from '@testing-library/react';
import { UserCard } from '@/components/UserCard';

test('muestra nombre y email', () => {
render(<UserCard name="Juan" email="juan@email.com" />);
expect(screen.getByText(/Juan/)).toBeInTheDocument();
});

Usar jest + testing-library permite testear sin depender de detalles de implementación (clases CSS, props internas).


15. 🚀 Performance y Optimización

🎯 Objetivo

Minimizar tiempos de carga, uso de recursos y mejorar Core Web Vitals (LCP, FID, CLS).

Buenas prácticas

  • Usar next/image para imágenes optimizadas y lazy loading.
  • Prefetch automático con <Link> (de Next.js).
  • Dividir el bundle con dynamic() y lazy imports.
  • Limpiar dependencias innecesarias del cliente.
  • Monitorizar con web-vitals, Lighthouse o Vercel Analytics.

Ejemplo: carga dinámica

tsx
CopiarEditar
import dynamic from 'next/dynamic';

const HeavyComponent = dynamic(() => import('@/components/HeavyComponent'), {
ssr: false,
loading: () => <p>Cargando...</p>,
});

Esto reduce el tamaño del bundle inicial y mejora el tiempo hasta la interactividad (TTI).


16. 🔍 SEO y Accesibilidad

🎯 Objetivo

Hacer que la aplicación sea indexable por buscadores y usable por todos los usuarios, incluidos aquellos con discapacidades.

Buenas prácticas

  • Usar generateMetadata() en app/ para meta tags.
  • Definir <title>, <meta name="description">, og:image, etc.
  • Usar etiquetas semánticas (<header>, <main>, <footer>, etc.).
  • Incluir alt en imágenes y aria-* en elementos interactivos.

Ejemplo

ts
CopiarEditar
export const metadata = {
title: 'Página de contacto',
description: 'Contáctanos para más información.',
openGraph: {
title: 'Contáctanos',
description: 'Estamos para ayudarte',
images: ['/og-image.jpg'],
},
};

Estas prácticas mejoran el posicionamiento en Google y la usabilidad para lectores de pantalla.


17. ⚙️ Despliegue y DevOps

🎯 Objetivo

Garantizar despliegues seguros, automatizados y escalables en producción.

Recomendaciones

  • Usar Vercel como plataforma oficial (mejor integración).
  • Configurar variables de entorno por entorno (.env.local, .env.production).
  • Activar analyze para visualizar tamaño del bundle.
  • Configurar robots.txt, sitemap.xml, favicon, manifest.json.

Ejemplo: script de build personalizado

json
CopiarEditar
{
"scripts": {
"dev": "next dev",
"build": "ANALYZE=true next build",
"start": "next start"
}
}

Archivos importantes

  • vercel.json: comportamiento personalizado (headers, redirects, etc).
  • next.config.js: configuración avanzada (i18n, redirects, rewrites, etc).

Vercel ofrece preview deploys automáticos y CI/CD sin configuración manual, ideal para flujos modernos de frontend.