//
Busca en todas las páginas de la documentación
🤖 Read the SystemsArchitect.io Blog for over 100+ cloud architecture articles 🔥
Tipifica React Server Components (RSC), componentes async y Server Actions en Next.js. Entiende cómo TypeScript funciona con el límite servidor/cliente.
// app/users/page.tsx - Componente de Servidor Async
type User = {
id: string;
name: string;
email: string;
};
async function getUsers(): Promise<User[]> {
const res = await fetch("https://api.example.com/users", {
cache: "force-cache",
});
if (!res.ok) throw new Error("Error al obtener usuarios");
return res.json() as Promise<User[]>;
}
export default async function UsersPage() {
const users = await getUsers();
return (
<main>
<h1>Usuarios</h1>
<ul>
{users.map((user) => (
<li key={user.id}>{user.name}</li>
))}
</ul>
</main>
);
}// Server Action con params tipificados
"use server";
type CreateUserInput = {
name: string;
email: string;
};
type ActionResult = {
success: boolean;
message: string;
};
export async function createUser(input: CreateUserInput): Promise<ActionResult> {
// Valida y guarda en la base de datos
if (!input.name || !input.email) {
return { success: false, message: "Nombre y email son requeridos" };
}
// ... operación de base de datos
return { success: true, message: "Usuario creado" };
}// Componente de cliente consumiendo el Server Action
"use client";
import { useActionState } from "react";
import { createUser } from "./actions";
export function CreateUserForm() {
const [state, formAction, isPending] = useActionState(
async (_prevState: ActionResult | null, formData: FormData) => {
const result = await createUser({
name: formData.get("name") as string,
email: formData.get("email") as string,
});
return result;
},
null
);
return (
<form action={formAction}>
<input name="name" required />
<input name="email" type="email" required />
<button type="submit" disabled={isPending}>
{isPending ? "Creando..." : "Crear Usuario"}
</button>
{state?.message && <p>{state.message}</p>}
</form>
);
}async. TypeScript permite async function Page() que devuelve Promise<JSX.Element>. Esto es único para RSC -- los componentes de cliente no pueden ser async."use server". Se ejecutan en el servidor pero pueden ser llamados desde componentes de cliente. TypeScript refuerza los tipos de parámetros y retorno a través del límite."use client" y "use server" crean límites de serialización. Solo los tipos serializables (strings, números, objetos simples, arrays, Date, FormData, etc.) pueden cruzar estos límites.useActionState (React 19) reemplaza el antiguo useFormState. Devuelve [state, formAction, isPending] con inferencia completa de tipos desde la función de acción.Params de página tipificados (Next.js 15):
// app/users/[id]/page.tsx
type PageProps = {
params: Promise<{ id: string }>;
searchParams: Promise<{ [key: string]: string | string[] | undefined }>;
};
export default async function UserPage({ params, searchParams }: PageProps) {
const { id } = await params;
const { tab } = await searchParams;
const user = await getUser(id);
return <div>{user.name}</div>;
}Server Action con FormData:
"use server";
export async function submitForm(formData: FormData): Promise<ActionResult> {
const name = formData.get("name");
const email = formData.get("email");
if (typeof name !== "string" || typeof email !== "string") {
return { success: false, message: "Datos de formulario inválidos" };
}
// ... procesa
return { success: true, message: "Enviado" };
}Componente de layout tipificado:
// app/layout.tsx
type RootLayoutProps = {
children: React.ReactNode;
};
export default function RootLayout({ children }: RootLayoutProps) {
return (
<html lang="es">
<body>{children}</body>
</html>
);
}params y searchParams son tipos Promise que deben ser awaited. Esto es un cambio importante respecto a Next.js 14.React.FC no soporta componentes async. Usa declaraciones de función simples para Server Components.formData.get() devuelve FormDataEntryValue | null (que es string | File | null). Siempre valida y estrecha el tipo."use server" en un archivo de acción significa que la función se ejecuta en el cliente, lo que fallará si usa APIs solo del servidor como acceso a base de datos.useState, useEffect, etc.). TypeScript no atrapará esto -- es un error en tiempo de ejecución.| Enfoque | Pros | Contras |
|---|---|---|
| Server Components Async | Obtención de datos directa, cero JS de cliente | No pueden usar hooks o APIs del navegador |
| Server Actions | Mutaciones con tipo seguro, mejora progresiva | Solo parámetros y retornos serializables |
| Manejadores de Rutas de API | Control HTTP completo, cualquier cliente puede llamar | Fetch manual + tipificación en cliente |
| tRPC | Seguridad de tipos de extremo a extremo | Dependencia adicional y setup |
getServerSideProps del lado del servidor (Pages Router) | Patrón familiar | Solo Pages Router, siendo descontinuado |
async y devolver Promise<JSX.Element>.type PageProps = {
params: Promise<{ id: string }>;
searchParams: Promise<{ [key: string]: string | string[] | undefined }>;
};
export default async function Page({ params, searchParams }: PageProps) {
const { id } = await params;
}useActionState (React 19) devuelve [state, formAction, isPending].useFormState con inferencia completa de tipos y una bandera de pendencia integrada.React.FC no soporta componentes async.async function Page() { ... }.type RootLayoutProps = {
children: React.ReactNode;
};
export default function RootLayout({ children }: RootLayoutProps) {
return <html><body>{children}</body></html>;
}FormDataEntryValue | null, que es string | File | null.typeof value === "string" antes de usarlo como string."use server";
export async function submitForm(formData: FormData): Promise<ActionResult> {
const name = formData.get("name");
if (typeof name !== "string") {
return { success: false, message: "Inválido" };
}
// procesa...
return { success: true, message: "Hecho" };
}Revisado por Chris St. John·Última actualización: 19 jul 2026
🤖 Read the SystemsArchitect.io Blog for over 100+ cloud architecture articles 🔥