Alternar tema

Configuração avançada de TypeScript no Next.js: otimize o tsconfig e aumente a segurança de tipos

Easton editorial illustration: server-client bridge

No relatório de testes, uma linha vermelha chamava atenção: Production Error: Cannot read property 'id' of undefined. Um usuário avisou que a página ficava completamente em branco ao clicar na área de perfil. Ao voltar ao código, encontrei o problema: a rota estava escrita como /users/profile, e não /user/profile. Havia um s a mais. O TypeScript não avisou nada, a IDE também não mostrou erro, e assim o bug chegou à produção.

Não parece um erro básico? Sim, parece. Mas esse tipo de “erro básico” aparece com uma frequência desanimadora nos projetos que mantenho. Rota digitada incorretamente, nome de variável de ambiente errado, parâmetros de função todos definidos como any… O TypeScript promete “segurança de tipos”, mas, na prática, o desenvolvimento às vezes não parece muito diferente de usar JavaScript.

Depois percebi que o problema não era o TypeScript, e sim a minha configuração ruim. O tsconfig.json estava cheio de opções que eu não sabia se deveria ativar ou desativar. Os tutoriais na internet se contradiziam: alguns diziam que o modo strict aumentava demais o trabalho; outros, que usar TypeScript sem ele não fazia sentido. Depois de quase um ano com Next.js + TypeScript, o projeto ainda estava repleto de any.

Neste artigo, compartilho o que aprendi depois de um ano lidando com esses problemas. Vamos da otimização do tsconfig à implementação de rotas type-safe e à tipagem de variáveis de ambiente, transformando o TypeScript de “obstáculo” em “guardião”. Nada de teoria abstrata: apenas técnicas que você pode aplicar de verdade.

Como otimizar o tsconfig e criar uma base sólida

O verdadeiro significado do modo strict

Muita gente — inclusive eu, no passado — acha que strict: true é apenas um interruptor: basta ativá-lo para o TypeScript ficar mais rigoroso. Mas não é bem assim.

Ao consultar a documentação oficial do TypeScript, você descobre que strict funciona como um atalho para sete opções do compilador:

{
  "compilerOptions": {
    "strict": true,
    // Equivale a definir como true todas as sete opções abaixo
    "strictNullChecks": true,        // Verificação rigorosa de valores nulos
    "strictFunctionTypes": true,     // Verificação rigorosa de tipos de função
    "strictBindCallApply": true,     // Verificação rigorosa de bind/call/apply
    "strictPropertyInitialization": true, // Inicialização rigorosa de propriedades
    "noImplicitAny": true,          // Proíbe any implícito
    "noImplicitThis": true,         // Proíbe this implícito
    "alwaysStrict": true            // Sempre analisa no modo strict
  }
}

As três primeiras são as mais úteis. Comecemos por strictNullChecks: quando essa opção está ativa, o TypeScript trata null e undefined como tipos independentes, e não como “valores válidos para qualquer tipo”.

Veja um exemplo. Imagine que você consulta os dados de um usuário no banco:

// Sem strictNullChecks
const user = await db.user.findOne({ id: userId })
console.log(user.name) // O TypeScript não reclama, mas user pode ser null

// Depois de ativar
const user = await db.user.findOne({ id: userId })
console.log(user.name) // ❌ Erro do TypeScript: o objeto pode ser null

// É preciso escrever assim
if (user) {
  console.log(user.name) // ✅ Válido
}

Na primeira vez que ativei essa opção em um projeto antigo, mais de 200 sublinhados vermelhos apareceram de uma vez na IDE. Entrei em pânico e quase voltei atrás. Depois, olhando com calma, percebi que aqueles “erros” eram, na verdade, bugs em potencial: os trechos sem verificação de valor nulo realmente poderiam falhar em produção.

noImplicitAny também é essencial. Essa opção impede que parâmetros de funções ou variáveis se tornem any “implicitamente”:

// Sem noImplicitAny
function handleData(data) {  // data se torna any automaticamente
  return data.value  // Nenhuma operação gera erro
}

// Depois de ativar
function handleData(data) {  // ❌ Erro: o parâmetro tem tipo any implícito
  return data.value
}

// É preciso declarar o tipo explicitamente
function handleData(data: { value: string }) {  // ✅
  return data.value
}

Sinceramente, no começo isso parece trabalhoso. Antes, bastava escrever uma função rapidamente; agora é preciso definir os tipos. Depois de algum tempo, porém, você percebe que as sugestões da IDE ficam muito mais inteligentes: assim que digita data., todas as propriedades aparecem, sem precisar consultar a documentação.

Configurações de TypeScript específicas do Next.js

O tsconfig.json de um projeto Next.js tem algumas configurações particulares. Abaixo está a versão de boas práticas que uso atualmente:

{
  "compilerOptions": {
    // Configurações básicas
    "target": "ES2020",
    "lib": ["dom", "dom.iterable", "esnext"],
    "jsx": "preserve",
    "module": "esnext",
    "moduleResolution": "bundler",

    // Obrigatório no Next.js
    "allowJs": true,
    "noEmit": true,
    "esModuleInterop": true,
    "isolatedModules": true,
    "resolveJsonModule": true,

    // Modo strict (essencial)
    "strict": true,
    "skipLibCheck": true,

    // Otimização de desempenho
    "incremental": true,

    // Plugin do Next.js
    "plugins": [
      {
        "name": "next"
      }
    ],

    // Aliases de caminho
    "paths": {
      "@/*": ["./src/*"],
      "@/components/*": ["./src/components/*"],
      "@/lib/*": ["./src/lib/*"],
      "@/styles/*": ["./src/styles/*"]
    }
  },
  "include": [
    "next-env.d.ts",
    "**/*.ts",
    "**/*.tsx",
    ".next/types/**/*.ts"
  ],
  "exclude": ["node_modules"]
}

Vale destacar alguns itens fáceis de ignorar:

1. incremental: compilação incremental

60%
aumento na velocidade de compilação

Essa opção pode acelerar bastante a compilação de projetos grandes. Quando ela está ativa, o TypeScript armazena em cache as informações da última compilação e, na próxima, compila apenas os arquivos alterados. Em um projeto com mais de 300 componentes, medi uma redução de aproximadamente 45 para 18 segundos. A diferença foi bem clara.

2. paths: aliases de caminho

Antes, os imports eram escritos assim:

import Button from '../../../components/ui/Button'
import { formatDate } from '../../../../lib/utils'

Era difícil até contar quantos .. havia ali, e qualquer pequena mudança na estrutura de pastas quebrava tudo.

Depois de configurar os aliases:

import Button from '@/components/ui/Button'
import { formatDate } from '@/lib/utils'

Fica muito mais limpo. Além disso, o TypeScript consegue inferir os tipos corretamente, e a navegação da IDE continua funcionando.

3. plugins: plugin do Next.js

Embora pareça simples, "plugins": [{ "name": "next" }] ajuda o TypeScript a compreender recursos específicos do Next.js, como os tipos de arquivos especiais layout.tsx e page.tsx no diretório app, além da diferença entre componentes de servidor e de cliente.

Sem esse plugin, o TypeScript pode apresentar erros de tipo incorretos ao escrever componentes de servidor.

Como ativar o modo strict gradualmente

Se o seu projeto já está em andamento e tem bastante código, ativar strict: true de uma vez pode realmente ser doloroso. Minha sugestão é não tentar resolver tudo de uma vez.

Estratégia 1: código novo rigoroso, código antigo corrigido aos poucos

Mantenha strict: true no tsconfig.json, mas, nos arquivos antigos que ainda não podem ser corrigidos, adicione temporariamente no topo:

// @ts-nocheck  // Ignora a verificação de tipos do arquivo inteiro

Ou, para uma linha específica:

// @ts-ignore  // Ignora o erro de tipo da próxima linha

É importante observar a diferença entre @ts-ignore e @ts-expect-error:

// @ts-ignore
const x = 1 as any  // Não reclama mesmo que a próxima linha não tenha erro

// @ts-expect-error
const y = 1  // Se não houver erro na próxima linha, o TypeScript avisa sobre o comentário desnecessário

Prefiro @ts-expect-error, pois ele evita que você “esqueça de remover o comentário”: depois que o bug é corrigido, o TypeScript avisa que aquela anotação já não é necessária.

Estratégia 2: ativar por módulo funcional

Você pode, por exemplo, começar corrigindo todos os arquivos do diretório components e manter temporariamente regras menos rigorosas nos demais. Uma configuração possível é:

// tsconfig.strict.json (modo strict)
{
  "extends": "./tsconfig.json",
  "compilerOptions": {
    "strict": true
  },
  "include": ["src/components/**/*"]
}

No desenvolvimento cotidiano, use o tsconfig.json normal; ao refatorar um módulo, mude para a versão strict.

No fim das contas, o modo strict não existe para dificultar a vida. Certa vez, ao refatorar um componente antigo, ativei strictNullChecks e encontrei cinco verificações de valor nulo ausentes. Três delas já tinham causado erros em produção, mas as exceções estavam sendo engolidas por um try-catch e nunca ficaram visíveis. Naquele momento, aqueles sublinhados vermelhos começaram a parecer bem simpáticos.

Como implementar rotas type-safe e eliminar erros de digitação

Typed Routes nativo do Next.js

Lembra do bug em produção citado no início do artigo? Um s a mais na rota levou a página a um erro 404. Esse tipo de problema pode ser totalmente evitado.

O Next.js 13 introduziu um recurso experimental chamado typedRoutes. Quando ele está ativo, o TypeScript gera definições de tipo para todas as rotas do projeto.

Como ativar?

Adicione uma linha ao next.config.ts:

// next.config.ts
import type { NextConfig } from 'next'

const nextConfig: NextConfig = {
  experimental: {
    typedRoutes: true,  // Ativa rotas type-safe
  },
}

export default nextConfig

Em seguida, reinicie o servidor de desenvolvimento (npm run dev). O Next.js examinará automaticamente o diretório app e gerará as definições de tipo das rotas na pasta .next/types.

Como isso funciona na prática?

Suponha que o projeto tenha esta estrutura:

app/
├── page.tsx           // Página inicial
├── blog/
│   ├── page.tsx      // Lista do blog
│   └── [slug]/
│       └── page.tsx  // Detalhes do artigo
└── user/
    └── [id]/
        └── profile/
            └── page.tsx  // Perfil do usuário

Depois de ativar typedRoutes, a IDE oferece preenchimento automático ao escrever rotas no componente Link e no useRouter:

import Link from 'next/link'

export default function Nav() {
  return (
    <nav>
      <Link href="/">Início</Link>
      <Link href="/blog">Blog</Link>
      <Link href="/blog/hello-world">Detalhes do artigo</Link>
      <Link href="/user/123/profile">Perfil</Link>

      {/* ❌ Erro do TypeScript: a rota não existe */}
      <Link href="/users/123/profile" />  // Observe que aqui está users, não user
    </nav>
  )
}

Ao digitar href="/, a IDE exibe todas as rotas disponíveis. Qualquer erro de digitação é destacado imediatamente.

Sem exagero: quando experimentei esse recurso pela primeira vez, pensei apenas “isso é bom demais”.

Limitações

O recurso ainda tem algumas limitações:

  1. Funciona apenas com o App Router: se o projeto ainda usa o diretório pages, o recurso não está disponível
  2. Parâmetros de rotas dinâmicas precisam ser informados manualmente: em /blog/[slug], por exemplo, ainda é necessário interpolar o valor de slug
  3. Os parâmetros de consulta não são verificados: o parâmetro tab em /user?tab=settings não passa por verificação de tipo

Em resumo, ele garante que o próprio caminho não tenha erros, mas os valores dos parâmetros ainda dependem de você.

Biblioteca de terceiros: nextjs-routes

Se você ainda usa o diretório pages ou quer uma segurança de tipos mais completa para as rotas, incluindo parâmetros de consulta, vale experimentar a biblioteca nextjs-routes.

Instalação e configuração:

npm install nextjs-routes

Depois, adicione ao next.config.ts:

const nextRoutes = require('nextjs-routes/config')

const nextConfig = nextRoutes({
  // Sua configuração original do Next.js
})

export default nextConfig

Como usar:

A biblioteca gera uma função route, que permite definir as rotas por meio de objetos:

import { route } from 'nextjs-routes'

// Objeto de rota type-safe
const profileRoute = route({
  pathname: '/user/[id]/profile',
  query: {
    id: '123',
    tab: 'settings',  // Os parâmetros de consulta também têm verificação de tipo
  }
})

router.push(profileRoute)  // Totalmente type-safe

// Se o caminho estiver incorreto
const wrongRoute = route({
  pathname: '/users/[id]/profile',  // ❌ Erro do TypeScript: o caminho não existe
})

Em comparação com a solução nativa do Next.js, nextjs-routes oferece estas vantagens:

  • Compatibilidade com o diretório pages
  • Verificação de tipos nos parâmetros de consulta
  • Definição de rotas por meio de objetos, sem precisar interpolar strings manualmente

O ponto negativo é a dependência adicional. Além disso, sempre que a estrutura das rotas muda, os arquivos de tipos precisam ser gerados novamente — embora esse processo seja automático.

Inferência de tipos nos parâmetros de rota

E os parâmetros das rotas dinâmicas? Em app/blog/[slug]/page.tsx, por exemplo, qual é o tipo do parâmetro slug?

O Next.js gera automaticamente o tipo de params:

// app/blog/[slug]/page.tsx
export default function BlogPost({
  params,
}: {
  params: { slug: string }
}) {
  return <h1>Artigo: {params.slug}</h1>
}

O problema é que slug continua sendo apenas uma string, então qualquer texto pode ser fornecido. Se você quiser mais rigor — aceitando somente um formato específico de slug, por exemplo — use zod para fazer a validação em tempo de execução:

import { z } from 'zod'

const slugSchema = z.string().regex(/^[a-z0-9-]+$/)

export default function BlogPost({
  params,
}: {
  params: { slug: string }
}) {
  // Valida o formato do slug
  const validatedSlug = slugSchema.parse(params.slug)

  return <h1>Artigo: {validatedSlug}</h1>
}

Se o slug não estiver no formato esperado — por exemplo, se contiver letras maiúsculas ou caracteres especiais — o zod lançará um erro.

Essa técnica é especialmente útil em rotas de API. Afinal, você não controla o que o usuário envia, e validar antes é sempre melhor do que descobrir o problema em produção.

Como tipar variáveis de ambiente e eliminar any de vez

A origem do problema

O suporte padrão do TypeScript a variáveis de ambiente é bastante limitado.

Você certamente já escreveu algo assim:

const apiKey = process.env.API_KEY

Ao passar o mouse sobre apiKey, o tipo exibido é string | undefined. Pelo menos o TypeScript sabe que o valor pode ser undefined.

Mas um caso ainda mais comum é este:

const apiUrl = process.env.NEXT_PUBLIC_API_URL
console.log(apiUrl.toUpperCase())  // Falha em tempo de execução: apiUrl is undefined

O TypeScript não acusa erro; você só descobre durante a execução que a variável de ambiente não foi configurada.

E, se o nome da variável estiver digitado incorretamente, o TypeScript também não percebe:

const key = process.env.API_SECRE  // Faltou o T
// TypeScript: tudo bem, sou apenas string | undefined

Isso é frustrante. Mesmo usando TypeScript, ainda é necessário conferir os nomes das variáveis visualmente, como se o código fosse JavaScript puro.

Solução com T3 Env (recomendada)

Atualmente, a solução mais reconhecida pela comunidade para esse problema é o T3 Env. Ele oferece verificação de tipos e validação em tempo de execução ao mesmo tempo.

Instalação:

npm install @t3-oss/env-nextjs zod

Configuração:

Crie env.mjs — ou env.ts — na raiz do projeto:

import { createEnv } from "@t3-oss/env-nextjs"
import { z } from "zod"

export const env = createEnv({
  // Variáveis de ambiente do servidor (não podem ser acessadas no cliente)
  server: {
    DATABASE_URL: z.string().url(),
    API_SECRET: z.string().min(32),
    SMTP_HOST: z.string().min(1),
  },

  // Variáveis de ambiente do cliente (devem começar com NEXT_PUBLIC_)
  client: {
    NEXT_PUBLIC_APP_URL: z.string().url(),
    NEXT_PUBLIC_ANALYTICS_ID: z.string().optional(),
  },

  // Mapeamento das variáveis de ambiente em tempo de execução
  runtimeEnv: {
    DATABASE_URL: process.env.DATABASE_URL,
    API_SECRET: process.env.API_SECRET,
    SMTP_HOST: process.env.SMTP_HOST,
    NEXT_PUBLIC_APP_URL: process.env.NEXT_PUBLIC_APP_URL,
    NEXT_PUBLIC_ANALYTICS_ID: process.env.NEXT_PUBLIC_ANALYTICS_ID,
  },
})

Uso:

import { env } from './env.mjs'

// ✅ Totalmente type-safe, com preenchimento automático
const dbUrl = env.DATABASE_URL  // string
const appUrl = env.NEXT_PUBLIC_APP_URL  // string

// ❌ Erro do TypeScript: nome digitado incorretamente
const wrong = env.DATABASE_UR

// ❌ Erro do TypeScript: o cliente não pode acessar variáveis do servidor
// Em um componente de cliente
'use client'
const secret = env.API_SECRET  // Erro de compilação

As melhores partes:

  1. Validação na inicialização: se uma variável de ambiente estiver ausente ou em um formato incorreto, o aplicativo apresenta um erro ao iniciar, e não apenas durante a execução
  2. Inferência de tipos: todas as variáveis de ambiente têm tipos exatos, em vez de string | undefined
  3. Proteção contra vazamentos: o acesso a uma variável do servidor no código do cliente gera um erro de compilação

Antes de usar o T3 Env, era comum o serviço não iniciar no ambiente de testes porque alguém tinha esquecido de configurar uma variável. Eu sempre precisava examinar os logs para descobrir qual estava faltando. Agora o problema aparece na inicialização, o que economiza bastante tempo.

Solução com arquivo de declaração de tipos personalizado

Se você não quiser adicionar o T3 Env ou se o projeto for pequeno, também pode estender manualmente o tipo ProcessEnv:

// env.d.ts
namespace NodeJS {
  interface ProcessEnv {
    // Variáveis do servidor
    DATABASE_URL: string
    API_SECRET: string
    SMTP_HOST: string

    // Variáveis do cliente
    NEXT_PUBLIC_APP_URL: string
    NEXT_PUBLIC_ANALYTICS_ID?: string  // Variáveis opcionais usam ?
  }
}

Assim, o TypeScript conhece os tipos dessas variáveis:

const dbUrl = process.env.DATABASE_URL  // string
const apiSecret = process.env.API_SECRET  // string

// ❌ Erro do TypeScript
const wrong = process.env.DATABASE_UR  // A propriedade 'DATABASE_UR' não existe

Desvantagens:

  • Não há validação em tempo de execução; você só descobre que uma variável está ausente durante a execução
  • Não impede que o cliente acesse variáveis do servidor
  • Exige manutenção manual das definições de tipo

Essa abordagem serve para projetos pequenos ou cenários com exigências menores de segurança de tipos. Mas, sinceramente, se o projeto já usa TypeScript, recomendo adotar o T3 Env desde o início.

Como aplicar o modo strict do TypeScript na prática

Como lidar com problemas de tipos em bibliotecas de terceiros

Às vezes, o problema não está no seu código, e sim em uma biblioteca de terceiros sem definições de tipo ou com definições incorretas.

Caso 1: a biblioteca não tem nenhuma definição de tipo

Imagine que você usa um pacote npm antigo e o import inteiro vira any:

import oldLib from 'some-old-lib'  // any

Primeiro, pesquise no npm se existe @types/some-old-lib:

npm install -D @types/some-old-lib

Se não existir, você precisará escrever a declaração. Crie types/some-old-lib.d.ts:

declare module 'some-old-lib' {
  export function doSomething(param: string): number
  export default someOldLib
}

Assim, o TypeScript passa a conhecer os tipos da biblioteca.

Caso 2: a definição de tipos está incorreta

Às vezes, a definição de tipos de um pacote @types não corresponde à API real, especialmente em bibliotecas que mudam rapidamente. Nesse caso, uma “asserção de tipo” pode servir como solução temporária:

import { someFunction } from 'buggy-lib'

// A definição de tipo diz que o retorno é string, mas na prática ele é number
const result = someFunction() as number

Essa é apenas uma solução provisória. O ideal é abrir uma issue ou enviar um PR no GitHub da biblioteca.

Vale a pena ativar skipLibCheck?

O tsconfig tem uma opção chamada skipLibCheck. Quando ativa, o TypeScript ignora a verificação de tipos dentro de node_modules.

Minha recomendação é: ative.

Por quê? Você não consegue corrigir os erros de tipos em node_modules, e essa verificação ainda deixa a compilação mais lenta. Em vez de fazer o TypeScript analisar uma série de problemas nas bibliotecas de terceiros, é melhor concentrar a atenção no próprio código.

Situações comuns em que any escapa e como corrigi-las

Mesmo com o modo strict ativo, alguns lugares ainda têm facilidade para “escapar” para o tipo any.

Cenário 1: funções de tratamento de eventos

// ❌ Prática ruim
const handleSubmit = (e: any) => {
  e.preventDefault()
}

// ✅ Prática correta
const handleSubmit = (e: React.FormEvent<HTMLFormElement>) => {
  e.preventDefault()
  // e.currentTarget tem sugestões de tipo completas
}

Tipos de evento usados com frequência:

  • React.MouseEvent<HTMLButtonElement>
  • React.ChangeEvent<HTMLInputElement>
  • React.KeyboardEvent<HTMLDivElement>

Cenário 2: dados da resposta de uma API

// ❌ Prática ruim
const res = await fetch('/api/user')
const data = await res.json()  // any

// ✅ Solução 1: definir a interface manualmente
interface User {
  id: string
  name: string
  email: string
}

const data: User = await res.json()

// ✅ Solução 2: validar com zod (recomendada)
import { z } from 'zod'

const UserSchema = z.object({
  id: z.string(),
  name: z.string(),
  email: z.string().email(),
})

const data = UserSchema.parse(await res.json())  // Infere o tipo automaticamente

A vantagem do zod é combinar verificação de tipos e validação em tempo de execução. Se a estrutura dos dados retornados pelo backend mudar, você percebe imediatamente.

Cenário 3: imports dinâmicos

// ❌ Prática ruim
const module = await import('./utils')  // any

// ✅ Prática correta
const module = await import('./utils') as typeof import('./utils')

Também é possível importar diretamente o item desejado:

const { formatDate } = await import('./utils')  // Infere o tipo automaticamente

Como usar os utility types do TypeScript para desenvolver mais rápido

O TypeScript inclui vários utility types que, quando bem usados, eliminam muito código repetido.

Pick: extrair algumas propriedades

interface User {
  id: string
  name: string
  email: string
  password: string
  createdAt: Date
}

// Precisamos apenas das informações públicas do usuário
type PublicUser = Pick<User, 'id' | 'name' | 'email'>
// { id: string; name: string; email: string }

Omit: excluir algumas propriedades

// Para criar um usuário, não precisamos de id nem de createdAt
type CreateUserInput = Omit<User, 'id' | 'createdAt'>

Partial: tornar todas as propriedades opcionais

// Ao atualizar um usuário, todos os campos são opcionais
type UpdateUserInput = Partial<User>

Required: tornar todas as propriedades obrigatórias

type RequiredUser = Required<Partial<User>>  // Operação inversa

Utility type personalizado

Se os tipos nativos não forem suficientes, você pode criar o seu próprio:

// Torna opcionais todas as propriedades do tipo string
type PartialString<T> = {
  [K in keyof T]: T[K] extends string ? T[K] | undefined : T[K]
}

Para ser sincero, esses utility types podem parecer um pouco assustadores no começo. Depois que você se acostuma, percebe como eles são úteis, principalmente ao trabalhar com tipos de objetos complexos: dá para eliminar muito código duplicado.

Conclusão

Depois de escrever tudo isso, volto ao bug das três da manhã citado no início.

Se o typedRoutes do Next.js estivesse ativo, o erro de digitação da rota nunca teria chegado à produção. Se eu tivesse usado o T3 Env, a ausência da variável de ambiente teria sido detectada durante a inicialização. E, com o modo strict bem configurado, aqueles any implícitos teriam sido identificados pelo TypeScript muito antes.

A segurança de tipos do TypeScript não existe para dificultar o desenvolvimento, mas para antecipar bugs do “tempo de execução” para o “momento da escrita”. Em vez de esperar um usuário encontrar uma tela branca em produção, é melhor deixar a IDE destacar o problema enquanto você digita.

Para encerrar, estes são os pontos principais do artigo:

  1. Otimização do tsconfig: ative o modo strict, configure incremental e paths e use o plugin do Next.js
  2. Rotas type-safe: ative typedRoutes no Next.js 13+ ou use a biblioteca nextjs-routes
  3. Tipagem de variáveis de ambiente: use o T3 Env para combinar verificação de tipos e validação em tempo de execução
  4. Uso prático do modo strict: adote-o gradualmente, resolva problemas de tipos em bibliotecas de terceiros e elimine os escapes mais comuns para any

No começo, a configuração pode parecer trabalhosa e as anotações de tipo, repetitivas. Mas, depois que você se acostuma com as sugestões precisas da IDE e com a descoberta imediata de problemas em potencial ao editar o código, é difícil voltar ao JavaScript sem proteção.

Abra agora o seu tsconfig.json e mude strict para true. Quanto mais sublinhados vermelhos aparecerem, mais bugs em potencial você terá encontrado — e isso é algo positivo.

FAQ

O modo strict deixa a compilação do projeto mais lenta?
Não. O modo strict apenas torna a verificação de tipos mais rigorosa e não afeta de forma significativa a velocidade de compilação. Com a opção incremental, projetos grandes podem até compilar de 30% a 50% mais rápido.
Como ativar o modo strict com segurança em um projeto antigo?
Adote uma estratégia gradual: primeiro ative strict no tsconfig.json e marque com @ts-expect-error os trechos que ainda não podem ser corrigidos. Exija rigor no código novo e refatore o código antigo aos poucos. Também é possível fazer a migração módulo por módulo.
Qual é a diferença entre o T3 Env e a declaração manual do tipo ProcessEnv?
O T3 Env oferece validação em tempo de execução: ao iniciar, o aplicativo verifica se faltam variáveis de ambiente ou se algum formato está incorreto. Ele também impede que o cliente acesse variáveis do servidor. A declaração manual só verifica tipos durante a compilação e não oferece essa proteção em tempo de execução.
O typedRoutes do Next.js funciona com o diretório pages?
Não. O typedRoutes é um recurso experimental do Next.js 13+ criado para o App Router e funciona apenas com o diretório app. Se você ainda usa pages, considere a biblioteca de terceiros nextjs-routes.
Ativar skipLibCheck cria algum risco de segurança?
Não. skipLibCheck apenas ignora a verificação de tipos em node_modules; o seu código continua sendo verificado rigorosamente. Como você não consegue corrigir os erros de tipos das bibliotecas de terceiros, ignorá-los pode acelerar a compilação e manter o foco no próprio código.

18 min de leitura · Publicado em: 6 jan 2026 · Atualizado em: 4 set 2026

Comentários

Entre com GitHub para comentar

Easton BlogEaston Blog