Middleware y API Routes

Por: Artiko
vinextmiddlewareapi-routesnext-server

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.