Saltar al contenido principal

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 any sin 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.