Acciones de Servidor y Mutaciones
Acciones de Servidor (Server Actions) son funciones asíncronas que se ejecutan en el servidor. Pueden ser llamadas en Componentes de Servidor y Cliente para manejar envíos de formularios y mutaciones de datos en aplicaciones Next.js.
🎥 Ver: Aprenda más sobre mutaciones con Acciones de Servidor → YouTube (10 minutos).
Convención
Una Acción de Servidor puede ser definida con la directiva "use server"
de React. Puede colocar la directiva al inicio de una función async
para marcar la función como una Acción de Servidor, o al inicio de un archivo separado para marcar todas las exportaciones de ese archivo como Acciones de Servidor.
Componentes de Servidor
Los Componentes de Servidor pueden usar la directiva "use server"
a nivel de función o módulo. Para incluir una Acción de Servidor en línea, agregue "use server"
al inicio del cuerpo de la función:
export default function Page() {
// Acción de Servidor
async function create() {
'use server'
// Mutar datos
}
return '...'
}
export default function Page() {
// Acción de Servidor
async function create() {
'use server'
// Mutar datos
}
return '...'
}
Componentes de Cliente
Para llamar a una Función de Servidor en un Componente de Cliente, cree un nuevo archivo y agregue la directiva "use server"
al inicio. Todas las funciones exportadas dentro del archivo serán marcadas como Funciones de Servidor que pueden ser reutilizadas en Componentes de Cliente y Servidor:
'use server'
export async function create() {}
'use server'
export async function create() {}
Pasando acciones como props
También puede pasar una Acción de Servidor a un Componente de Cliente como prop:
<ClientComponent updateItemAction={updateItem} />
'use client'
export default function ClientComponent({
updateItemAction,
}: {
updateItemAction: (formData: FormData) => void
}) {
return <form action={updateItemAction}>{/* ... */}</form>
}
'use client'
export default function ClientComponent({ updateItemAction }) {
return <form action={updateItemAction}>{/* ... */}</form>
}
Normalmente, el plugin TypeScript de Next.js marcaría updateItemAction
en client-component.tsx
ya que es una función que generalmente no puede ser serializada a través de los límites cliente-servidor. Sin embargo, las props llamadas action
o que terminan con Action
se asumen que reciben Acciones de Servidor. Esto es solo una heurística ya que el plugin TypeScript no sabe realmente si recibe una Acción de Servidor o una función ordinaria. La verificación de tipos en tiempo de ejecución aún asegurará que no pase accidentalmente una función a un Componente de Cliente.
Comportamiento
- Las acciones de servidor pueden ser invocadas usando el atributo
action
en un elemento<form>
.- Los Componentes de Servidor soportan mejora progresiva por defecto, lo que significa que el formulario se enviará incluso si JavaScript no se ha cargado aún o está deshabilitado.
- En Componentes de Cliente, los formularios que invocan Acciones de Servidor encolarán los envíos si JavaScript no está cargado aún, priorizando la hidratación del cliente.
- Después de la hidratación, el navegador no se recarga al enviar el formulario.
- Las Acciones de Servidor no están limitadas a
<form>
y pueden ser invocadas desde manejadores de eventos,useEffect
, bibliotecas de terceros y otros elementos de formulario como<button>
. - Las Acciones de Servidor se integran con la arquitectura de caché y revalidación de Next.js. Cuando se invoca una acción, Next.js puede devolver tanto la UI actualizada como nuevos datos en un solo viaje al servidor.
- Detrás de escena, las acciones usan el método
POST
, y solo este método HTTP puede invocarlas. - Los argumentos y valor de retorno de las Acciones de Servidor deben ser serializables por React. Consulte la documentación de React para ver una lista de argumentos y valores serializables.
- Las Acciones de Servidor son funciones. Esto significa que pueden ser reutilizadas en cualquier parte de su aplicación.
- Las Acciones de Servidor heredan el runtime de la página o layout en el que se usan.
- Las Acciones de Servidor heredan la Configuración del Segmento de Ruta de la página o layout en el que se usan, incluyendo campos como
maxDuration
.
Ejemplos
Manejadores de eventos
Aunque es común usar Acciones de Servidor dentro de elementos <form>
, también pueden ser invocadas con manejadores de eventos como onClick
. Por ejemplo, para incrementar un contador de "me gusta":
También puede agregar manejadores de eventos a elementos de formulario, por ejemplo, para guardar un campo de formulario onChange
:
'use client'
import { publishPost, saveDraft } from './actions'
export default function EditPost() {
return (
<form action={publishPost}>
<textarea
name="content"
onChange={async (e) => {
await saveDraft(e.target.value)
}}
/>
<button type="submit">Publish</button>
</form>
)
}
Para casos como este, donde se pueden disparar múltiples eventos rápidamente, recomendamos usar debouncing para evitar invocaciones innecesarias de Acciones de Servidor.
useEffect
Puede usar el hook useEffect
de React para invocar una Acción de Servidor cuando el componente se monta o una dependencia cambia. Esto es útil para mutaciones que dependen de eventos globales o necesitan ser disparadas automáticamente. Por ejemplo, onKeyDown
para atajos de aplicación, un hook de observador de intersección para scroll infinito, o cuando el componente se monta para actualizar un contador de vistas:
'use client'
import { incrementViews } from './actions'
import { useState, useEffect, useTransition } from 'react'
export default function ViewCount({ initialViews }: { initialViews: number }) {
const [views, setViews] = useState(initialViews)
const [isPending, startTransition] = useTransition()
useEffect(() => {
startTransition(async () => {
const updatedViews = await incrementViews()
setViews(updatedViews)
})
}, [])
// Puede usar `isPending` para dar retroalimentación a los usuarios
return <p>Total Views: {views}</p>
}
'use client'
import { incrementViews } from './actions'
import { useState, useEffect, useTransition } from 'react'
export default function ViewCount({ initialViews }) {
const [views, setViews] = useState(initialViews)
const [isPending, startTransition] = useTransition()
useEffect(() => {
starTransition(async () => {
const updatedViews = await incrementViews()
setViews(updatedViews)
})
}, [])
// Puede usar `isPending` para dar retroalimentación a los usuarios
return <p>Total Views: {views}</p>
}
Recuerde considerar el comportamiento y advertencias de useEffect
.
Manejo de errores
Cuando se lanza un error, será capturado por el límite error.js
o <Suspense>
más cercano en el cliente. Consulte Manejo de Errores para más información.
Nota importante:
- Además de lanzar el error, también puede devolver un objeto para ser manejado por
useActionState
.
Revalidando datos
Puede revalidar la Caché de Next.js dentro de sus Acciones de Servidor con la API revalidatePath
:
'use server'
import { revalidatePath } from 'next/cache'
export async function createPost() {
try {
// ...
} catch (error) {
// ...
}
revalidatePath('/posts')
}
'use server'
import { revalidatePath } from 'next/cache'
export async function createPost() {
try {
// ...
} catch (error) {
// ...
}
revalidatePath('/posts')
}
O invalidar una obtención de datos específica con una etiqueta de caché usando revalidateTag
:
'use server'
import { revalidateTag } from 'next/cache'
export async function createPost() {
try {
// ...
} catch (error) {
// ...
}
revalidateTag('posts')
}
'use server'
import { revalidateTag } from 'next/cache'
export async function createPost() {
try {
// ...
} catch (error) {
// ...
}
revalidateTag('posts')
}
Redireccionamiento
Si desea redirigir al usuario a una ruta diferente después de completar una Acción de Servidor, puede usar la API redirect
. redirect
debe ser llamada fuera del bloque try/catch
:
'use server'
import { redirect } from 'next/navigation'
import { revalidateTag } from 'next/cache'
export async function createPost(id: string) {
try {
// ...
} catch (error) {
// ...
}
revalidateTag('posts') // Actualizar posts en caché
redirect(`/post/${id}`) // Navegar a la página del nuevo post
}
'use server'
import { redirect } from 'next/navigation'
import { revalidateTag } from 'next/cache'
export async function createPost(id) {
try {
// ...
} catch (error) {
// ...
}
revalidateTag('posts') // Actualizar posts en caché
redirect(`/post/${id}`) // Navegar a la página del nuevo post
}
Cookies
Puede obtener
, establecer
y eliminar
cookies dentro de una Acción de Servidor usando la API cookies
:
'use server'
import { cookies } from 'next/headers'
export async function exampleAction() {
const cookieStore = await cookies()
// Obtener cookie
cookieStore.get('name')?.value
// Establecer cookie
cookieStore.set('name', 'Delba')
// Eliminar cookie
cookieStore.delete('name')
}
'use server'
import { cookies } from 'next/headers'
export async function exampleAction() {
// Obtener cookie
const cookieStore = await cookies()
// Obtener cookie
cookieStore.get('name')?.value
// Establecer cookie
cookieStore.set('name', 'Delba')
// Eliminar cookie
cookieStore.delete('name')
}
Consulte ejemplos adicionales para eliminar cookies desde Acciones de Servidor.
Seguridad
Por defecto, cuando se crea y exporta una Acción de Servidor, crea un endpoint HTTP público y debe ser tratada con las mismas suposiciones de seguridad y verificaciones de autorización. Esto significa, incluso si una Acción de Servidor o función de utilidad no es importada en otro lugar de su código, sigue siendo accesible públicamente.
Para mejorar la seguridad, Next.js tiene las siguientes características incorporadas:
- IDs de acción seguros: Next.js crea IDs encriptados y no determinísticos para permitir que el cliente haga referencia y llame a la Acción de Servidor. Estos IDs son recalculados periódicamente entre builds para mayor seguridad.
- Eliminación de código muerto: Las Acciones de Servidor no utilizadas (referenciadas por sus IDs) son eliminadas del bundle del cliente para evitar acceso público por terceros.
Nota importante:
Los IDs son creados durante la compilación y se almacenan en caché por un máximo de 14 días. Serán regenerados cuando se inicie un nuevo build o cuando la caché de build sea invalidada. Esta mejora de seguridad reduce el riesgo en casos donde falta una capa de autenticación. Sin embargo, aún debe tratar las Acciones de Servidor como endpoints HTTP públicos.
// app/actions.js
'use server'
// Esta acción **es** usada en nuestra aplicación, por lo que Next.js
// creará un ID seguro para permitir que el cliente haga referencia
// y llame a la Acción de Servidor.
export async function updateUserAction(formData) {}
// Esta acción **no es** usada en nuestra aplicación, por lo que Next.js
// automáticamente eliminará este código durante `next build`
// y no creará un endpoint público.
export async function deleteUserAction(formData) {}
Autenticación y autorización
Debe asegurarse de que el usuario esté autorizado para realizar la acción. Por ejemplo:
'use server'
import { auth } from './lib'
export function addItem() {
const { user } = auth()
if (!user) {
throw new Error('Debes iniciar sesión para realizar esta acción')
}
// ...
}
Closures y encriptación
Definir una Acción de Servidor dentro de un componente crea un closure donde la acción tiene acceso al alcance de la función externa. Por ejemplo, la acción publish
tiene acceso a la variable publishVersion
:
export default async function Page() {
const publishVersion = await getLatestVersion();
async function publish() {
"use server";
if (publishVersion !== await getLatestVersion()) {
throw new Error('La versión ha cambiado desde que se presionó publicar');
}
...
}
return (
<form>
<button formAction={publish}>Publicar</button>
</form>
);
}
export default async function Page() {
const publishVersion = await getLatestVersion();
async function publish() {
"use server";
if (publishVersion !== await getLatestVersion()) {
throw new Error('La versión ha cambiado desde que se presionó publicar');
}
...
}
return (
<form>
<button formAction={publish}>Publicar</button>
</form>
);
}
Los closures son útiles cuando necesita capturar una instantánea de datos (ej. publishVersion
) al momento de renderizar para que pueda ser usada más tarde cuando se invoque la acción.
Sin embargo, para que esto suceda, las variables capturadas son enviadas al cliente y de vuelta al servidor cuando se invoca la acción. Para evitar que datos sensibles sean expuestos al cliente, Next.js encripta automáticamente las variables cerradas. Se genera una nueva clave privada para cada acción cada vez que se construye una aplicación Next.js. Esto significa que las acciones solo pueden ser invocadas para un build específico.
Nota importante: No recomendamos confiar únicamente en la encriptación para evitar que valores sensibles sean expuestos en el cliente. En su lugar, debe usar las APIs React taint para prevenir proactivamente que datos específicos sean enviados al cliente.
Sobrescribir claves de cifrado (avanzado)
Al alojar tu aplicación Next.js en múltiples servidores, cada instancia del servidor puede terminar con una clave de cifrado diferente, lo que puede generar inconsistencias.
Para mitigar esto, puedes sobrescribir la clave de cifrado utilizando la variable de entorno process.env.NEXT_SERVER_ACTIONS_ENCRYPTION_KEY
. Al especificar esta variable, te aseguras de que tus claves de cifrado sean persistentes entre compilaciones y que todas las instancias del servidor usen la misma clave. Esta variable debe estar cifrada con AES-GCM.
Este es un caso de uso avanzado donde un comportamiento de cifrado consistente en múltiples despliegues es crítico para tu aplicación. Debes considerar prácticas estándar de seguridad como la rotación de claves y la firma.
Nota importante: Las aplicaciones Next.js desplegadas en Vercel manejan esto automáticamente.
Orígenes permitidos (avanzado)
Dado que las Acciones de Servidor (Server Actions) pueden invocarse en un elemento <form>
, esto las expone a ataques CSRF.
Internamente, las Acciones de Servidor usan el método POST
, y solo este método HTTP está permitido para invocarlas. Esto previene la mayoría de vulnerabilidades CSRF en navegadores modernos, especialmente con las cookies SameSite siendo la configuración predeterminada.
Como protección adicional, las Acciones de Servidor en Next.js también comparan la cabecera Origin con la cabecera Host (o X-Forwarded-Host
). Si no coinciden, la solicitud será abortada. En otras palabras, las Acciones de Servidor solo pueden invocarse en el mismo host que la página que las aloja.
Para aplicaciones grandes que usan proxies inversos o arquitecturas backend multicapa (donde el API del servidor difiere del dominio de producción), se recomienda usar la opción de configuración serverActions.allowedOrigins
para especificar una lista de orígenes seguros. La opción acepta un array de strings.
/** @type {import('next').NextConfig} */
module.exports = {
experimental: {
serverActions: {
allowedOrigins: ['my-proxy.com', '*.my-proxy.com'],
},
},
}
Aprende más sobre Seguridad y Acciones de Servidor.
Recursos adicionales
Para más información, consulta la documentación de React: