De Vue.js + Vite para Next.js + React + TypeScript: o que uma migração de verdade te ensina
Trocar tecnologia é fácil de falar — o desafio real é reconstruir uma aplicação inteira sem perder nada pelo caminho
10/08/2026 Desenvolvimento
Recentemente participei da migração de um projeto que saiu de Vue.js + Vite para uma nova stack baseada em Next.js, React e TypeScript.
E se tem uma coisa que essa experiência deixou clara é: trocar tecnologia é fácil de falar, difícil de fazer bem.
Mais do que substituir uma ferramenta por outra, o desafio real foi adaptar toda a estrutura do frontend para uma nova arquitetura — mantendo as funcionalidades existentes e sem quebrar a integração com o backend em FastAPI.
Neste artigo, compartilho os principais pontos de atenção dessa migração: o que muda de fato entre os frameworks, onde mora a maior parte da dor, e por que esse tipo de trabalho ensina muito mais sobre arquitetura de software do que sobre sintaxe nova.
Antes: Vue.js + Vite → FastAPI
Depois: Next.js + React + TS → FastAPI
O backend permaneceu praticamente intacto. Toda a transformação aconteceu no frontend — e é sobre isso que vamos falar.
1. Vue.js → React: não é sintaxe, é filosofia
A primeira armadilha de qualquer migração desse tipo é achar que basta "aprender a nova sintaxe".
Vue e React resolvem o mesmo problema — construir interfaces reativas — mas partem de filosofias opostas.
Vue trabalha com Single File Components (.vue), onde <template>, <script> e <style> convivem no mesmo arquivo.
A reatividade é automática: você declara um ref ou reactive, e o Vue rastreia sozinho quando e onde aquele dado é usado. As diretivas (v-if, v-for, v-model) tornam o template declarativo e compacto.
<template>
<input v-model="nome" />
<ul>
<li v-for="produto in produtos" :key="produto.id">
{{ produto.nome }}
</li>
</ul>
</template>
React não tem essa mágica embutida.
É JavaScript (ou TypeScript) puro com JSX, e a reatividade é explícita: você decide, via hooks, quando um estado muda e quando um componente deve re-renderizar.
const [nome, setNome] = useState("");
<input value={nome} onChange={(e) => setNome(e.target.value)} />
<ul>
{produtos.map((produto) => (
<li key={produto.id}>{produto.nome}</li>
))}
</ul>
Não existe v-model pronto — o two-way binding vira value + onChange escritos à mão.
Não existe v-if/v-for — viram expressões JavaScript comuns (&&, .map()).
Esse foi, sem dúvida, o maior choque de mentalidade da migração: sair de um modelo onde o framework cuida da reatividade para um modelo onde você é responsável por cada re-render.
2. Vite → Next.js: a pegadinha de achar que é só bundler
Aqui mora um erro comum de quem encara essa migração como "trocar a ferramenta de build".
Vite é uma build tool. Next.js é um framework completo, com decisões de arquitetura embutidas.
Com Vite + Vue Router, a aplicação era uma SPA client-side clássica: tudo renderiza no navegador, o HTML inicial é praticamente vazio, e o JavaScript assume o controle assim que carrega.
Com Next.js e o App Router, o cenário muda completamente porque Server Components são o padrão.
Isso significa que parte da aplicação pode rodar no servidor e nunca enviar JavaScript para o cliente — inclusive o fetch de dados pode acontecer direto no componente server, sem precisar da combinação clássica useEffect + axios que era natural no mundo Vue/Vite.
Foi exatamente aqui que surgiram boa parte dos erros de build durante a migração.
Qualquer componente que use useState, useEffect ou algum evento de clique precisa da diretiva "use client" no topo do arquivo — sem ela, o Next tenta renderizar aquele componente no servidor e o build simplesmente quebra.
"use client";
import { useState } from "react";
export default function Filtro() {
const [termo, setTermo] = useState("");
// ...
}
Entender essa fronteira entre server e client component foi, na prática, o ajuste mental mais importante de toda a migração.
3. Rotas: de Vue Router para o App Router
No mundo Vue, as rotas costumam viver em um único arquivo de configuração, mapeando path para component de forma explícita e centralizada.
No Next.js com App Router, o roteamento é baseado em estrutura de pastas dentro de app/.
Cada pasta representa uma rota; page.tsx define o conteúdo daquela rota; layout.tsx define layouts compartilhados entre rotas; e arquivos especiais como loading.tsx e error.tsx tratam automaticamente estados de carregamento e erro, sem código adicional.
app/
├── produtos/
│ ├── page.tsx → /produtos
│ ├── [id]/
│ │ └── page.tsx → /produtos/:id
│ └── layout.tsx
Rotas simples são tranquilas de mapear.
A dor aparece nas rotas dinâmicas e aninhadas: um /produtos/:id do Vue Router vira uma pasta [id]/page.tsx, e reorganizar rotas mais profundas exige repensar a estrutura de pastas do zero — não é um find-and-replace, é redesenho de árvore de navegação.
4. Camada de API e a tipagem "de verdade" com TypeScript
Essa foi provavelmente a etapa mais burocrática — e também a mais valiosa.
Em projetos Vue com JavaScript (ou TypeScript mais permissivo), é comum que as respostas da API circulem com tipagem implícita ou nem tipadas.
Migrando para uma stack Next.js + TypeScript com strict: true, isso deixa de ser opcional:
Definir interfaces/types para cada retorno do FastAPI
Tipar os hooks de fetch de forma explícita
Lidar com o compilador reclamando de undefined/null
O FastAPI já gera automaticamente schemas Pydantic, que podem alimentar geração de tipos no frontend — ferramentas como openapi-typescript ajudam bastante nesse processo.
interface Produto {
id: number;
nome: string;
preco: number;
}
async function getProdutos(): Promise<Produto[]> {
const res = await fetch(`${API_URL}/produtos`);
return res.json();
}
O ganho aqui não é estético. É contrato: o frontend passa a "conversar" com o backend com garantias em tempo de compilação, não só em tempo de execução.
O que essa migração realmente ensina
O ponto mais importante de tudo isso não é técnico — é conceitual:
A stack muda, mas o domínio da aplicação não
O trabalho real de uma migração como essa não é "aprender React" ou "aprender Next.js".
É entender profundamente o que cada tela fazia, quais dados ela consumia do backend, quais regras de negócio estavam implícitas no comportamento antigo — e reconstruir tudo isso com outras ferramentas, sem quebrar o contrato com o backend em FastAPI.
Resolver os erros de build, entender a fronteira entre server e client components, reorganizar rotas, tipar corretamente as respostas da API — nada disso é sobre decorar sintaxe nova.
É sobre entender como a aplicação funciona como um todo, e ter disciplina para reconstruir essa lógica em cima de uma arquitetura diferente sem perder nada pelo caminho.
Essa é, no fim das contas, a diferença entre quem só troca de ferramenta e quem entende arquitetura de sistema.
Cada migração traz seus próprios desafios.
E, junto com eles, muito aprendizado.