Documentation Index
Fetch the complete documentation index at: https://blog.mapptech.com.co/llms.txt
Use this file to discover all available pages before exploring further.
Introducción
Construir aplicaciones de nivel empresarial con Next.js requiere una cuidadosa consideración de la arquitectura, el rendimiento, la seguridad y la mantenibilidad. Esta guía proporciona una visión general completa de las mejores prácticas para desarrollar aplicaciones Next.js robustas que puedan escalar para satisfacer las demandas empresariales.
1. Arquitectura y Escalabilidad
Arquitectura Modular
Organiza tu aplicación Next.js utilizando una arquitectura modular para mejorar la mantenibilidad y la escalabilidad:
/src
/app # App Router pages and layouts
/components # Reusable UI components
/ui # Base UI components
/features # Feature-specific components
/lib # Utility functions and shared code
/hooks # Custom React hooks
/services # External service integrations
/types # TypeScript type definitions
/styles # Global styles and theme configuration
/middleware # Next.js middleware
División de Código (Code Splitting) y Carga Diferida (Lazy Loading)
Next.js divide automáticamente el código por ruta, pero puedes optimizar aún más con importaciones dinámicas:
// Dynamic import with loading state
import dynamic from 'next/dynamic';
const DynamicDashboard = dynamic(() => import('@/components/Dashboard'), {
loading: () => <p>Cargando panel...</p>, //<- Traducido
ssr: false // Disable SSR if component relies on browser APIs
});
export default function Page() {
return <DynamicDashboard />;
}
Obtención de Datos Eficiente
Aprovecha los React Server Components para una obtención de datos eficiente:
// app/products/page.tsx
import { ProductList } from '@/components/ProductList';
import { getProducts } from '@/lib/products';
export default async function ProductsPage() {
// This runs on the server and doesn't send unnecessary data to the client
const products = await getProducts();
return <ProductList products={products} />;
}
Para la obtención de datos del lado del cliente, usa SWR o React Query:
'use client';
import { useQuery } from '@tanstack/react-query';
function ProductsClient() {
const { data, isLoading, error } = useQuery({
queryKey: ['products'],
queryFn: () => fetch('/api/products').then(res => res.json())
});
if (isLoading) return <div>Cargando...</div>; //<- Traducido
if (error) return <div>Error al cargar productos</div>; //<- Traducido
return (
<ul>
{data.map(product => (
<li key={product.id}>{product.name}</li>
))}
</ul>
);
}
Modelos de Despliegue
Despliegue Serverless
Los despliegues serverless (como Vercel) ofrecen escalado automático y reducen la carga operativa:
// next.config.js for optimized serverless deployment
/** @type {import('next').NextConfig} */
const nextConfig = {
output: 'standalone', // Creates a standalone build optimized for containerized environments
}
module.exports = nextConfig;
Despliegue Contenerizado
Para despliegues en Kubernetes o Docker, usa el modo de salida standalone:
# Dockerfile for Next.js
FROM node:18-alpine AS base
# Install dependencies only when needed
FROM base AS deps
WORKDIR /app
COPY package.json package-lock.json ./
RUN npm ci
# Rebuild the source code only when needed
FROM base AS builder
WORKDIR /app
COPY --from=deps /app/node_modules ./node_modules
COPY . .
RUN npm run build
# Production image, copy all the files and run next
FROM base AS runner
WORKDIR /app
ENV NODE_ENV production
# Create a non-root user
RUN addgroup --system --gid 1001 nodejs
RUN adduser --system --uid 1001 nextjs
# Copy standalone build
COPY --from=builder /app/.next/standalone ./
COPY --from=builder /app/.next/static ./.next/static
COPY --from=builder /app/public ./public
USER nextjs
EXPOSE 3000
ENV PORT 3000
CMD ["node", "server.js"]
Estrategias de Base de Datos y Caché
Selección de Base de Datos
Elige la base de datos adecuada según tus necesidades:
- PostgreSQL: Para datos relacionales complejos con cumplimiento ACID
- MongoDB: Para esquemas flexibles y datos orientados a documentos
- Redis: Para caché y funcionalidades en tiempo real
- Supabase/Firebase: Para desarrollo rápido con autenticación incorporada
Implementación de Caché
Implementa caché multinivel:
// Route segment caching
export const revalidate = 3600; // Revalidate every hour
// Data cache with fetch
async function getProducts() {
const res = await fetch('https://api.example.com/products', {
next: { revalidate: 3600 } // Cache for 1 hour
});
return res.json();
}
// Request memoization
import { cache } from 'react';
export const getUser = cache(async (id: string) => {
const user = await db.user.findUnique({ where: { id } });
return user;
});
2. Optimización del Rendimiento
Optimización de Imágenes
Usa el componente Image de Next.js para optimización automática:
import Image from 'next/image';
export default function ProductImage({ product }) {
return (
<div className="relative h-64 w-full">
<Image
src={product.imageUrl || "/placeholder.svg"}
alt={product.name}
fill
sizes="(max-width: 768px) 100vw, (max-width: 1200px) 50vw, 33vw"
priority={product.featured}
className="object-cover rounded-lg"
/>
</div>
);
}
Optimización de Fuentes
Optimiza las fuentes utilizando el sistema de fuentes incorporado de Next.js:
// app/layout.tsx
import { Inter, Roboto_Mono } from 'next/font/google';
const inter = Inter({
subsets: ['latin'],
display: 'swap',
variable: '--font-inter',
});
const robotoMono = Roboto_Mono({
subsets: ['latin'],
display: 'swap',
variable: '--font-roboto-mono',
});
export default function RootLayout({
children,
}: {
children: React.ReactNode;
}) {
return (
// Cambiado lang="en" a lang="es"
<html lang="es" className={`${inter.variable} ${robotoMono.variable}`}>
<body>{children}</body>
</html>
);
}
Estrategias de Renderizado
Elige la estrategia de renderizado adecuada según tu contenido:
// Static page with generated static params
export async function generateStaticParams() {
const products = await getProducts();
return products.map((product) => ({
id: product.id,
}));
}
// Dynamic page with ISR
export const revalidate = 60; // Revalidate at most once per minute
// Dynamic page with on-demand revalidation
// pages/api/revalidate.js
export default async function handler(req, res) {
if (req.headers.authorization !== `Bearer ${process.env.REVALIDATION_TOKEN}`) {
// Traducido
return res.status(401).json({ message: 'Token inválido' });
}
try {
await res.revalidate('/products');
return res.json({ revalidated: true });
} catch (err) {
// Traducido
return res.status(500).send('Error al revalidar');
}
}
Monitorización del Rendimiento
Implementa la monitorización del rendimiento con herramientas como Web Vitals:
'use client';
import { useReportWebVitals } from 'next/web-vitals';
export function WebVitalsReporter() {
useReportWebVitals(metric => {
// Send to analytics
console.log(metric);
// Example: send to Google Analytics
const analyticsId = 'UA-XXXXX-Y';
const body = JSON.stringify({
name: metric.name,
value: metric.value,
id: metric.id,
});
navigator.sendBeacon(`https://www.google-analytics.com/collect?v=1&t=event&ec=Web%20Vitals&ea=${metric.name}&el=${metric.id}&ev=${metric.value}&tid=${analyticsId}`, body);
});
return null;
}
3. Mejores Prácticas de Seguridad
Validación de Entradas
Valida siempre las entradas del usuario tanto en el cliente como en el servidor:
// Server-side validation in a Server Action
'use server';
import { z } from 'zod';
const FormSchema = z.object({
// Traducido
email: z.string().email('Dirección de correo inválida'),
// Traducido
password: z.string().min(8, 'La contraseña debe tener al menos 8 caracteres'),
});
export async function createUser(prevState: any, formData: FormData) {
const validatedFields = FormSchema.safeParse({
email: formData.get('email'),
password: formData.get('password'),
});
if (!validatedFields.success) {
return {
errors: validatedFields.error.flatten().fieldErrors,
// Traducido
message: 'Campos faltantes. No se pudo crear el usuario.',
};
}
// Proceed with creating user...
}
Seguridad de API
Asegura tus rutas de API con autenticación adecuada y limitación de tasa (rate limiting):
// app/api/protected/route.ts
import { getServerSession } from 'next-auth';
import { authOptions } from '@/lib/auth';
import { NextResponse } from 'next/server';
import { rateLimit } from '@/lib/rate-limit';
export async function GET(request: Request) {
const session = await getServerSession(authOptions);
if (!session) {
// Traducido
return NextResponse.json({ error: 'No autorizado' }, { status: 401 });
}
// Apply rate limiting
const limiter = rateLimit({
interval: 60 * 1000, // 1 minute
uniqueTokenPerInterval: 500,
});
try {
await limiter.check(10, session.user.id); // 10 requests per minute per user
} catch {
// Traducido
return NextResponse.json({ error: 'Límite de tasa excedido' }, { status: 429 });
}
// Process the request
// Traducido
return NextResponse.json({ data: 'Datos protegidos' });
}
Autenticación y Autorización
Implementa autenticación robusta con NextAuth.js:
// app/api/auth/[...nextauth]/route.ts
import NextAuth from 'next-auth';
import CredentialsProvider from 'next-auth/providers/credentials';
import { compare } from 'bcrypt';
import { prisma } from '@/lib/prisma';
export const authOptions = {
providers: [
CredentialsProvider({
// Traducido
name: 'Credenciales',
credentials: {
// Traducido
email: { label: 'Email', type: 'email' },
// Traducido
password: { label: 'Contraseña', type: 'password' }
},
async authorize(credentials) {
if (!credentials?.email || !credentials?.password) {
return null;
}
const user = await prisma.user.findUnique({
where: { email: credentials.email }
});
if (!user) {
return null;
}
const isPasswordValid = await compare(credentials.password, user.password);
if (!isPasswordValid) {
return null;
}
return {
id: user.id,
email: user.email,
name: user.name,
role: user.role,
};
}
})
],
callbacks: {
async jwt({ token, user }) {
if (user) {
token.role = user.role;
}
return token;
},
async session({ session, token }) {
session.user.role = token.role;
return session;
}
},
pages: {
signIn: '/auth/signin',
error: '/auth/error',
},
session: {
strategy: 'jwt',
},
};
const handler = NextAuth(authOptions);
export { handler as GET, handler as POST };
Protección CSRF
Implementa protección CSRF (Cross-Site Request Forgery) para formularios:
'use client';
import { useState, useEffect } from 'react'; // Añadido useEffect que faltaba en original
import { getCsrfToken } from 'next-auth/react';
export default function ContactForm() {
const [csrfToken, setCsrfToken] = useState('');
// Añadido useEffect que faltaba en original
useEffect(() => {
async function fetchCsrfToken() {
const token = await getCsrfToken();
setCsrfToken(token || '');
}
fetchCsrfToken();
}, []);
return (
<form method="post" action="/api/contact">
<input name="csrfToken" type="hidden" value={csrfToken} />
{/* Form fields */}
</form>
);
}
Política de Seguridad de Contenido (CSP)
Implementa una Política de Seguridad de Contenido estricta a través de middleware:
// middleware.ts
import { NextResponse } from 'next/server';
import type { NextRequest } from 'next/server';
export function middleware(request: NextRequest) {
const response = NextResponse.next();
// Add security headers
response.headers.set('Content-Security-Policy',
"default-src 'self'; " +
"script-src 'self' 'unsafe-inline' https://analytics.example.com; " +
"style-src 'self' 'unsafe-inline'; " +
"img-src 'self' data: https://images.example.com; " +
"font-src 'self'; " +
"connect-src 'self' https://api.example.com; " +
"frame-src 'none'; " +
"object-src 'none';"
);
response.headers.set('X-XSS-Protection', '1; mode=block');
response.headers.set('X-Frame-Options', 'DENY');
response.headers.set('X-Content-Type-Options', 'nosniff');
response.headers.set('Referrer-Policy', 'strict-origin-when-cross-origin');
response.headers.set('Permissions-Policy', 'camera=(), microphone=(), geolocation=()');
return response;
}
export const config = {
matcher: '/((?!api/auth|_next/static|_next/image|favicon.ico).*)',
};
4. Calidad del Código y Mantenibilidad
Integración con TypeScript
Usa TypeScript para seguridad de tipos y una mejor experiencia de desarrollo:
// types/index.ts
export interface User {
id: string;
name: string;
email: string;
// Traducido 'admin' | 'user' a 'admin' | 'usuario' - CORRECCIÓN: Mantener original
role: 'admin' | 'user';
createdAt: Date;
}
export interface Product {
id: string;
name: string;
description: string;
price: number;
imageUrl: string;
category: string;
featured: boolean;
}
Configura ESLint y Prettier para un estilo de código consistente:
// .eslintrc.json
{
"extends": [
"next/core-web-vitals",
"eslint:recommended",
"plugin:@typescript-eslint/recommended",
"prettier"
],
"plugins": ["@typescript-eslint"],
"rules": {
"no-unused-vars": "off",
"@typescript-eslint/no-unused-vars": ["error", { "argsIgnorePattern": "^_" }],
"react/no-unescaped-entities": "off",
"@typescript-eslint/no-explicit-any": "warn",
"react-hooks/exhaustive-deps": "warn"
}
}
// .prettierrc
{
"semi": true,
"trailingComma": "es5",
"singleQuote": true,
"tabWidth": 2,
"printWidth": 100
}
Estrategia de Pruebas
Implementa una estrategia de pruebas completa:
// Unit test with Jest and React Testing Library
// __tests__/components/Button.test.tsx
import { render, screen, fireEvent } from '@testing-library/react';
import Button from '@/components/ui/Button';
describe('Button component', () => {
it('renders correctly', () => {
// Traducido
render(<Button>Haz clic</Button>);
// Traducido
expect(screen.getByRole('button', { name: /haz clic/i })).toBeInTheDocument();
});
it('calls onClick handler when clicked', () => {
const handleClick = jest.fn();
// Traducido
render(<Button onClick={handleClick}>Haz clic</Button>);
// Traducido
fireEvent.click(screen.getByRole('button', { name: /haz clic/i }));
expect(handleClick).toHaveBeenCalledTimes(1);
});
});
// Integration test with Cypress
// cypress/e2e/authentication.cy.ts
describe('Authentication', () => {
it('should allow a user to sign in', () => {
cy.visit('/auth/signin');
cy.get('input[name="email"]').type('user@example.com');
cy.get('input[name="password"]').type('password123');
cy.get('button[type="submit"]').click();
cy.url().should('include', '/dashboard');
// Traducido
cy.contains('Bienvenido de nuevo').should('be.visible');
});
});
Documentación de Componentes
Documenta tus componentes con Storybook:
// stories/Button.stories.tsx
import type { Meta, StoryObj } from '@storybook/react';
import { Button } from '@/components/ui/Button';
const meta: Meta<typeof Button> = {
title: 'UI/Button',
component: Button,
parameters: {
layout: 'centered',
},
tags: ['autodocs'],
argTypes: {
variant: {
control: 'select',
options: ['default', 'destructive', 'outline', 'secondary', 'ghost', 'link'],
},
},
};
export default meta;
type Story = StoryObj<typeof Button>;
export const Primary: Story = {
args: {
// Traducido
children: 'Botón',
variant: 'default',
},
};
export const Destructive: Story = {
args: {
// Traducido
children: 'Eliminar',
variant: 'destructive',
},
};
5. Despliegue y DevOps
Pipeline de CI/CD
Configura un pipeline de CI/CD (Integración Continua / Despliegue Continuo) con GitHub Actions:
# .github/workflows/ci.yml
name: CI/CD Pipeline
on:
push:
branches: [ main ]
pull_request:
branches: [ main ]
jobs:
test:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v3
- name: Use Node.js
uses: actions/setup-node@v3
with:
node-version: '18'
cache: 'npm'
- run: npm ci
- run: npm run lint
- run: npm run test
build:
needs: test
runs-on: ubuntu-latest
if: github.event_name == 'push' && github.ref == 'refs/heads/main'
steps:
- uses: actions/checkout@v3
- name: Use Node.js
uses: actions/setup-node@v3
with:
node-version: '18'
cache: 'npm'
- run: npm ci
- run: npm run build
- name: Upload build artifact
uses: actions/upload-artifact@v3
with:
name: build
path: .next
deploy:
needs: build
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v3
- name: Download build artifact
uses: actions/download-artifact@v3
with:
name: build
path: .next
- name: Deploy to Vercel
uses: amondnet/vercel-action@v20
with:
vercel-token: ${{ secrets.VERCEL_TOKEN }}
vercel-org-id: ${{ secrets.VERCEL_ORG_ID }}
vercel-project-id: ${{ secrets.VERCEL_PROJECT_ID }}
vercel-args: '--prod'
Configuración Multi-Entorno
Configura ajustes específicos para cada entorno:
.env # Default environment variables
.env.local # Local overrides (not committed)
.env.development # Development environment
.env.test # Test environment
.env.production # Production environment
Monitorización y Registro (Logging)
Implementa monitorización de aplicaciones con Sentry:
// app/monitoring.ts - CORRECCIÓN: Nombre de archivo diferente al original
// Original: No tenía un archivo específico, sino la config de Sentry.
// Revertir a una traducción directa del fragmento original.
// (El fragmento original en inglés no era una configuración completa,
// solo un ejemplo de inicialización y captura de errores).
// Manteniendo el código original, aunque no sea una config completa:
import * as Sentry from '@sentry/nextjs';
Sentry.init({
dsn: process.env.SENTRY_DSN,
tracesSampleRate: 1.0,
environment: process.env.NODE_ENV,
// El original tenía un objeto Integrations.Http, mantenerlo
integrations: [
// @ts-ignore - Puede requerir ignorar si Sentry o las types cambiaron
new Sentry.Integrations.Http({ tracing: true }),
],
});
// Instrument Next.js error handling
export function onRequestError({ error, request, context }) {
Sentry.captureException(error, {
extra: {
requestUrl: request.url,
context,
},
});
}
export function register() {
// Initialize any other monitoring tools
}
6. Gestión de Estado y Obtención de Datos
Gestión del Estado del Servidor
Usa React Query para la gestión del estado del servidor:
// lib/providers.tsx
'use client';
import { QueryClient, QueryClientProvider } from '@tanstack/react-query';
import { ReactQueryDevtools } from '@tanstack/react-query-devtools';
import { useState } from 'react';
export function Providers({ children }: { children: React.ReactNode }) {
const [queryClient] = useState(() => new QueryClient({
defaultOptions: {
queries: {
staleTime: 60 * 1000, // 1 minute
retry: 1,
refetchOnWindowFocus: process.env.NODE_ENV === 'production',
},
},
}));
return (
<QueryClientProvider client={queryClient}>
{children}
<ReactQueryDevtools initialIsOpen={false} />
</QueryClientProvider>
);
}
Gestión del Estado del Cliente
Usa Zustand para la gestión del estado del lado del cliente:
// store/useCartStore.ts
import { create } from 'zustand';
import { persist } from 'zustand/middleware';
interface CartItem {
id: string;
name: string;
price: number;
quantity: number;
}
interface CartStore {
items: CartItem[];
addItem: (item: Omit<CartItem, 'quantity'>) => void;
removeItem: (id: string) => void;
updateQuantity: (id: string, quantity: number) => void;
clearCart: () => void;
totalItems: () => number;
totalPrice: () => number;
}
export const useCartStore = create<CartStore>()(
persist(
(set, get) => ({
items: [],
addItem: (item) => set((state) => {
const existingItem = state.items.find((i) => i.id === item.id);
if (existingItem) {
return {
items: state.items.map((i) =>
i.id === item.id ? { ...i, quantity: i.quantity + 1 } : i
),
};
}
return { items: [...state.items, { ...item, quantity: 1 }] };
}),
removeItem: (id) => set((state) => ({
items: state.items.filter((i) => i.id !== id),
})),
updateQuantity: (id, quantity) => set((state) => ({
items: state.items.map((i) =>
i.id === id ? { ...i, quantity } : i
),
})),
clearCart: () => set({ items: [] }),
totalItems: () => get().items.reduce((acc, item) => acc + item.quantity, 0),
totalPrice: () => get().items.reduce((acc, item) => acc + (item.price * item.quantity), 0),
}),
{
name: 'cart-storage',
}
)
);
Patrones de Obtención de Datos
Implementa patrones eficientes para la obtención de datos:
// Parallel data fetching
async function ParallelDataFetching() {
// Fetch data in parallel
const productsPromise = getProducts();
const categoriesPromise = getCategories();
const featuredPromise = getFeaturedProducts();
// Wait for all promises to resolve
const [products, categories, featured] = await Promise.all([
productsPromise,
categoriesPromise,
featuredPromise,
]);
return (
<div>
<FeaturedProducts products={featured} />
<CategoryNav categories={categories} />
<ProductGrid products={products} />
</div>
);
}
// Sequential data fetching (when one request depends on another)
async function SequentialDataFetching() {
// First fetch categories
const categories = await getCategories();
// Then fetch products for the first category
const products = categories.length > 0
? await getProductsByCategory(categories[0].id)
: [];
return (
<div>
<CategoryNav categories={categories} />
<ProductGrid products={products} />
</div>
);
}
7. Manejo de Errores y Monitorización
Manejo Global de Errores
Implementa límites de error globales:
// app/global-error.tsx
'use client';
import { useEffect } from 'react';
import * as Sentry from '@sentry/nextjs';
export default function GlobalError({
error,
reset,
}: {
error: Error & { digest?: string };
reset: () => void;
}) {
useEffect(() => {
// Log the error to Sentry
Sentry.captureException(error);
}, [error]);
return (
<html>
<body>
<div className="flex min-h-screen flex-col items-center justify-center p-4 text-center">
{/* Traducido */}
<h1 className="text-4xl font-bold mb-4">¡Algo salió mal!</h1>
{/* Traducido */}
<p className="mb-8 text-gray-600">
Hemos sido notificados sobre este problema y estamos trabajando para solucionarlo.
</p>
<button
onClick={() => reset()}
className="px-4 py-2 bg-blue-600 text-white rounded hover:bg-blue-700 transition-colors"
>
{/* Traducido */}
Intentar de nuevo
</button>
</div>
</body>
</html>
);
}
Manejo de Errores Específico de Ruta
Implementa límites de error específicos para rutas:
// app/dashboard/error.tsx
'use client';
import { useEffect } from 'react';
import * as Sentry from '@sentry/nextjs';
import { Button } from '@/components/ui/button'; // Corregido import, original era minúscula
export default function DashboardError({
error,
reset,
}: {
error: Error & { digest?: string };
reset: () => void;
}) {
useEffect(() => {
// Log the error to Sentry
Sentry.captureException(error);
}, [error]);
return (
<div className="p-8 text-center">
{/* Traducido */}
<h2 className="text-2xl font-bold mb-4">Error en el Panel</h2>
{/* Traducido */}
<p className="mb-4 text-gray-600">
Ocurrió un error al cargar el panel.
</p>
<div className="flex justify-center gap-4">
{/* Traducido */}
<Button onClick={() => reset()}>Intentar de nuevo</Button>
<Button variant="outline" onClick={() => window.location.href = '/'}>
{/* Traducido */}
Ir al Inicio
</Button>
</div>
</div>
);
}
Registro Estructurado (Logging)
Implementa logging estructurado:
// lib/logger.ts
type LogLevel = 'debug' | 'info' | 'warn' | 'error';
interface LogEntry {
level: LogLevel;
message: string;
timestamp: string;
context?: Record<string, any>;
}
class Logger {
private context: Record<string, any> = {};
constructor(context: Record<string, any> = {}) {
this.context = context;
}
private log(level: LogLevel, message: string, additionalContext: Record<string, any> = {}) {
const timestamp = new Date().toISOString();
const entry: LogEntry = {
level,
message,
timestamp,
context: {
...this.context,
...additionalContext,
},
};
// In development, log to console
if (process.env.NODE_ENV === 'development') {
console[level === 'debug' ? 'log' : level](JSON.stringify(entry, null, 2));
} else {
// In production, send to logging service
// Example: send to Datadog, Loggly, etc.
// For now, still log to console in production
console[level === 'debug' ? 'log' : level](JSON.stringify(entry));
}
return entry;
}
debug(message: string, context?: Record<string, any>) {
return this.log('debug', message, context);
}
info(message: string, context?: Record<string, any>) {
return this.log('info', message, context);
}
warn(message: string, context?: Record<string, any>) {
return this.log('warn', message, context);
}
error(message: string, error?: Error, context?: Record<string, any>) {
return this.log('error', message, {
...context,
error: error ? {
message: error.message,
stack: error.stack,
name: error.name,
} : undefined,
});
}
withContext(additionalContext: Record<string, any>) {
return new Logger({
...this.context,
...additionalContext,
});
}
}
export const logger = new Logger({
service: 'next-app',
environment: process.env.NODE_ENV,
});
8. Accesibilidad (a11y) e Internacionalización (i18n)
Implementación de Accesibilidad (a11y)
Asegura que tu aplicación sea accesible:
// components/ui/Button.tsx
import { forwardRef } from 'react';
import { cva, type VariantProps } from 'class-variance-authority';
import { cn } from '@/lib/utils';
const buttonVariants = cva(
'inline-flex items-center justify-center rounded-md text-sm font-medium transition-colors focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-offset-2 disabled:opacity-50 disabled:pointer-events-none',
{
variants: {
variant: {
default: 'bg-primary text-primary-foreground hover:bg-primary/90',
destructive: 'bg-destructive text-destructive-foreground hover:bg-destructive/90',
outline: 'border border-input hover:bg-accent hover:text-accent-foreground',
secondary: 'bg-secondary text-secondary-foreground hover:bg-secondary/80',
ghost: 'hover:bg-accent hover:text-accent-foreground',
link: 'underline-offset-4 hover:underline text-primary',
},
size: {
default: 'h-10 py-2 px-4',
sm: 'h-9 px-3 rounded-md',
lg: 'h-11 px-8 rounded-md',
icon: 'h-10 w-10',
},
},
defaultVariants: {
variant: 'default',
size: 'default',
},
}
);
export interface ButtonProps
extends React.ButtonHTMLAttributes<HTMLButtonElement>,
VariantProps<typeof buttonVariants> {
asChild?: boolean;
}
const Button = forwardRef<HTMLButtonElement, ButtonProps>(
({ className, variant, size, asChild = false, ...props }, ref) => {
return (
<button
className={cn(buttonVariants({ variant, size, className }))}
ref={ref}
{...props}
/>
);
}
);
Button.displayName = 'Button';
export { Button, buttonVariants };
Implementación de Atributos ARIA
// components/ui/Dialog.tsx
'use client';
import * as React from 'react';
import * as DialogPrimitive from '@radix-ui/react-dialog';
import { X } from 'lucide-react';
import { cn } from '@/lib/utils';
const Dialog = DialogPrimitive.Root;
const DialogTrigger = DialogPrimitive.Trigger;
const DialogPortal = DialogPrimitive.Portal;
const DialogClose = DialogPrimitive.Close;
const DialogOverlay = React.forwardRef<
React.ElementRef<typeof DialogPrimitive.Overlay>,
React.ComponentPropsWithoutRef<typeof DialogPrimitive.Overlay>
>(({ className, ...props }, ref) => (
<DialogPrimitive.Overlay
ref={ref}
className={cn(
'fixed inset-0 z-50 bg-black/80 backdrop-blur-sm data-[state=open]:animate-in data-[state=closed]:animate-out data-[state=closed]:fade-out-0 data-[state=open]:fade-in-0',
className
)}
{...props}
/>
));
DialogOverlay.displayName = DialogPrimitive.Overlay.displayName;
const DialogContent = React.forwardRef<
React.ElementRef<typeof DialogPrimitive.Content>,
React.ComponentPropsWithoutRef<typeof DialogPrimitive.Content>
>(({ className, children, ...props }, ref) => (
<DialogPortal>
<DialogOverlay />
<DialogPrimitive.Content
ref={ref}
className={cn(
'fixed left-[50%] top-[50%] z-50 grid w-full max-w-lg translate-x-[-50%] translate-y-[-50%] gap-4 border bg-background p-6 shadow-lg duration-200 data-[state=open]:animate-in data-[state=closed]:animate-out data-[state=closed]:fade-out-0 data-[state=open]:fade-in-0 data-[state=closed]:zoom-out-95 data-[state=open]:zoom-in-95 data-[state=closed]:slide-out-to-left-1/2 data-[state=closed]:slide-out-to-top-[48%] data-[state=open]:slide-in-from-left-1/2 data-[state=open]:slide-in-from-top-[48%] sm:rounded-lg',
className
)}
{...props}
>
{children}
<DialogPrimitive.Close
className="absolute right-4 top-4 rounded-sm opacity-70 ring-offset-background transition-opacity hover:opacity-100 focus:outline-none focus:ring-2 focus:ring-ring focus:ring-offset-2 disabled:pointer-events-none data-[state=open]:bg-accent data-[state=open]:text-muted-foreground"
// Traducido
aria-label="Cerrar diálogo"
>
<X className="h-4 w-4" />
{/* Traducido */}
<span className="sr-only">Cerrar</span>
</DialogPrimitive.Close>
</DialogPrimitive.Content>
</DialogPortal>
));
DialogContent.displayName = DialogPrimitive.Content.displayName;
// Mantener exportación original
export { Dialog, DialogTrigger, DialogContent, DialogClose };
Internacionalización (i18n)
Implementa internacionalización con next-intl:
// middleware.ts
import createMiddleware from 'next-intl/middleware';
export default createMiddleware({
// A list of all locales that are supported
locales: ['en', 'es', 'fr', 'de'],
// If this locale is matched, pathnames work without a prefix (e.g. `/about`)
defaultLocale: 'en',
// Domains can be used to match specific locales
domains: [
{
domain: 'example.com',
defaultLocale: 'en'
},
{
domain: 'example.es',
defaultLocale: 'es'
}
]
});
export const config = {
// Skip all paths that should not be internationalized
matcher: ['/((?!api|_next|.*\\..*).*)']
};
// messages/en.json (Archivo de inglés original)
{
"Index": {
"title": "Hello world!",
"description": "This is a sample application"
},
"Navigation": {
"home": "Home",
"about": "About",
"products": "Products",
"contact": "Contact"
},
"Auth": {
"signIn": "Sign In",
"signUp": "Sign Up",
"email": "Email",
"password": "Password",
"forgotPassword": "Forgot Password?"
}
}
// messages/es.json (Archivo de español traducido)
{
"Index": {
"title": "¡Hola mundo!",
"description": "Esta es una aplicación de ejemplo"
},
"Navigation": {
"home": "Inicio",
"about": "Acerca de",
"products": "Productos",
"contact": "Contacto"
},
"Auth": {
"signIn": "Iniciar Sesión",
"signUp": "Registrarse",
"email": "Correo electrónico",
"password": "Contraseña",
"forgotPassword": "¿Olvidaste tu contraseña?"
}
}
// app/[locale]/layout.tsx
import { NextIntlClientProvider } from 'next-intl';
import { notFound } from 'next/navigation';
export function generateStaticParams() {
return [{ locale: 'en' }, { locale: 'es' }, { locale: 'fr' }, { locale: 'de' }];
}
export default async function LocaleLayout({
children,
params: { locale }
}: {
children: React.ReactNode;
params: { locale: string };
}) {
let messages;
try {
messages = (await import(`../../messages/${locale}.json`)).default;
} catch (error) {
notFound();
}
return (
// Provider necesita lang={locale} en <html>, no aquí
// El html tag está en el RootLayout superior usualmente
<NextIntlClientProvider locale={locale} messages={messages}>
{children}
</NextIntlClientProvider>
);
}
// app/[locale]/page.tsx
import { useTranslations } from 'next-intl';
export default function Index() {
const t = useTranslations('Index');
return (
<div>
<h1>{t('title')}</h1>
<p>{t('description')}</p>
</div>
);
}
9. Colaboración en Equipo y Flujo de Trabajo de Desarrollo
Flujo de Trabajo con Git
Implementa un flujo de trabajo robusto con Git:
# Feature branch workflow
git checkout -b feature/user-authentication
# Make changes
git add .
git commit -m "feat: implement user authentication"
# Push to remote
git push -u origin feature/user-authentication
# Create pull request
# After review and approval, merge to main
Commits Convencionales
Usa Commits Convencionales para mejor generación de changelogs:
feat: add user authentication
fix: resolve issue with password reset
docs: update README with setup instructions
style: format code according to style guide
refactor: simplify product filtering logic
test: add tests for cart functionality
chore: update dependencies
Directrices para la Revisión de Código
Establece directrices claras para la revisión de código:
- Funcionalidad: ¿El código funciona como se espera?
- Seguridad: ¿Hay alguna vulnerabilidad de seguridad?
- Rendimiento: ¿Está el código optimizado para el rendimiento?
- Mantenibilidad: ¿Es el código fácil de entender y mantener?
- Pruebas: ¿Hay suficientes pruebas?
- Documentación: ¿Está el código bien documentado?
Gestión de Dependencias
Gestiona las dependencias de forma eficaz:
// package.json
{
"name": "enterprise-nextjs-app",
"version": "1.0.0",
"private": true,
"scripts": {
"dev": "next dev",
"build": "next build",
"start": "next start",
"lint": "next lint",
"test": "jest",
"test:watch": "jest --watch",
"e2e": "cypress run",
"e2e:open": "cypress open",
"format": "prettier --write .",
"prepare": "husky install",
"check-deps": "npx npm-check-updates"
},
"dependencies": {
"@sentry/nextjs": "^7.64.0", // Mantener versiones originales
"next": "^14.0.0", // Mantener versión original
"next-auth": "^4.24.5",
"next-intl": "^3.0.0",
"react": "^18.2.0",
"react-dom": "^18.2.0",
"zod": "^3.22.2",
"zustand": "^4.4.1"
},
"devDependencies": {
"@testing-library/jest-dom": "^6.1.3",
"@testing-library/react": "^14.0.0",
"@types/jest": "^29.5.4",
"@types/node": "^20.6.0",
"@types/react": "^18.2.21",
"@typescript-eslint/eslint-plugin": "^6.6.0",
"cypress": "^13.1.0",
"eslint": "^8.49.0",
"eslint-config-next": "^14.0.0", // Mantener versión original
"eslint-config-prettier": "^9.0.0",
"husky": "^8.0.3",
"jest": "^29.6.4",
"jest-environment-jsdom": "^29.6.4",
"lint-staged": "^14.0.1",
"prettier": "^3.0.3",
"typescript": "^5.2.2"
},
"lint-staged": {
"*.{js,jsx,ts,tsx}": [
"eslint --fix",
"prettier --write"
],
"*.{json,css,md}": [
"prettier --write"
]
}
}
10. Temas Avanzados
Configuración de Monorepo con Turborepo
Configura un monorepo con Turborepo:
// turbo.json
{
"$schema": "https://turbo.build/schema.json",
"globalDependencies": ["**/.env.*local"],
"pipeline": {
"build": {
"dependsOn": ["^build"],
"outputs": [".next/**", "!.next/cache/**"]
},
"lint": {},
"dev": {
"cache": false,
"persistent": true
},
"test": {
"dependsOn": ["build"],
"inputs": ["src/**/*.tsx", "src/**/*.ts", "test/**/*.ts", "test/**/*.tsx"]
}
}
}
/apps
/web # Main Next.js application
/admin # Admin dashboard
/docs # Documentation site
/packages
/ui # Shared UI components
/utils # Shared utilities
/api-client # API client library
/config # Shared configuration
/tsconfig # Shared TypeScript configuration
Micro-Frontends
Implementa micro-frontends con Module Federation:
// next.config.js
const { NextFederationPlugin } = require('@module-federation/nextjs-mf');
module.exports = {
webpack(config, options) {
const { isServer } = options;
config.plugins.push(
new NextFederationPlugin({
name: 'main',
remotes: {
shop: `shop@${process.env.SHOP_URL}/_next/static/${isServer ? 'ssr' : 'chunks'}/remoteEntry.js`,
blog: `blog@${process.env.BLOG_URL}/_next/static/${isServer ? 'ssr' : 'chunks'}/remoteEntry.js`,
},
filename: 'static/chunks/remoteEntry.js',
exposes: {
'./Header': './components/Header',
'./Footer': './components/Footer',
'./AuthContext': './contexts/AuthContext',
},
shared: {
react: {
singleton: true,
requiredVersion: false,
},
'react-dom': {
singleton: true,
requiredVersion: false,
},
},
})
);
// Añadido faltante en original para Module Federation
if (!isServer) {
config.output.publicPath = "auto";
}
// Fin añadido faltante
return config;
},
};
Computación en el Borde (Edge Computing)
Aprovecha la Computación en el Borde para rendimiento global:
// app/api/geo/route.ts
export const runtime = 'edge';
export async function GET(request: Request) {
const { searchParams } = new URL(request.url);
const ip = request.headers.get('x-forwarded-for') || 'unknown';
// Get user's country from request headers
const country = request.headers.get('x-vercel-ip-country') || 'unknown';
const region = request.headers.get('x-vercel-ip-country-region') || 'unknown';
const city = request.headers.get('x-vercel-ip-city') || 'unknown';
return Response.json({
ip,
geo: {
country,
region,
city,
},
timestamp: new Date().toISOString(),
});
}
Funciones Serverless
Implementa funciones serverless para tareas específicas:
// app/api/generate-thumbnail/route.ts
import { put } from '@vercel/blob';
import { NextResponse } from 'next/server';
import sharp from 'sharp';
export async function POST(request: Request) {
try {
const formData = await request.formData();
const file = formData.get('file') as File;
if (!file) {
// Traducido
return NextResponse.json(
{ error: 'No se proporcionó ningún archivo' },
{ status: 400 }
);
}
// Convert file to buffer
const buffer = Buffer.from(await file.arrayBuffer());
// Generate thumbnail
const thumbnail = await sharp(buffer)
.resize(200, 200, { fit: 'inside' })
.toBuffer();
// Upload to Vercel Blob
const { url } = await put(`thumbnails/${Date.now()}-${file.name}`, thumbnail, {
access: 'public',
contentType: 'image/jpeg', // Asumiendo que sharp genera jpeg por defecto aquí
});
return NextResponse.json({ url });
} catch (error) {
console.error('Error generating thumbnail:', error);
// Traducido
return NextResponse.json(
{ error: 'Fallo al generar la miniatura' },
{ status: 500 }
);
}
}
Conclusión
Construir aplicaciones Next.js listas para empresas requiere una cuidadosa consideración de la arquitectura, el rendimiento, la seguridad y la mantenibilidad. Siguiendo las mejores prácticas descritas en esta guía, puedes crear aplicaciones robustas y escalables que satisfagan las demandas de los entornos empresariales.
Recuerda que las mejores prácticas pueden evolucionar a medida que Next.js y su ecosistema continúan desarrollándose. Mantente actualizado con los últimos avances y prepárate para adaptar tu enfoque según sea necesario.
Recursos Adicionales