Obtención, almacenamiento en caché y revalidación de datos
La obtención de datos es una parte fundamental de cualquier aplicación. Esta página explica cómo puedes obtener, almacenar en caché y revalidar datos en React y Next.js.
Existen cuatro formas de obtener datos:
- En el servidor, con
fetch
- En el servidor, con bibliotecas de terceros
- En el cliente, mediante un Route Handler
- En el cliente, con bibliotecas de terceros.
Obtención de datos en el servidor con fetch
Next.js extiende la API nativa fetch
de Web para permitirte configurar el comportamiento de almacenamiento en caché y revalidación para cada solicitud fetch en el servidor. React extiende fetch
para memorizar automáticamente las solicitudes fetch mientras se renderiza un árbol de componentes de React.
Puedes usar fetch
con async
/await
en Server Components, en Route Handlers, y en Server Actions.
Por ejemplo:
async function getData() {
const res = await fetch('https://api.example.com/...')
// El valor devuelto *no* está serializado
// Puedes devolver Date, Map, Set, etc.
if (!res.ok) {
// Esto activará el límite de error más cercano `error.js`
throw new Error('Error al obtener los datos')
}
return res.json()
}
export default async function Page() {
const data = await getData()
return <main></main>
}
async function getData() {
const res = await fetch('https://api.example.com/...')
// El valor devuelto *no* está serializado
// Puedes devolver Date, Map, Set, etc.
if (!res.ok) {
// Esto activará el límite de error más cercano `error.js`
throw new Error('Error al obtener los datos')
}
return res.json()
}
export default async function Page() {
const data = await getData()
return <main></main>
}
Nota importante:
- Next.js proporciona funciones útiles que puedes necesitar al obtener datos en Server Components, como
cookies
yheaders
. Estas harán que la ruta se renderice dinámicamente ya que dependen de información en tiempo de solicitud.- En Route Handlers, las solicitudes fetch no se memorizan ya que los Route Handlers no son parte del árbol de componentes de React.
- Para usar
async
/await
en un Server Component con TypeScript, necesitarás TypeScript5.1.3
o superior y@types/react
18.2.8
o superior.
Almacenamiento en caché de datos
El almacenamiento en caché guarda datos para que no sea necesario volver a obtenerlos desde tu fuente de datos en cada solicitud.
Por defecto, Next.js almacena automáticamente en caché los valores devueltos por fetch
en el Data Cache del servidor. Esto significa que los datos pueden obtenerse en tiempo de compilación o solicitud, almacenarse en caché y reutilizarse en cada solicitud de datos.
// 'force-cache' es el valor por defecto y puede omitirse
fetch('https://...', { cache: 'force-cache' })
Las solicitudes fetch
que usan el método POST
también se almacenan automáticamente en caché. A menos que estén dentro de un Route Handler que use el método POST
, en cuyo caso no se almacenarán en caché.
¿Qué es el Data Cache?
El Data Cache es una caché HTTP persistente. Dependiendo de tu plataforma, la caché puede escalar automáticamente y compartirse entre múltiples regiones.
Más información sobre el Data Cache.
Revalidación de datos
La revalidación es el proceso de purgar el Data Cache y volver a obtener los datos más recientes. Esto es útil cuando tus datos cambian y quieres asegurarte de mostrar la información más actualizada.
Los datos en caché pueden revalidarse de dos formas:
- Revalidación basada en tiempo: Revalida automáticamente los datos después de que haya pasado cierta cantidad de tiempo. Esto es útil para datos que cambian con poca frecuencia y la frescura no es crítica.
- Revalidación bajo demanda: Revalida manualmente los datos basándose en un evento (por ejemplo, el envío de un formulario). La revalidación bajo demanda puede usar un enfoque basado en etiquetas o rutas para revalidar grupos de datos a la vez. Esto es útil cuando quieres asegurarte de que se muestren los datos más recientes lo antes posible (por ejemplo, cuando se actualiza contenido desde tu CMS headless).
Revalidación basada en tiempo
Para revalidar datos en intervalos de tiempo, puedes usar la opción next.revalidate
de fetch
para establecer el tiempo de vida en caché de un recurso (en segundos).
fetch('https://...', { next: { revalidate: 3600 } })
Alternativamente, para revalidar todas las solicitudes fetch
en un segmento de ruta, puedes usar las Opciones de Configuración de Segmento.
export const revalidate = 3600 // revalidar como máximo cada hora
Si tienes múltiples solicitudes fetch en una ruta renderizada estáticamente, y cada una tiene una frecuencia de revalidación diferente. Se usará el tiempo más bajo para todas las solicitudes. Para rutas renderizadas dinámicamente, cada solicitud fetch
se revalidará independientemente.
Más información sobre revalidación basada en tiempo.
Revalidación bajo demanda
Los datos pueden revalidarse bajo demanda por ruta (revalidatePath
) o por etiqueta de caché (revalidateTag
) dentro de un Route Handler o una Server Action.
Next.js tiene un sistema de etiquetado de caché para invalidar solicitudes fetch
entre rutas.
- Al usar
fetch
, tienes la opción de etiquetar entradas de caché con una o más etiquetas. - Luego, puedes llamar a
revalidateTag
para revalidar todas las entradas asociadas con esa etiqueta.
Por ejemplo, la siguiente solicitud fetch
agrega la etiqueta de caché collection
:
export default async function Page() {
const res = await fetch('https://...', { next: { tags: ['collection'] } })
const data = await res.json()
// ...
}
export default async function Page() {
const res = await fetch('https://...', { next: { tags: ['collection'] } })
const data = await res.json()
// ...
}
Si usas un Route Handler, deberías crear un token secreto que solo conozca tu aplicación Next.js. Este secreto se usará para evitar intentos de revalidación no autorizados. Por ejemplo, puedes acceder a la ruta (ya sea manualmente o con un webhook) con la siguiente estructura de URL:
https://<tusitio.com>/api/revalidate?tag=collection&secret=<token>
import { NextRequest } from 'next/server'
import { revalidateTag } from 'next/cache'
// Ejemplo: un webhook a `tusitio.com/api/revalidate?tag=collection&secret=<token>`
export async function POST(request: NextRequest) {
const secret = request.nextUrl.searchParams.get('secret')
const tag = request.nextUrl.searchParams.get('tag')
if (secret !== process.env.MY_SECRET_TOKEN) {
return Response.json({ message: 'Token secreto inválido' }, { status: 401 })
}
if (!tag) {
return Response.json({ message: 'Falta el parámetro tag' }, { status: 400 })
}
revalidateTag(tag)
return Response.json({ revalidated: true, now: Date.now() })
}
import { revalidateTag } from 'next/cache'
// Ejemplo: un webhook a `tusitio.com/api/revalidate?tag=collection&secret=<token>`
export async function POST(request) {
const secret = request.nextUrl.searchParams.get('secret')
const tag = request.nextUrl.searchParams.get('tag')
if (secret !== process.env.MY_SECRET_TOKEN) {
return Response.json({ message: 'Token secreto inválido' }, { status: 401 })
}
if (!tag) {
return Response.json({ message: 'Falta el parámetro tag' }, { status: 400 })
}
revalidateTag(tag)
return Response.json({ revalidated: true, now: Date.now() })
}
Alternativamente, puedes usar revalidatePath
para revalidar todos los datos asociados con una ruta.
import { NextRequest } from 'next/server'
import { revalidatePath } from 'next/cache'
export async function POST(request: NextRequest) {
const path = request.nextUrl.searchParams.get('path')
if (!path) {
return Response.json({ message: 'Falta el parámetro path' }, { status: 400 })
}
revalidatePath(path)
return Response.json({ revalidated: true, now: Date.now() })
}
import { revalidatePath } from 'next/cache'
export async function POST(request) {
const path = request.nextUrl.searchParams.get('path')
if (!path) {
return Response.json({ message: 'Falta el parámetro path' }, { status: 400 })
}
revalidatePath(path)
return Response.json({ revalidated: true, now: Date.now() })
}
Más información sobre revalidación bajo demanda.
Manejo de errores y revalidación
Si se produce un error al intentar revalidar datos, se seguirán sirviendo los últimos datos generados correctamente desde la caché. En la siguiente solicitud, Next.js intentará revalidar los datos nuevamente.
Exclusión del almacenamiento en caché de datos
Las solicitudes fetch
no se almacenan en caché si:
- Se agrega
cache: 'no-store'
a las solicitudesfetch
. - Se agrega la opción
revalidate: 0
a solicitudesfetch
individuales. - La solicitud
fetch
está dentro de un Route Handler que usa el métodoPOST
. - La solicitud
fetch
ocurre después del uso deheaders
ocookies
. - Se usa la opción de segmento de ruta
const dynamic = 'force-dynamic'
. - La opción de segmento de ruta
fetchCache
está configurada para omitir la caché por defecto. - La solicitud
fetch
usa encabezadosAuthorization
oCookie
y hay una solicitud no almacenada en caché arriba en el árbol de componentes.
Solicitudes fetch
individuales
Para excluir el almacenamiento en caché para solicitudes fetch
individuales, puedes establecer la opción cache
en fetch
como 'no-store'
. Esto obtendrá los datos dinámicamente, en cada solicitud.
fetch('https://...', { cache: 'no-store' })
Consulta todas las opciones disponibles de cache
en la referencia de la API fetch
.
Múltiples solicitudes fetch
Si tienes múltiples solicitudes fetch
en un segmento de ruta (por ejemplo, un Layout o Page), puedes configurar el comportamiento de almacenamiento en caché de todas las solicitudes de datos en el segmento usando las Opciones de Configuración de Segmento.
Por ejemplo, usar const dynamic = 'force-dynamic'
hará que todos los datos se obtengan en tiempo de solicitud, y el segmento se renderizará dinámicamente.
// Agrega
export const dynamic = 'force-dynamic'
Hay una extensa lista de opciones de configuración de segmento, que te dan control detallado sobre el comportamiento estático y dinámico de un segmento de ruta. Consulta la referencia de API para más información.
Obtención de datos en el servidor con bibliotecas de terceros
En casos donde uses una biblioteca de terceros que no soporte o exponga fetch
(por ejemplo, un cliente de base de datos, CMS u ORM), puedes configurar el comportamiento de almacenamiento en caché y revalidación de esas solicitudes usando las Opciones de Configuración de Segmento y la función cache
de React.
Si los datos se almacenan en caché o no dependerá de si el segmento de ruta se renderiza estática o dinámicamente. Si el segmento es estático (por defecto), la salida de la solicitud se almacenará en caché y se revalidará como parte del segmento de ruta. Si el segmento es dinámico, la salida de la solicitud no se almacenará en caché y se volverá a obtener en cada solicitud cuando se renderice el segmento.
Nota importante:
Next.js está trabajando en una API,
unstable_cache
, para configurar el almacenamiento en caché y el comportamiento de revalidación de solicitudes individuales de terceros.
Ejemplo
En el siguiente ejemplo:
- La opción
revalidate
se establece en3600
, lo que significa que los datos se almacenarán en caché y se revalidarán como máximo cada hora. - La función
cache
de React se usa para memorizar solicitudes de datos.
import { cache } from 'react'
export const revalidate = 3600 // revalidar los datos como máximo cada hora
export const getItem = cache(async (id: string) => {
const item = await db.item.findUnique({ id })
return item
})
import { cache } from 'react'
export const revalidate = 3600 // revalidar los datos como máximo cada hora
export const getItem = cache(async (id) => {
const item = await db.item.findUnique({ id })
return item
})
Aunque la función getItem
se llama dos veces, solo se hará una consulta a la base de datos.
import { getItem } from '@/utils/get-item'
export default async function Layout({
params: { id },
}: {
params: { id: string }
}) {
const item = await getItem(id)
// ...
}
import { getItem } from '@/utils/get-item'
export default async function Layout({ params: { id } }) {
const item = await getItem(id)
// ...
}
import { getItem } from '@/utils/get-item'
export default async function Page({
params: { id },
}: {
params: { id: string }
}) {
const item = await getItem(id)
// ...
}
import { getItem } from '@/utils/get-item'
export default async function Page({ params: { id } }) {
const item = await getItem(id)
// ...
}
Obtención de datos en el cliente con Route Handlers
Si necesitas obtener datos en un componente cliente, puedes llamar a un Route Handler desde el cliente. Los Route Handlers se ejecutan en el servidor y devuelven los datos al cliente. Esto es útil cuando no quieres exponer información sensible al cliente, como tokens de API.
Consulta la documentación de Route Handler para ver ejemplos.
Server Components y Route Handlers
Dado que los Server Components se renderizan en el servidor, no necesitas llamar a un Route Handler desde un Server Component para obtener datos. En su lugar, puedes obtener los datos directamente dentro del Server Component.
Obtención de datos en el cliente con bibliotecas de terceros
También puedes obtener datos en el cliente usando una biblioteca de terceros como SWR o React Query. Estas bibliotecas proporcionan sus propias APIs para memorizar solicitudes, almacenar en caché, revalidar y mutar datos.
Futuras APIs:
use
es una función de React que acepta y maneja una promesa devuelta por una función. Envolverfetch
enuse
actualmente no se recomienda en Client Components y puede desencadenar múltiples re-renderizados. Más información sobreuse
en el RFC de React.