Route Handler como proxy: ocultar tu API externa en Next.js
Llamar a una API de terceros directo desde el cliente expone tu API key en el navegador. Un Route Handler propio soluciona esto y te da control de cache.
Necesitas consumir una API externa (un servicio de pagos, un CMS headless, un proveedor de datos) desde tu app Next.js. La forma más directa —llamarla desde un Client Component con fetch()— tiene un problema serio si esa API requiere una key secreta: cualquier cosa que corra en el navegador es visible para quien abra las DevTools. Un Route Handler propio, actuando como intermediario, resuelve esto y de paso te da control adicional que vale la pena aprovechar.
El problema con llamar la API externa directo desde el cliente
// ❌ Client Component llamando directo a una API externa con key expuesta
'use client'
export function PriceWidget() {
useEffect(() => {
fetch('https://api.proveedor.com/precios', {
headers: { Authorization: `Bearer ${process.env.NEXT_PUBLIC_API_KEY}` },
})
}, [])
// ...
}
El prefijo NEXT_PUBLIC_ no es un detalle cosmético — es literalmente la señal de que esa variable de entorno se incluye en el bundle de JavaScript que se descarga al navegador. Cualquier persona puede abrir el código fuente o el Network tab y copiar esa key. Si el proveedor cobra por uso, o esa key da acceso a operaciones sensibles, esto es un riesgo real, no teórico.
La solución: un Route Handler que actúa de intermediario
// src/app/api/precios/route.ts
import { NextResponse } from 'next/server'
export async function GET() {
const response = await fetch('https://api.proveedor.com/precios', {
headers: {
// Esta key SOLO existe en el servidor, nunca llega al navegador
Authorization: `Bearer ${process.env.PROVEEDOR_API_KEY}`,
},
next: { revalidate: 300 }, // cachea la respuesta del proveedor externo 5 min
})
if (!response.ok) {
return NextResponse.json({ error: 'No se pudo obtener precios' }, { status: 502 })
}
const data = await response.json()
return NextResponse.json(data)
}
// Client Component: ahora llama a TU propio endpoint, sin key visible
'use client'
export function PriceWidget() {
useEffect(() => {
fetch('/api/precios') // sin Authorization, sin key expuesta
}, [])
// ...
}
PROVEEDOR_API_KEY (sin el prefijo NEXT_PUBLIC_) vive únicamente en el entorno del servidor — nunca se incluye en el bundle del cliente. El navegador solo conoce tu propio endpoint /api/precios, que internamente hace la llamada real con la key de forma segura.
El beneficio extra: control total sobre el cache de una API que no controlas
Aquí está el valor que va más allá de solo “ocultar la key”. Cuando el proveedor externo no te da buenos headers de Cache-Control (algo bastante común), no tienes forma de decirle al navegador cómo cachear esa respuesta si la llamas directo. Pasando por tu propio Route Handler, tú decides la política de cache, usando las mismas herramientas de Next.js que ya conoces:
export async function GET() {
const response = await fetch('https://api.proveedor.com/precios', {
headers: { Authorization: `Bearer ${process.env.PROVEEDOR_API_KEY}` },
next: { revalidate: 300, tags: ['precios-externos'] },
})
const data = await response.json()
return NextResponse.json(data, {
headers: { 'Cache-Control': 'public, max-age=60, stale-while-revalidate=300' },
})
}
Esto normaliza el comportamiento de cache de toda tu aplicación bajo un mismo criterio — independientemente de qué tan bien o mal gestione el cache el proveedor externo.
Un proxy más flexible: rutas dinámicas con [...path]
Si necesitas exponer varios endpoints del backend externo sin escribir un Route Handler por cada uno, puedes usar un segmento catch-all:
// src/app/api/backend/[...path]/route.ts
import { NextRequest, NextResponse } from 'next/server'
export async function GET(
request: NextRequest,
{ params }: { params: Promise<{ path: string[] }> }
) {
const { path } = await params
const targetUrl = `https://api.proveedor.com/${path.join('/')}${request.nextUrl.search}`
const response = await fetch(targetUrl, {
headers: { Authorization: `Bearer ${process.env.PROVEEDOR_API_KEY}` },
})
const data = await response.json()
return NextResponse.json(data, { status: response.status })
}
Una petición del cliente a /api/backend/productos/123 se reenvía internamente a https://api.proveedor.com/productos/123, con la key inyectada del lado del servidor, sin que el cliente necesite saberlo.
Cuándo vale la pena este patrón (y cuándo no)
Tiene sentido cuando:
- La API externa requiere credenciales que no deben exponerse al navegador.
- Necesitas normalizar el cache de una API que no controlas.
- Quieres transformar o filtrar la respuesta antes de que llegue al cliente (por ejemplo, remover campos internos).
Probablemente no lo necesites si la API externa ya está pensada para uso público desde el navegador (con su propia autenticación por dominio, CORS configurado, y sin datos sensibles en la key) — en ese caso, llamarla directo desde el cliente es válido y más simple.
Conclusión
Un Route Handler actuando como proxy no es solo “una capa extra de código” — es la diferencia entre exponer credenciales sensibles al público y mantenerlas exclusivamente del lado del servidor, además de darte control sobre el cache de datos que de otra forma quedarían fuera de tu gestión.
¿Tu aplicación consume APIs externas con credenciales expuestas en el cliente? Agenda una auditoría técnica gratuita de 15 minutos y lo revisamos.
¿Tienes un proyecto en mente?
Convierte tu idea en un producto real
Desarrollo web, aplicaciones a medida y consultoría tecnológica para empresas y startups. Cuéntame tu proyecto y te respondo en menos de 24 horas.