Middleware y API Routes
Middleware
El middleware intercepta requests antes de que lleguen al route handler. Se define en middleware.ts en la raíz del proyecto:
// middleware.ts
import { NextResponse } from "next/server";
import type { NextRequest } from "next/server";
export function middleware(request: NextRequest) {
// Agregar header custom a todas las respuestas
const response = NextResponse.next();
response.headers.set("x-powered-by", "vinext");
return response;
}
Matchers
Controla en qué rutas se ejecuta el middleware:
export const config = {
matcher: ["/dashboard/:path*", "/api/:path*"],
};
Sin configuración explícita, el middleware se ejecuta en todas las rutas excepto /_next/*, archivos estáticos y /favicon.ico.
Patrones de middleware
Redirect condicional:
export function middleware(request: NextRequest) {
const token = request.cookies.get("auth-token");
if (!token && request.nextUrl.pathname.startsWith("/dashboard")) {
return NextResponse.redirect(new URL("/login", request.url));
}
return NextResponse.next();
}
Rewrite (proxy interno):
export function middleware(request: NextRequest) {
const country = request.geo?.country || "US";
if (country === "ES") {
return NextResponse.rewrite(new URL("/es" + request.nextUrl.pathname, request.url));
}
return NextResponse.next();
}
Modificar headers de request:
export function middleware(request: NextRequest) {
const requestHeaders = new Headers(request.headers);
requestHeaders.set("x-request-id", crypto.randomUUID());
return NextResponse.next({
request: { headers: requestHeaders },
});
}
Route Handlers (App Router)
Los Route Handlers son la alternativa moderna a las API routes de Pages Router. Se definen en archivos route.ts:
// app/api/hello/route.ts
export async function GET() {
return Response.json({
message: "Hola desde Vinext",
timestamp: new Date().toISOString(),
});
}
Métodos HTTP
Cada método se exporta como una función nombrada:
// app/api/posts/route.ts
export async function GET() {
const posts = await db.posts.findMany();
return Response.json(posts);
}
export async function POST(request: Request) {
const body = await request.json();
const post = await db.posts.create(body);
return Response.json(post, { status: 201 });
}
Métodos soportados: GET, POST, PUT, DELETE, PATCH. Vinext genera automáticamente OPTIONS y HEAD.
Rutas dinámicas en Route Handlers
// app/api/posts/[id]/route.ts
export async function GET(
_request: Request,
{ params }: { params: Promise<{ id: string }> }
) {
const { id } = await params;
const post = await db.posts.findById(id);
if (!post) {
return Response.json({ error: "No encontrado" }, { status: 404 });
}
return Response.json(post);
}
export async function DELETE(
_request: Request,
{ params }: { params: Promise<{ id: string }> }
) {
const { id } = await params;
await db.posts.delete(id);
return new Response(null, { status: 204 });
}
Cookies en Route Handlers
import { cookies } from "next/headers";
export async function GET() {
const cookieStore = await cookies();
const token = cookieStore.get("session");
return Response.json({ authenticated: !!token });
}
export async function POST() {
const cookieStore = await cookies();
cookieStore.set("session", crypto.randomUUID(), {
httpOnly: true,
secure: true,
maxAge: 60 * 60 * 24, // 1 día
});
return Response.json({ ok: true });
}
Headers
import { headers } from "next/headers";
export async function GET() {
const headersList = await headers();
const userAgent = headersList.get("user-agent");
const referer = headersList.get("referer");
return Response.json({ userAgent, referer });
}
Utilidades de next/server
NextRequest
Extiende Request con utilidades adicionales:
import type { NextRequest } from "next/server";
export function middleware(request: NextRequest) {
// URL con helpers
console.log(request.nextUrl.pathname);
console.log(request.nextUrl.searchParams.get("q"));
// Cookies con API mejorada
const session = request.cookies.get("session");
console.log(session?.value);
// Geolocalización (cuando está disponible)
console.log(request.geo?.country);
console.log(request.ip);
}
NextResponse
Extiende Response con factories estáticas:
import { NextResponse } from "next/server";
// JSON response
NextResponse.json({ data: "valor" });
// Redirect
NextResponse.redirect(new URL("/login", request.url));
// Rewrite (cambia la ruta internamente sin redirect)
NextResponse.rewrite(new URL("/api/v2/data", request.url));
// Continuar con modificaciones
NextResponse.next({ headers: { "x-custom": "valor" } });
userAgent
import { userAgent } from "next/server";
export function middleware(request: NextRequest) {
const { browser, device, os } = userAgent(request);
if (device.type === "mobile") {
return NextResponse.rewrite(new URL("/mobile" + request.nextUrl.pathname, request.url));
}
}
Siguiente paso
En el siguiente capítulo aprenderemos a desplegar nuestra aplicación en Cloudflare Workers con vinext deploy.