📘 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.jspara centralización. - Usar
@layer componentspara 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.memodonde 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
fetchoaxiospara peticiones HTTP.React Querypara manejo de estado remoto (loading, caching, retries).SWRsi prefieres un enfoque hook-first simple.
Buenas prácticas
- Centralizar servicios en
lib/oservices/. - Manejar errores con
try/catchy 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-authpara OAuth/JWT, fácil de integrar y seguro. - Proteger rutas desde el middleware (
middleware.ts) y desde el servidor (getServerSideProps). - Usar
SessionProvideryuseSessionpara 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/ypage.tsx(Next.js 13+) para rutas modernas. - Separar lógica de layout (
layout.tsx) de contenido (page.tsx). - Usar rutas dinámicas con
slugoidygenerateMetadata()para SEO. - Navegación con
next/link, no cona.
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écnica | Cuándo usar | Método |
|---|---|---|
| SSR | Datos dinámicos y privados | getServerSideProps |
| SSG | Contenido público inmutable | generateStaticParams |
| ISR | Datos públicos que cambian con frecuencia | revalidate |
Buenas prácticas
- Evitar SSR para contenido que puede ser estático o cacheado.
- Para datos públicos, usar
fetchdirecto en los nuevospage.tsx async. - Usar
revalidate: Npara 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
zodoyuppara validación de esquema.react-hook-form+zodResolverpara validaciones de input.@hapi/joi,superstructsi 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
.jsonorganizados 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
Jest: para pruebas unitarias y mocks.React Testing Library: pruebas de componentes realistas.Cypress: pruebas E2E.Playwright: alternativa a Cypress.
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/imagepara 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()enapp/para meta tags. - Definir
<title>,<meta name="description">,og:image, etc. - Usar etiquetas semánticas (
<header>,<main>,<footer>, etc.). - Incluir
alten imágenes yaria-*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
analyzepara 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.