O caos que ninguém te avisa
A gestão de pacotes e bibliotecas em um projeto react é uma das partes mais subestimadas do desenvolvimento. Você instala algo, ele funciona no seu computador, e depois no CI/CD ou na máquina do colega começa a dar erro de compatibilidade. Eu já perdi um dia inteiro porque o zustand atualizou uma versão maior que quebrou o TypeScript de um jeito que o erro só aparecia após o build, não no IDE.
a gestão de pacotes e bibliotecas em um projeto react
O básico funciona assim: você tem o package.json, o lock file, e o node_modules. O lock file (package-lock.json para npm, yarn.lock para Yarn, pnpm-lock.yaml para pnpm) é o que garante que todo mundo no time está usando exatamente as mesmas versões transitivas. Sem ele, você está apostando que o que instalou hoje é o mesmo que vai instalar semana que vem. Isso não acontece. Eu uso pnpm porque ele é rápido e não duplica pacotes no disco, mas o tamanho do node_modules já causou problema num repositório legado que precisava de 12 GB só pra instalar. O time migrou pro npm com freeze-lockfile=false temporariamente e resolveu, mas isso expõe outro problema: lockfile desbloqueado significa instabilidade de build.
👉 Clique no botão abaixo para saber mais sobre o assunto!
O problema real começa quando você mistura workspaces monorepo com bibliotecas que dependem de versões conflitantes do React. O Next.js 14 ainda usa React 18, mas algumas bibliotecas de UI anunciam compatibilidade com React 19 antes da hora. Se você deixar o resolver.field do pnpm achar "react" em todo lugar, vai ter dois Reacts rodando no mesmo bundle. Eu vi isso acontecer num app que carregava o hook personalizado de uma biblioteca interna enquanto a biblioteca de terceiro usava o React global. O render ficou com duplicação de estado e o cache do TanStack Query falhava intermitentemente. A solução foi configurar o pnpm.overrides com react = 18.3.1 e re-React a biblioteca problemática dentro do workspace. O outro erro comum é confiar cegamente no devDependencies vs dependencies. Se você exporta um componente que usa uma biblioteca, essa biblioteca precisa estar em dependencies, não em devDependencies. Senão quem instalar seu pacote não vai receber a dependência. Já tive que debugar um erro de "Cannot find module" num pacote publishado porque o devDependencies tinha sido confundido com dependencies no published.
Para manter isso sob controle, eu faço o seguinte: fixo as versões das dependências principais, uso resolutions ou overrides para forçar consistência transitiva, e nunca rodo npm install --legacy-peer-deps como regra, apenas como último recurso documentado. Quando preciso dessa flag, registro o motivo no CHANGELOG e crio uma issue tracker interna para resolver a incompatibilidade depois. Testes de compatibilidade devem incluir um build limpo em CI, não só o start local. Eu adicionei um job que roda pnpm install --frozen-lockfile && pnpm build && pnpm test:e2e, e se alguma dessas etapas falhar, o deploy para. Isso elimina a ilusão de que "funciona na minha máquina" é suficiente. O tempo de instalação cai de cerca de 40 minutos pro padrão npm velho pro algo em torno de 6 minutos com pnpm, mas o ganho real é na ausência de surpresas entre ambientes.
Se o projeto for muito grande ou tiver muitas bibliotecas obscuras, o ideal é manter um audit regular com pnpm audit --prod e revisar as versões com dependências conhecidas de vulnerabilidade ou breakings. Uma vez por mês eu passo pelo changelog das principais bibliotecas do app: zustand, react-query, tanstack router, e a biblioteca de UI. Se houver atualização de major, faço um branch de teste antes de aplicar no main. Não existe solução perfeita. Dependências transitivas podem ter bugs que só aparecem em produção, e às vezes a única saída é forkar a biblioteca ou usar um patch do pnpm com a opção patch: para corrigir localmente até o maintainer resolver. Isso adiciona manutenção, mas é melhor que travar o app inteiro numa versão velha por medo de upgrade.