Testing, Optimización, Seguridad y Despliegue
13. Testing
Regla
13.1: Se deben escribir pruebas unitarias para componentes, utilidades y lógica de negocio crítica utilizando Jest y React Testing Library. 13.2: Se deben escribir pruebas de integración para verificar la interacción entre componentes y Route Handlers/Server Actions. 13.3: Se debe considerar el uso de pruebas End-to-End (E2E) con herramientas como Cypress o Playwright para validar los flujos de usuario completos. 13.4: Las pruebas deben cubrir los casos de éxito, casos límite y manejo de errores. 13.5: Se debe integrar la ejecución de pruebas en el pipeline de CI/CD para asegurar la calidad antes del despliegue. 13.6: No se deben mockear excesivamente las dependencias en pruebas unitarias; mockear solo lo necesario para aislar la unidad bajo prueba.
Contexto/Justificación
El testing es fundamental para garantizar la calidad, mantenibilidad y fiabilidad de la aplicación. Next.js se integra bien con el ecosistema de testing de React. Las pruebas unitarias validan pequeñas piezas de código, las de integración comprueban su colaboración y las E2E aseguran que la aplicación funcione como un todo desde la perspectiva del usuario.
Ejemplos
Correcto (Prueba unitaria de componente con React Testing Library):
// components/Button/Button.test.jsx
import { render, screen, fireEvent } from '@testing-library/react';
import Button from './Button';
import '@testing-library/jest-dom';
// Mock de una función onClick
const handleClick = jest.fn();
it('debe renderizar el botón y llamar a onClick al hacer clic', () => {
render(<Button onClick={handleClick}>Haz clic</Button>);
// Verificar que el botón está en el documento
const buttonElement = screen.getByRole('button', { name: /haz clic/i });
expect(buttonElement).toBeInTheDocument();
// Simular clic
fireEvent.click(buttonElement);
// Verificar que la función mock fue llamada
expect(handleClick).toHaveBeenCalledTimes(1);
});
Correcto (Prueba E2E simple con Cypress - conceptual):
// cypress/e2e/login.cy.js
describe('Flujo de Login', () => {
it('debe permitir al usuario iniciar sesión correctamente', () => {
cy.visit('/login');
cy.get('input[name="email"]').type('usuario@ejemplo.com');
cy.get('input[name="password"]').type('contraseña123');
cy.get('button[type="submit"]').click();
cy.url().should('include', '/dashboard');
cy.contains('Bienvenido, usuario').should('be.visible');
});
});
Cuándo aplicar
- Siempre, como parte integral del desarrollo de cualquier funcionalidad.
Cuándo evitar
- Escribir pruebas triviales que no aportan valor (ej. probar directamente implementaciones de terceros).
- Retrasar la escritura de pruebas hasta el final del desarrollo.
14. Optimización del Rendimiento
Regla
14.1: Se debe aprovechar el renderizado en servidor (SSR/SSG/ISR) y los Server Components para minimizar el JavaScript enviado al cliente.
14.2: Se deben optimizar las imágenes utilizando el componente next/image para el redimensionamiento, optimización y carga diferida automáticos.
14.3: Se debe realizar Code Splitting automático a nivel de ruta (proporcionado por Next.js) y considerar el Code Splitting dinámico con next/dynamic para componentes grandes o de uso infrecuente.
14.4: Se deben analizar los bundles de la aplicación utilizando @next/bundle-analyzer para identificar e importar dependencias pesadas o innecesarias.
14.5: Se debe hacer un uso eficiente del caching (Data Cache, Full Route Cache, Request Memoization) configurándolo adecuadamente según las necesidades de frescura de los datos.
14.6: Para fuentes, se debe utilizar next/font para optimizar su carga y evitar cambios de layout (CLS).
14.7: No se debe realizar fetching de datos innecesario o redundante en el cliente si puede hacerse en el servidor.
Contexto/Justificación
El rendimiento web es crítico para la experiencia del usuario y el SEO. Next.js ofrece múltiples características integradas para optimizar el rendimiento, desde el renderizado y la carga de assets hasta el caching y el análisis de bundles. Aplicar estas optimizaciones de forma obligatoria es esencial.
Ejemplos
Correcto (Uso de next/image):
import Image from 'next/image';
import profilePic from '../public/me.png';
export default function MyComponent() {
return (
<Image
src={profilePic}
alt="Foto de perfil"
width={500} // Opcional si la imagen es local estática
height={500} // Opcional si la imagen es local estática
// placeholder="blur" // Opcional
// quality={80} // Opcional
priority // Opcional, para imágenes LCP
/>
);
}
Correcto (Uso de next/dynamic):
import dynamic from 'next/dynamic';
// Carga dinámica de un componente pesado solo en el cliente
const HeavyClientComponent = dynamic(() => import('../components/HeavyClientComponent'), {
ssr: false, // No renderizar en servidor
loading: () => <p>Cargando componente...</p>,
});
export default function Page() {
return (
<div>
<h1>Página Principal</h1>
<HeavyClientComponent />
</div>
);
}
Correcto (Uso de next/font):
// app/layout.tsx
import { Inter } from 'next/font/google';
const inter = Inter({ subsets: ['latin'] });
export default function RootLayout({ children }) {
return (
<html lang="es" className={inter.className}>
<body>{children}</body>
</html>
);
}
Cuándo aplicar
- De forma continua durante el desarrollo y antes de cada despliegue.
- Al añadir nuevas dependencias o funcionalidades pesadas.
Cuándo evitar
- La sobre-optimización prematura que complica el código sin beneficios medibles.
15. Seguridad
Regla
15.1: Se deben validar y sanitizar todas las entradas del usuario, tanto en el cliente como en el servidor (especialmente en API Routes y Server Actions), para prevenir ataques XSS, inyección de SQL, etc.
15.2: Se deben gestionar los secretos (claves de API, contraseñas de base de datos) de forma segura utilizando variables de entorno y nunca exponerlos en el código del lado del cliente.
15.3: Se deben implementar mecanismos de autenticación y autorización robustos para proteger rutas y endpoints de API sensibles.
15.4: Se deben configurar las cabeceras de seguridad HTTP apropiadas (ej. Content-Security-Policy, X-Content-Type-Options, Referrer-Policy, Strict-Transport-Security) utilizando el archivo next.config.js o middleware.
15.5: Se deben mantener actualizadas las dependencias del proyecto (npm audit fix o yarn audit) para corregir vulnerabilidades conocidas.
15.6: No se debe confiar implícitamente en los datos provenientes del cliente; siempre validar en el servidor.
15.7: No se debe utilizar dangerouslySetInnerHTML sin una sanitización extremadamente cuidadosa del contenido HTML.
Contexto/Justificación
La seguridad es primordial. Las aplicaciones Next.js, como cualquier aplicación web, están expuestas a diversas amenazas. Es crucial adoptar prácticas seguras por defecto, como la validación de entradas, el manejo seguro de secretos, la autenticación/autorización y la configuración de cabeceras de seguridad, para proteger la aplicación y los datos de los usuarios.
Ejemplos
Correcto (Validación en Server Action):
// app/actions.ts
'use server';
import { z } from 'zod';
import { db } from '@/lib/db';
import { revalidatePath } from 'next/cache';
const formSchema = z.object({
comment: z.string().min(1).max(500), // Validación de longitud
});
export async function addComment(formData: FormData) {
const rawData = {
comment: formData.get('comment'),
};
const validation = formSchema.safeParse(rawData);
if (!validation.success) {
return { errors: validation.error.flatten().fieldErrors };
}
// Sanitizar si es necesario antes de guardar
const sanitizedComment = sanitizeHtml(validation.data.comment); // Usar una librería como DOMPurify
try {
await db.saveComment(sanitizedComment);
revalidatePath('/posts/123'); // Revalidar caché
return { success: true };
} catch (error) {
return { errors: { _form: ['Error al guardar comentario'] } };
}
}
Correcto (Variables de entorno):
// .env.local (NO subir a Git)
DATABASE_URL="postgres://user:password@host:port/db"
API_KEY="secretkey123"
NEXT_PUBLIC_ANALYTICS_ID="UA-12345-Y" // Prefijo NEXT_PUBLIC_ para exponer al cliente
// lib/db.ts (Acceso en servidor)
const connectionString = process.env.DATABASE_URL;
// components/Analytics.jsx (Acceso en cliente)
const analyticsId = process.env.NEXT_PUBLIC_ANALYTICS_ID;
Cuándo aplicar
- Siempre, en todas las partes de la aplicación que manejen datos de usuario o accedan a recursos protegidos.
Cuándo evitar
- Introducir complejidad innecesaria para riesgos de seguridad muy bajos o inexistentes (evaluar el riesgo).
16. TypeScript
Regla
16.1: Se debe utilizar TypeScript en todos los nuevos proyectos Next.js.
16.2: Se deben definir tipos explícitos para props de componentes, estado, respuestas de API y cualquier estructura de datos compleja.
16.3: Se debe habilitar y configurar el modo estricto (strict: true) en tsconfig.json para aprovechar al máximo las verificaciones de tipo.
16.4: Se deben utilizar tipos proporcionados por Next.js (ej. NextPage, GetServerSideProps, AppProps, tipos para Route Handlers) cuando sea apropiado.
16.5: No se debe abusar del tipo any; utilizarlo solo como último recurso cuando el tipo no pueda ser inferido o definido fácilmente.
16.6: Se deben preferir interfaces (interface) para definir la forma de objetos públicos (ej. props, respuestas API) y tipos (type) para uniones, intersecciones o tipos más complejos.
Contexto/Justificación
TypeScript mejora la robustez, mantenibilidad y experiencia de desarrollo al añadir tipado estático a JavaScript. Next.js tiene un excelente soporte integrado para TypeScript. Usarlo de forma consistente y estricta ayuda a detectar errores en tiempo de compilación y mejora la colaboración en equipo.
Ejemplos
Correcto (Props de componente tipadas):
// components/UserProfile.tsx
import React from 'react';
interface UserProfileProps {
userId: string;
name: string;
email?: string; // Prop opcional
isActive: boolean;
}
const UserProfile: React.FC<UserProfileProps> = ({ userId, name, email, isActive }) => {
return (
<div>
<h2>{name} ({userId})</h2>
{email && <p>Email: {email}</p>}
<p>Estado: {isActive ? 'Activo' : 'Inactivo'}</p>
</div>
);
};
export default UserProfile;
Correcto (Tipado de Route Handler):
// app/api/items/route.ts
import { NextRequest, NextResponse } from 'next/server';
interface Item {
id: number;
name: string;
}
let items: Item[] = [{ id: 1, name: 'Ejemplo' }]; // Datos simulados
export async function GET(request: NextRequest): Promise<NextResponse> {
// request tiene tipos para URL, headers, etc.
return NextResponse.json(items);
}
export async function POST(request: NextRequest): Promise<NextResponse> {
try {
const newItem: Omit<Item, 'id'> = await request.json();
const newId = items.length > 0 ? Math.max(...items.map(i => i.id)) + 1 : 1;
const itemToAdd: Item = { ...newItem, id: newId };
items.push(itemToAdd);
return NextResponse.json(itemToAdd, { status: 201 });
} catch (error) {
return NextResponse.json({ message: 'Error al procesar la petición' }, { status: 400 });
}
}
Cuándo aplicar
- En todo el código base de un proyecto Next.js.
Cuándo evitar
- En proyectos JavaScript existentes donde la migración completa a TypeScript no sea factible (aunque se recomienda una migración gradual).
- Usar
anysin justificación clara.
17. Despliegue
Regla
17.1: Se debe desplegar la aplicación Next.js en plataformas optimizadas para ello, como Vercel (recomendado por los creadores de Next.js) o Netlify, AWS Amplify, o utilizando un servidor Node.js autogestionado con el build de producción (next build y next start).
17.2: Se debe configurar un pipeline de CI/CD (ej. GitHub Actions, GitLab CI, Jenkins) para automatizar el build, las pruebas y el despliegue.
17.3: Se deben gestionar las variables de entorno específicas para cada entorno (desarrollo, staging, producción) de forma segura en la plataforma de despliegue.
17.4: Se deben monitorizar la aplicación desplegada (rendimiento, errores, disponibilidad) utilizando herramientas adecuadas (ej. Vercel Analytics, Sentry, Datadog).
17.5: Se deben configurar correctamente los dominios personalizados y los certificados SSL/TLS.
Contexto/Justificación
El despliegue es el paso final para poner la aplicación a disposición de los usuarios. Elegir la plataforma adecuada y automatizar el proceso con CI/CD asegura despliegues consistentes, rápidos y fiables. La monitorización post-despliegue es crucial para detectar y solucionar problemas en producción.
Ejemplos
Correcto (Configuración básica de Vercel):
- Conectar repositorio Git (GitHub, GitLab, Bitbucket) a Vercel.
- Vercel detecta automáticamente Next.js.
- Configurar variables de entorno en la interfaz de Vercel.
- Vercel despliega automáticamente en cada push a la rama principal (o ramas configuradas).
Correcto (Dockerfile para despliegue autogestionado):
# Etapa 1: Instalar dependencias y construir
FROM node:18-alpine AS builder
WORKDIR /app
# Copiar package.json y lockfile
COPY package*.json ./
COPY yarn.lock ./
# O COPY package-lock.json ./
# Instalar dependencias
RUN yarn install --frozen-lockfile
# O RUN npm ci
# Copiar el resto del código
COPY . .
# Configurar variables de entorno para el build si es necesario
# ARG NEXT_PUBLIC_VAR=value
# ENV NEXT_PUBLIC_VAR=$NEXT_PUBLIC_VAR
# Construir la aplicación
RUN yarn build
# O RUN npm run build
# Etapa 2: Ejecutar la aplicación
FROM node:18-alpine AS runner
WORKDIR /app
# Copiar archivos de build de la etapa anterior
COPY --from=builder /app/public ./public
COPY --from=builder /app/.next ./.next
COPY --from=builder /app/node_modules ./node_modules
COPY --from=builder /app/package.json ./
# Exponer el puerto (por defecto 3000)
EXPOSE 3000
# Comando para iniciar la aplicación
CMD ["yarn", "start"]
# O CMD ["npm", "start"]
Cuándo aplicar
- Para cualquier aplicación Next.js destinada a ser utilizada por usuarios.
Cuándo evitar
- Desplegar builds de desarrollo (
next dev) en producción. - Gestionar secretos directamente en el código fuente o en archivos no seguros.