
Toni Caballero

Actualizado en noviembre de 2026 para Next.js 16 y React 19.
Las Server Actions son funciones asíncronas que se ejecutan en el servidor y que puedes llamar desde el navegador como si fueran funciones normales. Next.js crea por ti el endpoint interno, serializa los argumentos, hace la petición y te devuelve el resultado. No hace falta crear una ruta de API para cada operación.
En React se llaman Server Functions; Next.js las llama Server Actions cuando se usan para enviar formularios o modificar datos. Llegaron como experimentales en Next.js 13.4 y son estables desde Next.js 14: ya no necesitas activar nada en next.config.js.
En esta web las usamos en dos sitios reales, y de ahí salen los ejemplos de este artículo:
Basta con la directiva 'use server'. Puedes ponerla al principio de un archivo, y entonces todas las funciones que exporte serán Server Actions:
// app/content/messages.js
'use server';
import { query } from './query.js';
const sendMessage = async ({ name, email, message, surname }) => {
// `surname` es un campo trampa (honeypot): los bots lo rellenan, las personas no lo ven.
if (surname) return { ok: true };
if (!name || !email || !message) return { ok: false };
try {
const data = await query(`
mutation CreateMessage($message: MessageInput!) {
createMessage(data: $message) { documentId }
}`, { message: { Name: name, Email: email, Message: message } });
return { ok: !!data?.createMessage?.documentId };
} catch {
return { ok: false };
}
};
export { sendMessage };
También puedes declararla dentro de un Server Component, poniendo 'use server' como primera línea de la función. Lo que no puedes es definirla dentro de un Client Component: allí solo puedes importarla desde un archivo con 'use server'.
Ten en cuenta que los argumentos y el valor de retorno viajan por la red, así que deben ser serializables: objetos, arrays, strings, números, FormData… pero no funciones ni instancias de clases.
La forma más directa es pasarla a la prop action del formulario. La función recibe un FormData:
// app/newsletter/actions.js
'use server';
export async function subscribe(formData) {
const email = formData.get('email');
// ...guardar el email
}
import { subscribe } from './actions';
export default function Newsletter() {
return (
<form action={subscribe}>
<input type="email" name="email" required />
<button type="submit">Suscribirme</button>
</form>
);
}
Este formulario funciona incluso antes de que cargue el JavaScript, o si está desactivado: el navegador lo envía al servidor como un formulario de toda la vida (progressive enhancement).
Cuando necesitas mostrar un mensaje de error o de éxito, o desactivar el botón mientras se envía, usa el hook useActionState de React. La acción recibe el estado anterior como primer argumento:
'use server';
export async function subscribe(prevState, formData) {
const email = formData.get('email');
if (!email?.includes('@')) return { error: 'El email no es válido' };
// ...guardar el email
return { ok: true };
}
'use client';
import { useActionState } from 'react';
import { subscribe } from './actions';
export default function Newsletter() {
const [state, formAction, isPending] = useActionState(subscribe, {});
return (
<form action={formAction}>
<input type="email" name="email" required />
<button disabled={isPending}>{isPending ? 'Enviando…' : 'Suscribirme'}</button>
{state.error && <p>{state.error}</p>}
{state.ok && <p>¡Gracias!</p>}
</form>
);
}
Si has visto useFormState en tutoriales antiguos: es el nombre que tenía este hook antes de React 19.
Una Server Action es una función más, así que también puedes llamarla desde un onClick o desde tu propio hook. Así cargamos la siguiente página de un listado:
// app/content/listing.js
'use server';
const loadListing = async (kind, lang, { category = '', page = 0 } = {}) => {
const load = KINDS[kind];
if (!load) throw new Error(`Unknown listing "${kind}"`);
return load(lang, { category, page }); // { items, pageCount }
};
export { loadListing };
'use client';
import { loadListing } from '../content/listing';
// Dentro del hook que usan los listados:
const load = (options) => loadListing(kind, lang, options).catch(() => null);
Fíjate en que el componente cliente nunca habla con el CMS: recibe datos ya preparados.
Si tu acción cambia datos que se muestran en otras páginas, avisa a la caché de Next.js para que no se sirvan datos viejos:
revalidatePath('/blog'): marca una ruta para regenerarla.revalidateTag('posts', 'max'): invalida los datos cacheados con esa etiqueta. En Next.js 16 el segundo argumento (un perfil de cacheLife) es obligatorio.updateTag('posts'): nuevo en Next.js 16 y exclusivo de Server Actions. Expira la caché al momento, para que quien acaba de guardar vea su cambio en la siguiente carga.redirect('/gracias'): envía al usuario a otra página al terminar.Un detalle importante: redirect funciona lanzando una excepción especial. No lo pongas dentro de un try/catch, porque lo capturarías y la redirección no ocurriría. Llámalo después del bloque.
Que no escribas la ruta de API no significa que no exista. Cualquiera puede llamar a una Server Action con una petición POST, así que trátala como cualquier API:
Next.js te ayuda: solo acepta peticiones del mismo origen (puedes ampliarlo con serverActions.allowedOrigins), limita el tamaño del cuerpo a 1 MB por defecto (serverActions.bodySizeLimit), cifra las variables que una acción captura de su entorno y crea endpoints solo para las acciones que realmente usas.
Están pensadas para modificar datos. Para leer datos al renderizar una página, haz la consulta directamente en el Server Component: es más simple y se puede cachear. Ten en cuenta también que las Server Actions se ejecutan una detrás de otra, no en paralelo. Para cargar «una página más» de un listado, como hacemos nosotros, funcionan bien; para pedir muchos datos a la vez, no son la mejor opción.
Las Server Actions permiten escribir la lógica de servidor (guardar un formulario, llamar a una API externa, enviar un email) justo al lado del componente que la usa, sin rutas de API intermedias y con formularios que funcionan sin JavaScript. Úsalas para mutaciones, valida y autoriza dentro de cada una, y revalida la caché cuando cambies datos.
Es como trabajamos en nuestros proyectos de desarrollo web a medida con Next.js. Si estás montando algo parecido y quieres una segunda opinión, hablemos.