Next.js

Acciones de Servidor Next.js y Caché Avanzado en App Router

Domine las Acciones de Servidor Next.js, la interfaz de usuario optimista y las estrategias avanzadas de caché como revalidateTag en el App Router para aplicaciones performantes y consistentes.

By Mohamed DjoudirMay 16, 202610 min read
Compartir:
Acciones de Servidor Next.js y Caché Avanzado en App Router
#next.js#server-actions#caching#revalidation#app-router#performance

Las aplicaciones web modernas, especialmente las plataformas SaaS y los dashboards, exigen tanto capacidad de respuesta como consistencia de datos. Las Server Actions de Next.js, junto con estrategias sofisticadas de caché y revalidación dentro del App Router, proporcionan herramientas potentes para lograrlo. Al comprender cómo aprovechar estas funciones, los desarrolladores pueden crear experiencias altamente performantes y fáciles de usar, al tiempo que optimizan las interacciones con el backend.

Dominando las Server Actions de Next.js para mutaciones de datos sin esfuerzo

Las Server Actions simplifican el proceso de realizar cambios en el servidor directamente desde tus componentes de React. En lugar de construir rutas de API separadas para la lógica interna de la aplicación, puedes definir funciones del lado del servidor y llamarlas directamente desde componentes de cliente o formularios. Esto reduce significativamente el boilerplate y mejora la experiencia del desarrollador.

Las Server Actions utilizan principalmente peticiones POST y son ideales para mutaciones de datos como crear, actualizar o eliminar registros. También ofrecen mejora progresiva (progressive enhancement), lo que significa que los formularios funcionarán incluso si JavaScript está desactivado en el navegador, proporcionando una experiencia base robusta. Para la obtención de datos general o la exposición de endpoints HTTP públicos, los Route Handlers (API Routes) suelen seguir siendo la opción más adecuada.

React 19 introduce hooks refinados, totalmente compatibles con Next.js 16, que se integran perfectamente con las Server Actions:

  1. useActionState: Una evolución de useFormState, este hook proporciona una gestión de estado integral para todo el ciclo de vida de la mutación, incluyendo los datos del formulario, el estado pendiente y los resultados o errores del servidor.
  2. useOptimistic: Permite actualizaciones instantáneas de la interfaz de usuario antes de que se complete una mutación en el servidor, mejorando la percepción de rendimiento y la experiencia del usuario.
  3. useFormStatus: Ofrece acceso granular al estado pendiente del <form> más cercano o elemento de formulario, permitiendo feedback en la UI como deshabilitar botones o mostrar indicadores de carga.

Aquí tienes un ejemplo básico de una Server Action:

// app/actions.ts
"use server";

import { revalidatePath } from "next/cache";
import { redirect } from "next/navigation";

export async function createPost(formData: FormData) {
  const title = formData.get("title");
  const content = formData.get("content");

  // Simular una llamada a la base de datos
  await new Promise((resolve) => setTimeout(resolve, 1000));
  console.log("Creating post:", { title, content });

  // Invalidar la caché para mostrar los nuevos datos
  revalidatePath("/blog");
  redirect("/blog");
}

Y cómo usarla en un componente de cliente:

// app/blog/create/page.tsx
"use client";

import { createPost } from "@/app/actions";
import { useFormStatus } from "react-dom";

function SubmitButton() {
  const { pending } = useFormStatus();
  return (
    <button type="submit" disabled={pending}>
      {pending ? "Creating..." : "Create Post"}
    </button>
  );
}

export default function CreatePostPage() {
  return (
    <form action={createPost}>
      <input type="text" name="title" placeholder="Title" required /&gt;
      <textarea name="content" placeholder="Content" required /&gt;
      <SubmitButton />
    </form>
  );
}

Dominando el almacenamiento en caché de Next.js 16 con Cache Components

Next.js 16 introduce un modelo de caché más explícito y potente dentro del App Router, centrado en los Cache Components. Cuando se configura cacheComponents: true en tu next.config.ts, todos los Server Components se convierten en Cache Components. Este cambio fundamental significa que, por defecto, el código dinámico dentro de los Cache Components se ejecuta en el momento de la petición, dándote un control granular sobre cuándo y cómo se cachean los datos.

Así es como funciona este nuevo modelo de caché:

  • Dinámico por defecto: Con los Cache Components habilitados, la obtención de datos dentro de los Server Components es dinámica por defecto. Esto garantiza datos frescos para cada petición a menos que se cacheen explícitamente.
  • Caché explícito con "use cache": Puedes optar por cachear Server Components específicos o funciones de obtención de datos utilizando la directiva "use cache". Esta directiva le indica a Next.js que cachee la salida de ese componente o función.
  • cacheTag y cacheLife para el control:
    • cacheTag(...tags: string[]): Asigna una o más etiquetas a los datos cacheados, lo que permite invalidarlos selectivamente más tarde mediante updateTag o revalidateTag.
    • cacheLife({ stale, revalidate, expire }): Establece explícitamente el perfil de expiración para los datos cacheados. Aunque Next.js ofrece ajustes preestablecidos de cadena con nombre (por ejemplo, "hours", "days"), se recomienda usar la forma de objeto explícita, ya que los valores por defecto de los ajustes preestablecidos pueden variar según el entorno.
  • Router Cache (Lado del Cliente): La Router Cache del lado del cliente sigue existiendo, almacenando los resultados renderizados de las rutas visitadas para una navegación instantánea. Funciona en conjunto con los Cache Components del lado del servidor.
  • Memoización de peticiones: Dentro de una misma petición al servidor, si se realiza la misma obtención de datos varias veces, Next.js memoriza y reutiliza el resultado para evitar trabajo redundante.

Este modelo explícito permite a los desarrolladores tomar decisiones informadas sobre la frescura de los datos y el rendimiento, en lugar de depender de comportamientos de caché implícitos.

Revalidación de precisión: Aprovechando updateTag, revalidateTag y revalidatePath

Cuando tus datos cambian, asegurar que los usuarios vean la información actualizada es crítico. Next.js proporciona métodos potentes para invalidar datos cacheados. Comprender cuándo usar cada uno es clave para una gestión eficiente de la caché.

updateTag(tag: string): Invalidaciones inmediatas para consistencia "Read-Your-Writes"

Esta es la API recomendada (solo para Server Actions) para la invalidación inmediata después de una mutación de datos. Cuando llamas a updateTag(tag), Next.js invalida todos los datos cacheados asociados con esa etiqueta de forma inmediata, asegurando que las lecturas posteriores reflejen los cambios más recientes. Esto es crucial para la consistencia "read-your-writes" en aplicaciones donde los usuarios esperan ver sus cambios reflejados al instante.

"use server";
import { updateTag } from "next/cache";

export async function createProduct(formData: FormData) {
  // ... lógica de base de datos para crear el producto ...
  updateTag("products"); // Invalidar inmediatamente los datos relacionados con productos
}

revalidateTag(tag: string, profile: string | { expire: number }): Stale-While-Revalidate

revalidateTag() se utiliza para configurar un comportamiento de stale-while-revalidate para los datos cacheados. Funciona estableciendo un nuevo perfil de expiración para los datos asociados con una etiqueta específica.

Advertencia: La forma de un solo argumento revalidateTag(tag) está obsoleta en Next.js 16. Actualiza siempre a la firma de dos argumentos.

Cuando obtienes datos, puedes asociarles una etiqueta:

async function getProducts() {
  const res = await fetch("https://api.example.com/products", {
    next: { tags: ["products"] },
  });
  return res.json();
}

Luego, puedes revalidarlo con un perfil integrado (como "max") o una expiración personalizada:

"use server";
import { revalidateTag } from "next/cache";

export async function periodicallyUpdateProducts() {
  // Configura la caché para servir contenido antiguo (stale) mientras se revalida en segundo plano.
  // El perfil "max" se recomienda para datos stale-while-revalidate de larga duración.
  revalidateTag("products", "max");
}

La diferencia clave: updateTag fuerza una recuperación inmediata de los datos afectados, mientras que revalidateTag establece un cronograma para cuando los datos se vuelven obsoletos y elegibles para la revalidación.

revalidatePath(path: string): Invalidación basada en rutas

Esta función revalida todos los datos cacheados específicamente para una ruta determinada (como /blog) y también refresca la Full Route Cache para esa ruta. Aunque es más sencilla de usar, puede ser menos eficiente que updateTag si solo ha cambiado una pequeña parte de los datos de la página, lo que podría activar recuperaciones innecesarias para otros componentes en esa ruta.

En las Server Functions, revalidatePath actualmente también refresca las páginas visitadas anteriormente, pero este comportamiento es explícitamente temporal. Para la mayoría de los escenarios que involucran tipos de datos específicos (por ejemplo, una lista de posts, perfiles de usuario), updateTag o revalidateTag ofrecen una solución más robusta y eficiente.

Elevando la UX con Optimistic UI y estados pendientes en Server Actions

Un aspecto crucial de una buena experiencia de usuario es proporcionar feedback inmediato. Las Server Actions, combinadas con los hooks de React 19, facilitan la implementación de UI optimista y la gestión de estados pendientes:

  1. Actualizaciones optimistas con useOptimistic: Este hook te permite actualizar instantáneamente la interfaz de usuario con un resultado asumido de una mutación, revirtiéndolo o confirmándolo una vez que llega la respuesta real del servidor. Esto hace que la aplicación se sienta increíblemente rápida.

    // Client Component
    "use client";
    import { useOptimistic } from "react";
    import { updateItem } from "@/app/actions";
    
    type Item = { id: string; text: string; completed: boolean };
    
    export function TodoList({ items }: { items: Item[] }) {
      const [optimisticItems, addOptimisticItem] = useOptimistic(
        items,
        (currentItems, updatedItem: Item) => {
          const itemIndex = currentItems.findIndex((i) => i.id === updatedItem.id);
          if (itemIndex === -1) return [...currentItems, updatedItem];
          return currentItems.map((item) =>
            item.id === updatedItem.id ? { ...item, ...updatedItem } : item
          );
        }
      );
    
      async function toggleTodo(item: Item) {
        addOptimisticItem({ ...item, completed: !item.completed });
        await updateItem({ ...item, completed: !item.completed });
      }
    
      return (
        <ul>
          {optimisticItems.map((item) => (
            <li key={item.id}>
              <input
                type="checkbox"
                checked={item.completed}
                onChange={() => toggleTodo(item)}
              />
              {item.text} {item.completed ? "(Optimistic)" : ""}
            </li>
          ))}
        </ul>
      );
    }
    
  2. Estados pendientes con useFormStatus: Usa este hook dentro del <button type="submit"> de tu formulario o en un componente hijo para mostrar indicadores de carga o deshabilitar inputs mientras la acción está en curso.

    // (Consulta el ejemplo de SubmitButton en la sección de Server Actions)
    
  3. Ciclo de vida integral con useActionState: Para formularios, useActionState es potente ya que combina la funcionalidad de gestionar los datos del formulario, el resultado de la acción y el estado pendiente, proporcionando un enfoque unificado para manejar las mutaciones del servidor y sus interacciones en la UI.

Al combinar estos hooks, puedes crear interfaces de usuario altamente interactivas y performantes que responden instantáneamente a la entrada del usuario mientras aseguran la consistencia de los datos con el backend.

Patrones de caché avanzados: Aprovechando "use cache" y revalidación distribuida

Aunque fetch y updateTag cubren muchas necesidades de caché, Next.js ofrece patrones aún más avanzados para escenarios específicos:

Directiva "use cache" de grano fino

Con cacheComponents: true configurado en tu next.config.ts, puedes aprovechar la directiva "use cache" dentro de los Server Components o funciones de obtención de datos. Esto permite una revalidación precisa basada en el tiempo usando cacheLife:

// server-only.ts
import { cacheLife } from "next/cache";

export async function getData() {
  "use cache";
  
  // Se prefiere la configuración de objeto explícita sobre los ajustes preestablecidos
  // de cadena para garantizar un comportamiento consistente en todos los entornos.
  cacheLife({
    stale: 3600,      // 1 hora hasta que se considere obsoleto en el cliente
    revalidate: 7200, // 2 horas hasta que se revalide en el servidor
    expire: 86400     // 1 día hasta que expire por completo
  });
  
  const res = await fetch("https://api.example.com/some-data");
  return res.json();
}

Esto proporciona un control preciso sobre las recuperaciones de datos individuales o los renders de componentes, ideal para datos que no cambian con frecuencia pero que aún necesitan actualizaciones periódicas.

Revalidación distribuida para despliegues multi-instancia

En despliegues de Next.js con múltiples instancias (comunes en entornos de producción), los eventos de invalidación de caché activados por updateTag o revalidatePath suelen ser locales a la instancia que procesó la petición. Esto significa que si un usuario actualiza datos en una instancia del servidor, otras instancias podrían seguir sirviendo contenido obsoleto hasta que sus cachés locales expiren naturalmente o también sean revalidadas.

Para garantizar datos consistentes en todas las instancias, necesitas implementar un mecanismo para propagar las llamadas de invalidación. Esto normalmente implica configurar cacheHandlers personalizados en next.config.ts que transmitan estos mensajes de invalidación a todas las instancias en ejecución. Aunque Next.js proporciona los hooks para manejadores de caché personalizados para trabajos de plataforma avanzados, no incluye una receta integrada para la transmisión a través de una flota de servidores; esto queda a cargo de tu infraestructura.

Consideraciones sobre la obtención de datos en el lado del cliente

Incluso con el robusto almacenamiento en caché integrado en los Server Components y la API nativa extendida de fetch, las librerías de obtención de datos en el lado del cliente como SWR o React Query siguen ofreciendo beneficios significativos, especialmente para:

  1. Patrones complejos de invalidación de caché en el cliente: Cuando las interacciones del usuario dentro de un componente de cliente requieren una gestión de caché sofisticada.
  2. Actualizaciones optimistas avanzadas: Para UIs altamente interactivas donde las mutaciones de estado local son intrincadas.
  3. Estados de UI dedicados para carga, error y éxito: Estas librerías suelen proporcionar formas más estructuradas y optimizadas de gestionar estos estados en componentes de cliente.

Si bien los Server Components sobresalen en la obtención inicial de datos y mutaciones, estas librerías siguen siendo herramientas valiosas para una interactividad enriquecida en el lado del cliente.

Conclusión

Las Server Actions de Next.js, combinadas con una comprensión profunda de los mecanismos de caché del App Router y las estrategias avanzadas de revalidación, te permiten construir aplicaciones web altamente performantes, receptivas y con datos consistentes. Al dominar herramientas como useActionState, useOptimistic, updateTag y "use cache", puedes ofrecer experiencias de usuario excepcionales y optimizar tu flujo de trabajo de desarrollo. Mientras desarrollas tus aplicaciones Next.js, recuerda que Aniq UI ofrece plantillas premium listas para producción, construidas con estas mismas mejores prácticas, ayudándote a realizar entregas más rápidas y con confianza.

¿Encontró útil este artículo?

Compartir:
world map

Comunidad Global de Usuarios

Únete a miles de desarrolladores en todo el mundo que confían en Aniq-UI para sus proyectos. Nuestras plantillas se utilizan en todo el mundo para crear experiencias web impresionantes.

Contáctanos

Need custom work or reskin? Get in touch with us

Aniq-uiAniq-uiAniq-ui