Pular para o conteúdo
TypeScript

Tipar getServerSideProps no Next.js 2026

getServerSideProps sem tipagem vira um campo minado de any. Veja como tipar props, context, redirect e notFound corretamente no Pages Router do Next.js.

Por que isso é importante

Tipar getServerSideProps no Next.js 2026. getServerSideProps sem tipagem vira um campo minado de any. Veja como tipar props, context, redirect e notFound corretamente no Pages Router do Next.js.

O problema: props sem tipo

Quando você exporta getServerSideProps sem tipagem, o Next.js passa as props pro componente como any. Você acessa props.user.nome (errado, é props.user.name) e o TypeScript não reclama. O erro só aparece no browser.

// SEM tipagem - perigoso
export async function getServerSideProps(context) {
  // context é any
  const data = await fetch('...');
  return { props: { user: data } };
}

export default function Page(props) {
  // props é any
  return <h1>{props.user.nome}</h1>; // typo! deveria ser .name
  // Sem erro em compile time
}

Solução 1: GetServerSideProps generic

A forma mais direta é usar o tipo GetServerSideProps do Next.js como generic, passando a interface das props. Isso tipa o retorno e garante que o componente receba os campos certos.

import { GetServerSideProps } from 'next';

// Define a interface das props
interface UserPageProps {
  user: {
    id: string;
    name: string;
    email: string;
  };
  lastUpdated: string;
}

// Tipa o getServerSideProps com o generic
export const getServerSideProps: GetServerSideProps<UserPageProps> = async (context) => {
  // context agora é GetServerSidePropsContext - tipado!
  const { params, query, req, res } = context;

  const response = await fetch(`https://api.example.com/users/${params?.id}`);
  const user = await response.json();

  return {
    props: {
      user: {
        id: user.id,
        name: user.name,
        email: user.email,
      },
      lastUpdated: new Date().toISOString(),
    },
  };
};

// Componente com props tipadas
export default function UserPage({ user, lastUpdated }: UserPageProps) {
  return (
    <div>
      <h1>{user.name}</h1>
      <p>{user.email}</p>
      <small>Atualizado em: {lastUpdated}</small>
    </div>
  );
}

Com GetServerSideProps, o TypeScript garante que o objeto retornado em props bate exatamente com a interface. Se faltar ou sobrar campo, erro em compile time.

Solução 2: InferGetServerSidePropsType (zero duplicação)

Se você não quer manter uma interface separada, dá pra usar InferGetServerSidePropsType pra inferir o tipo das props direto da função. Assim o tipo fica sincronizado automaticamente.

import { GetServerSideProps, InferGetServerSidePropsType } from 'next';

export const getServerSideProps = (async (context) => {
  const res = await fetch('https://api.example.com/posts');
  const posts = (await res.json()) as Array<{
    id: string;
    title: string;
    content: string;
  }>;

  return {
    props: {
      posts,
      totalCount: posts.length,
      fetchedAt: new Date().toISOString(),
    },
  };
}) satisfies GetServerSideProps;

// Tipo inferido automaticamente!
export default function PostsPage(
  props: InferGetServerSidePropsType<typeof getServerSideProps>
) {
  // props.posts é Array<{ id: string; title: string; content: string }>
  // props.totalCount é number
  // props.fetchedAt é string
  return (
    <div>
      <h1>{props.totalCount} posts</h1>
      {props.posts.map(post => (
        <article key={post.id}>
          <h2>{post.title}</h2>
          <p>{post.content}</p>
        </article>
      ))}
    </div>
  );
}

O satisfies GetServerSideProps garante que a função tem o formato certo, e InferGetServerSidePropsType extrai o tipo das props automaticamente. Zero duplicação, zero chance de sair de sincronia.

Tipando o context: params, query e req

O context do getServerSideProps tem vários campos úteis. Dá pra tipar os params e query pra ter controle sobre o que vem da URL.

import { GetServerSideProps, GetServerSidePropsContext } from 'next';
import { ParsedUrlQuery } from 'querystring';

// Tipo customizado pros params da URL
interface PostParams extends ParsedUrlQuery {
  slug: string;
}

// Passa o tipo dos params como segundo generic
export const getServerSideProps: GetServerSideProps<PostProps, PostParams> = async (context) => {
  const { slug } = context.params!; // slug é string
  const { page } = context.query;   // page é string | string[] | undefined

  // Headers e cookies do request
  const authToken = context.req.headers.authorization;
  const cookies = context.req.cookies;
  const userAgent = context.req.headers['user-agent'];

  // Locale e resolvedUrl
  const locale = context.locale;         // string | undefined
  const url = context.resolvedUrl;       // string

  const post = await getPostBySlug(slug);

  return {
    props: {
      post,
      currentPage: Number(page) || 1,
    },
  };
};

Redirect e notFound tipados

O getServerSideProps pode retornar redirect ou notFound em vez de props. O TypeScript lida com isso via union type no retorno. A tipagem garante que você retorne a estrutura correta.

import { GetServerSideProps } from 'next';

interface DashboardProps {
  user: { id: string; name: string; role: string };
  stats: { totalOrders: number; revenue: number };
}

export const getServerSideProps: GetServerSideProps<DashboardProps> = async (context) => {
  const token = context.req.cookies['auth-token'];

  // Sem token: redireciona pro login
  if (!token) {
    return {
      redirect: {
        destination: '/login?callbackUrl=/dashboard',
        permanent: false, // 307 temporário
      },
    };
  }

  const user = await getUserFromToken(token);

  // Usuário não encontrado: 404
  if (!user) {
    return { notFound: true };
  }

  // Sem permissão: redireciona
  if (user.role !== 'admin') {
    return {
      redirect: {
        destination: '/unauthorized',
        permanent: false,
      },
    };
  }

  const stats = await getDashboardStats(user.id);

  return {
    props: {
      user: { id: user.id, name: user.name, role: user.role },
      stats,
    },
  };
};

Galera, o redirect usa permanent: false pra 307 (temporário) e permanent: true pra 308 (permanente). Escolha certo pro SEO: se o redirecionamento é definitivo, use true.

Padrão avançado: helper tipado reutilizável

Se várias páginas compartilham lógica no getServerSideProps (autenticação, por exemplo), crie um wrapper tipado que encapsula o padrão.

import { GetServerSideProps, GetServerSidePropsContext, GetServerSidePropsResult } from 'next';

interface AuthUser {
  id: string;
  name: string;
  role: string;
}

type AuthenticatedHandler<P extends Record<string, unknown>> = (
  context: GetServerSidePropsContext,
  user: AuthUser
) => Promise<GetServerSidePropsResult<P>>;

// Wrapper que garante autenticação
function withAuth<P extends Record<string, unknown>>(
  handler: AuthenticatedHandler<P>
): GetServerSideProps<P> {
  return async (context) => {
    const token = context.req.cookies['auth-token'];

    if (!token) {
      return {
        redirect: { destination: '/login', permanent: false },
      };
    }

    const user = await verifyToken(token);
    if (!user) {
      return {
        redirect: { destination: '/login', permanent: false },
      };
    }

    return handler(context, user);
  };
}

// Uso: limpo e tipado
interface ProfileProps {
  user: AuthUser;
  orders: Array<{ id: string; total: number }>;
}

export const getServerSideProps = withAuth<ProfileProps>(async (context, user) => {
  const orders = await getOrders(user.id);
  return {
    props: { user, orders },
  };
});

Erros comuns ao tipar getServerSideProps

Atenção

Erro 1: Retornar Date ou funções nas props. getServerSideProps serializa pra JSON. Date vira string, funções desaparecem. Converta datas pra ISO string antes de retornar.

Erro 2: Esquecer o ! em context.params quando a rota é dinâmica. O params pode ser undefined em rotas não-dinâmicas, mas numa rota [slug] ele sempre existe. Use non-null assertion ou tipagem de params.

Erro 3: Duplicar tipos entre getServerSideProps e componente. Use InferGetServerSidePropsType ou uma interface compartilhada. Nunca copie/cole a interface.

Erro 4: Não tratar erros da API no getServerSideProps. Se o fetch falha, o servidor retorna 500 genérico. Sempre envolva em try/catch e retorne notFound ou redirect em caso de erro.

Passo a passo: getServerSideProps tipado

  1. Defina a interface das props que o componente vai receber
  2. Tipe getServerSideProps com GetServerSideProps<Props>
  3. Se tiver params dinâmicos, passe a interface no segundo generic
  4. Tipe o componente com a mesma interface ou use InferGetServerSidePropsType
  5. Trate todos os caminhos: props, redirect e notFound
  6. Envolva chamadas externas em try/catch
  7. Converta Date pra string antes de retornar nas props

Checklist: getServerSideProps tipado

Checklist: SSR + TypeScript

  • GetServerSideProps tipado com generic das props
  • Params tipados com interface que extends ParsedUrlQuery
  • InferGetServerSidePropsType usado no componente (ou interface compartilhada)
  • Redirect e notFound retornados quando necessário
  • Datas convertidas pra string (JSON serializable)
  • Try/catch em todas as chamadas externas
  • Wrapper withAuth criado se autenticação é compartilhada
  • Context (req, query, locale) tipado e usado corretamente

TypeScript Profissional: Projeto Completo

No CrazyStack você constrói um projeto completo com TypeScript

Node.js e React. SSR tipado

APIs seguras e deploy otimizado. Acesse:

/comprar?src=blog-como-tipar-getserversideprops-nextjs-typescript
]
}
]
}
]
}