//
Busque em todas as páginas da documentação
🤖 Read the SystemsArchitect.io Blog for over 100+ cloud architecture articles 🔥
Tipagem de React Server Components (RSC), componentes assíncronos e Server Actions no Next.js. Entenda como o TypeScript funciona com a fronteira servidor/cliente.
// app/users/page.tsx - Server Component Assíncrono
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("Falha ao buscar usuários");
return res.json() as Promise<User[]>;
}
export default async function UsersPage() {
const users = await getUsers();
return (
<main>
<h1>Usuários</h1>
<ul>
{users.map((user) => (
<li key={user.id}>{user.name}</li>
))}
</ul>
</main>
);
}// Server Action com params tipados
"use server";
type CreateUserInput = {
name: string;
email: string;
};
type ActionResult = {
success: boolean;
message: string;
};
export async function createUser(input: CreateUserInput): Promise<ActionResult> {
// Valida e salva no banco de dados
if (!input.name || !input.email) {
return { success: false, message: "Nome e email são obrigatórios" };
}
// ... operação de banco de dados
return { success: true, message: "Usuário criado" };
}// Client component consumindo a 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 ? "Criando..." : "Criar Usuário"}
</button>
{state?.message && <p>{state.message}</p>}
</form>
);
}async. O TypeScript permite async function Page() que retorna Promise<JSX.Element>. Isso é exclusivo de RSC -- client components não podem ser assíncronos."use server". Elas rodam no servidor, mas podem ser chamadas de client components. O TypeScript garante os tipos de parâmetro e retorno através da fronteira."use client" e "use server" criam limites de serialização. Apenas tipos serializáveis (strings, números, objetos simples, arrays, Date, FormData, etc.) podem cruzar esses limites.useActionState (React 19) substitui o antigo useFormState. Ele retorna [state, formAction, isPending] com inferência de tipo completa da função de ação.Parâmetros de página tipados (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 com 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: "Dados do formulário inválidos" };
}
// ... processar
return { success: true, message: "Enviado" };
}Componente de layout tipado:
// app/layout.tsx
type RootLayoutProps = {
children: React.ReactNode;
};
export default function RootLayout({ children }: RootLayoutProps) {
return (
<html lang="en">
<body>{children}</body>
</html>
);
}params e searchParams são tipos Promise que precisam ser aguardados (awaited). Esta é uma mudança que quebra a compatibilidade com o Next.js 14.React.FC não suporta componentes assíncronos. Use declarações de função regulares para Server Components.formData.get() retorna FormDataEntryValue | null (que é string | File | null). Sempre valide e restrinja o tipo."use server" em um arquivo de ação significa que a função roda no cliente, o que falhará se usar APIs exclusivas do servidor como acesso a banco de dados.useState, useEffect, etc.). O TypeScript não pegará isso -- é um erro de tempo de execução.| Abordagem | Prós | Contras |
|---|---|---|
| Server Components Assíncronos | Busca de dados direta, zero JS no cliente | Não pode usar hooks ou APIs do navegador |
| Server Actions | Mutações type-safe, aprimoramento progressivo | Apenas parâmetros e retornos serializáveis |
| API Route Handlers | Controle total de HTTP, qualquer cliente pode chamar | Fetch manual + tipagem no cliente |
| tRPC | Type safety de ponta a ponta | Dependência e configuração adicionais |
getServerSideProps no servidor (Pages Router) | Padrão familiar | Apenas Pages Router, sendo descontinuado |
async e retornar 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) retorna [state, formAction, isPending].useFormState com inferência de tipo completa e um flag de pendência embutido.React.FC não suporta componentes assíncronos.async function Page() { ... }.type RootLayoutProps = {
children: React.ReactNode;
};
export default function RootLayout({ children }: RootLayoutProps) {
return <html><body>{children}</body></html>;
}FormDataEntryValue | null, que é string | File | null.typeof value === "string" antes de usá-lo 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" };
}
// processar...
return { success: true, message: "Feito" };
}Revisado por Chris St. John·Última atualização: 19 de jul. de 2026
🤖 Read the SystemsArchitect.io Blog for over 100+ cloud architecture articles 🔥