Next.js
React
Server Actions
Fullstack
Web Development

Next.js 15 y acciones del servidor: la guía definitiva para las mutaciones modernas

Next.js 15 y acciones del servidor: la guía definitiva para las mutaciones modernas

El desarrollo web está en constante cambio. Hubo un tiempo en el que todo se renderizaba en el servidor (PHP, Ruby on Rails). Luego, trasladamos todo al cliente (SPA con React, Vue). Ahora, con la llegada de React Server Components (RSC) y Next.js App Router, estamos encontrando un poderoso punto medio: lo mejor de ambos mundos.

Y la joya de la corona de esta nueva era en el ecosistema React son las Acciones del servidor.

Con el lanzamiento de Next.js 15, las acciones del servidor ya no son experimentales y se han convertido en el estándar recomendado para manejar mutaciones de datos. En esta enorme guía, exploraremos cada detalle de esta función, desde los conceptos básicos hasta los estándares avanzados de seguridad y UX. Si todavía estás escribiendo pages/api/submit.ts para procesar formularios, prepárate para eliminar una gran cantidad de código repetitivo.

¿Qué son las acciones del servidor?

En términos simples, Las acciones del servidor son funciones asincrónicas que se ejecutan en el servidor, pero se pueden invocar directamente desde los componentes del cliente o del servidor.

Anteriormente, para enviar un formulario, el flujo era:

  1. Cree un componente de formulario en el cliente ("use client").
  2. Cree un estado useState para las entradas.
  3. Cree una función onSubmit que haga un fetch('/api/submit', ...).
  4. Cree un archivo de Ruta API (pages/api/submit.ts) para recibir la solicitud, validarla y guardarla en la base de datos.
  5. Manejar errores, estados de carga y revalidación de datos manualmente.

Con Acciones del servidor, el flujo es:

  1. Cree una función async que guarde en el banco.
  2. Pase esta función al apoyo action de <form>.

Fin. Next.js se encarga de la comunicación, la serialización y la ejecución. Es una llamada a procedimiento remoto (RPC) hecha correctamente para la web.

Configuración inicial en Next.js 15

En Next.js 15, las acciones del servidor ya están habilitadas de forma predeterminada. No es necesario cambiar el next.config.js.

La convención es simple:

  • Para definir acciones que se pueden importar a los componentes del Cliente, cree un archivo con la directiva "use server" en la parte superior.
  • Para acciones en línea en componentes del servidor, agregue "use server" dentro de la función.

Ejemplo básico: el "Hola mundo" de las acciones

Creemos una acción para guardar una publicación de blog. Cree un archivo src/app/actions.ts:

// src/app/actions.ts 'use server' import { db } from '@/lib/db' import { revalidatePath } from 'next/cache' export async function createPost(formData: FormData) { const title = formData.get('title') as string const content = formData.get('content') as string // Validação básica (em produção use Zod!) if (!title || !content) { throw new Error('Campos obrigatórios faltando') } // Interação direta com o banco (sem API Routes!) await db.post.create({ data: { title, content } }) // A mágica: atualiza a UI instantaneamente revalidatePath('/blog') }

Y en tu componente:

// src/components/create-post-form.tsx import { createPost } from '@/app/actions' export function CreatePostForm() { return ( <form action={createPost} className="p-4 border rounded"> <input name="title" placeholder="Título" className="border p-2 mb-2 w-full" /> <textarea name="content" placeholder="Conteúdo" className="border p-2 w-full" /> <button type="submit" className="bg-blue-500 text-white p-2 rounded"> Salvar Post </button> </form> ) }

Tenga en cuenta la ausencia de JavaScript en el cliente para el envío. Este formulario funciona incluso si el usuario desactiva JS en el navegador (mejora progresiva), aunque en las aplicaciones modernas rara vez dependemos de esto.

Gestión del estado de carga (useFormStatus)

La mala experiencia de usuario es hacer clic en "Guardar" y no recibir comentarios. Como estamos usando el accesorio HTML nativo action, no tenemos un manual useState(loading). React nos proporciona el gancho useFormStatus para esto.

Nota: useFormStatus debe usarse en un componente renderizado dentro de <form>.

// src/components/submit-button.tsx 'use client' import { useFormStatus } from 'react-dom' export function SubmitButton() { const { pending } = useFormStatus() return ( <button type="submit" disabled={pending} className="bg-blue-500 disabled:bg-gray-400 text-white p-2 rounded flex items-center gap-2" > {pending ? 'Salvando...' : 'Salvar Post'} {pending && <Spinner />} </button> ) }

Ahora simplemente use <SubmitButton /> dentro del formulario del ejemplo anterior.

Manejo de errores y comentarios (useActionState)

¿Qué pasa si la base de datos falla? ¿O validación? Simplemente arrojar un error (throw new Error) no es la mejor UX. Queremos devolver mensajes de error al formulario.

Para hacer esto, usamos el gancho useActionState (anteriormente useFormState en React experimental). Permite que la acción del servidor devuelva un valor que se actualiza en el cliente.

Refactorizando nuestra acción:

// src/app/actions.ts 'use server' export type FormState = { message: string; errors?: { title?: string[]; content?: string[]; }; } export async function createPostSafely(prevState: FormState, formData: FormData): Promise<FormState> { // Simulação de validação com Zod const validatedFields = schema.safeParse({ title: formData.get('title'), content: formData.get('content'), }); if (!validatedFields.success) { return { message: 'Erro de validação', errors: validatedFields.error.flatten().fieldErrors } } try { await db.post.create({ data: validatedFields.data }) } catch (e) { return { message: 'Erro ao salvar no banco de dados' } } revalidatePath('/blog') return { message: 'Post criado com sucesso!' } }

Y en el componente del cliente:

'use client' import { useActionState } from 'react' import { createPostSafely } from '@/app/actions' const initialState = { message: '', errors: {} } export function AdvancedForm() { const [state, formAction] = useActionState(createPostSafely, initialState) return ( <form action={formAction}> {state.message && <p className="text-red-500">{state.message}</p>} <input name="title" /> {state.errors?.title && <p className="text-red-500 text-sm">{state.errors.title[0]}</p>} {/* ... restante do form ... */} </form> ) }

Reválida de caché: el poder de revalidatePath

En el modelo anterior, después de una mutación, había que volver a buscar los datos manualmente o usar bibliotecas como React Query o SWR para invalidar los cachés.

En Next.js, esto está integrado. La función revalidatePath(path) borra el caché para esa ruta específica. En la próxima visita (o inmediatamente, si se trata de una navegación SPA), Next.js recupera los datos nuevos del servidor. También está revalidateTag(tag), que le brinda control granular si utiliza la función de búsqueda con etiquetas (fetch(url, { next: { tags: ['posts'] } })).

Actualizaciones optimistas (useOptimistic)

Para las aplicaciones que aparecen "instantáneas", no queremos esperar a que el servidor responda para actualizar la interfaz de usuario. Si agrego un elemento a la lista, quiero verlo allí ahora.

El gancho useOptimistic lo permite de forma elegante.

'use client' import { useOptimistic } from 'react' export function PostList({ posts }: { posts: Post[] }) { const [optimisticPosts, addOptimisticPost] = useOptimistic( posts, (state, newPost: Post) => [newPost, ...state] ); async function action(formData: FormData) { const title = formData.get('title') as string; // Atualiza a UI imediatamente addOptimisticPost({ id: Math.random(), title, content: '...' }); // Chama a Server Action real await createPost(formData); } return ( <div> <form action={action}>...</form> <ul> {optimisticPosts.map(post => ( <li key={post.id}>{post.title}</li> ))} </ul> </div> ) }

Si la acción del servidor falla, React revierte automáticamente el estado optimista al estado real anterior. Es robusto y mágico.

Seguridad: El elefante en la habitación

Muchos desarrolladores miran "use server" y piensan: "Espera, ¿estoy exponiendo mi base de datos al cliente?"

No. El código de acción del servidor nunca se envía al navegador. Solo se expone la "firma" de la función (una URL de referencia interna de Next.js). Sin embargo, es fundamental tratar las acciones del servidor como puntos de entrada públicos a su API.

Lista de verificación de seguridad obligatoria:

  1. Autenticación: siempre verifique quién llama a la acción. No asuma que el usuario inició sesión solo porque el botón "Guardar" estaba visible.

    import { auth } from '@/auth' // Auth.js ou Clerk export async function deletePost(id: string) { const session = await auth() if (!session?.user) throw new Error('Não autorizado') // ... }
  2. Autorización: El usuario ha iniciado sesión, pero ¿puede *eliminar esta publicación?

    const post = await db.post.findUnique({ where: { id } }) if (post.authorId !== session.user.id) throw new Error('Proibido')
  3. Validación de entrada: nunca confíes en FormData ni en los argumentos pasados. Utilice Zod para asegurarse de que los datos estén en el formato correcto.

Acciones del servidor frente a rutas API

Después de todo, ¿están muertas las rutas API?

No exactamente, pero su uso ha disminuido drásticamente.

Utilice Acciones del servidor cuando:

  • Se trata de mutaciones de forma.
  • Quiere llamar a una función del servidor desde un evento en el cliente (onClick).
  • Quiere TypeScript escritura de un extremo a otro sin generar SDK.

Utilice Rutas API (controladores de rutas) cuando:

  • Debe exponer una [REST API47 pública a terceros (webhooks, aplicaciones móviles).
  • Necesita características específicas del protocolo HTTP que ocultan las abstracciones de React (por ejemplo, transmisión de archivos binarios personalizados, encabezados complejos).

Conclusión

Next.js 15 consolida Server Actions como uno de los cambios más productivos en el desarrollo web reciente. Eliminan la capa intermedia del "código adhesivo" (rutas API, captadores, administradores de estado) y le permiten concentrarse en lo que importa: lógica empresarial e interfaz de usuario.

Al combinar Server Actions con useActionState, useOptimistic y la validación con Zod, crea aplicaciones sólidas y seguras con una experiencia de usuario de clase mundial, escribiendo una fracción del código que habría escrito hace 3 años.

El futuro está del lado del servidor, pero la experiencia está del lado del cliente. Y las acciones del servidor son el puente entre ellos.


¿Ya migraste tus formularios a Server Actions? ¿Cuál fue el mayor desafío? ¡Deja tu comentario!

Lea también