Criar projeto React com Vite: passo a passo
npm create vite, o que cada arquivo do projeto faz, como o main.jsx liga o componente ao HTML e o que dá para apagar antes de começar.
Você só queria ver sua primeira tela React, mas o terminal pergunta por
framework, variante e template. Depois surgem arquivos desconhecidos, a pasta
node_modules e comandos parecidos. Nesta aula, você vai da pasta vazia a uma
agenda no navegador sabendo quem criou, instalou e executou cada parte.
React descreve os componentes da interface, mas não cria a estrutura do projeto
nem prepara sua publicação. O Vite organiza esse fluxo; o npm baixa
pacotes e executa comandos. Já create-vite é um scaffolder (gerador de
estrutura inicial): ele aplica um template, um modelo pronto de arquivos e
configurações.
Pense num kit de montagem: create-vite entrega peças e manual, npm install
traz o material listado, npm run dev liga a bancada e npm run build fecha a
caixa que vai para a hospedagem. A comparação termina aí: no projeto, o Vite lê,
transforma e conecta arquivos de código.
Os comandos e resultados abaixo foram testados no macOS com Node 24.16.0 e npm 11.13.0. A Pata Serena é uma clínica veterinária fictícia, criada apenas para dar contexto ao exemplo. Se React ainda for novidade, leia o que é React antes de continuar.
Como criar um projeto React com Vite?
Use o gerador create-vite com o template React. Ele cria a pasta e os arquivos
iniciais, mas ainda não baixa as dependências nem abre o projeto no navegador.
Abra o terminal, o programa em que a gente executa comandos de texto, na pasta onde você guarda seus projetos e rode:
npm create vite@latest clinica-vet -- --template react│ ◇ Scaffolding project in /private/tmp/pata-serena/clinica-vet… │ └ Done. Now run:
cd clinica-vet npm install npm run dev
Leia o comando da esquerda para a direita:
npm createpede ao npm que execute um gerador temporário.vite@latestescolhe o pacotecreate-vitena versão marcada comolatestno registro do npm.clinica-vetserá o nome da pasta e do projeto.- O primeiro
--separa os argumentos do npm dos argumentos enviados ao gerador. --template reactescolhe o modelo React sem abrir perguntas interativas.
O gerador só escreveu arquivos. Com --template react, ele não fez perguntas.
Sem essa opção, um assistente pede:
- Project name — nome da pasta e campo
namedopackage.json; prefira minúsculas e hífen, comoclinica-vet. - Select a framework — framework é um conjunto de ferramentas e convenções para estruturar a aplicação; aqui você escolhe React.
- Select a variant — variante é uma configuração dentro do framework, como JavaScript, TypeScript ou uma opção com React Compiler.
Depois do framework, aparecem apenas variantes compatíveis. Opções com seta,
como RSC e React Router, chamam o instalador de outro projeto. --template react
pula as perguntas; --help mostra os templates aceitos.
CLI significa command-line interface: programa controlado pelo terminal. RSC significa React Server Components; React Compiler é uma ferramenta de otimização do React.
Experimente você mesmo: o template aparece na ajuda?
Antes de executar o próximo comando, preveja dois nomes que devem aparecer:
react para JavaScript e react-ts para TypeScript. Depois compare sua previsão
com a seção Available templates da saída.
npx create-vite@latest --helpCreate a new Vite project in JavaScript or TypeScript. When running in TTY, the CLI will start in interactive mode.
Options: -t, –template NAME use a specific template -i, –immediate / –no-immediate install dependencies and start dev –eslint / –no-eslint use ESLint instead of Oxlint (only for React templates) –overwrite remove existing files if target directory is not empty –interactive / –no-interactive force interactive / non-interactive mode -h, –help display this help message
Available templates: vanilla-ts vanilla vue-ts vue react-ts react react-compiler-ts react-compiler preact-ts preact lit-ts lit svelte-ts svelte solid-ts solid qwik-ts qwik
O par react-ts / react confirma sua previsão. A lista mostra apenas templates
internos do create-vite; outras escolhas do assistente usam CLIs próprias.
--eslint troca o Oxlint pelo ESLint. Ambos são linters: analisam o código
sem executá-lo e apontam padrões problemáticos.
Quais arquivos o Vite cria no projeto?
O gerador cria o código-fonte, o HTML inicial e os arquivos que descrevem como
instalar e executar o projeto. Ele ainda não baixa pacotes; por isso vale olhar
o esqueleto antes de usar npm install.
Entre na pasta criada e liste os arquivos:
cd clinica-vet
find . -type f | sortQuinze arquivos. Nenhuma pasta node_modules ainda — esse é o diretório em que
o npm guardará os pacotes instalados. O create-vite só escreve o esqueleto.
src significa source: é onde fica o código-fonte que você edita. Um
ponto de entrada é o primeiro arquivo de código carregado para iniciar a
aplicação. Configuração é o conjunto de opções que orienta uma ferramenta.
E hash é uma impressão curta calculada a partir do conteúdo; veremos seu
efeito no build. Com esses nomes no lugar, veja o papel de cada arquivo:
| arquivo | para que serve | você mexe? |
|---|---|---|
index.html |
a única página real do projeto; contém a div onde o React monta |
sim, no título e nas meta tags |
package.json |
nome, dependências e os scripts dev, build, lint e preview |
sim, ao instalar biblioteca |
vite.config.js |
configuração do Vite; já vem com o plugin do React ligado | raramente |
.oxlintrc.json |
regras do linter, incluindo react/rules-of-hooks |
raramente |
src/main.jsx |
o ponto de entrada: liga o React ao index.html |
quase nunca |
src/App.jsx |
o componente raiz — a tela que você vê | sim, o tempo todo |
src/index.css |
estilo global da página | sim |
src/App.css |
estilo da tela de boas-vindas | sim (ou apaga) |
src/assets/ |
imagens importadas pelo JavaScript, com hash no build | sim |
public/ |
arquivos servidos crus, sem passar pelo Vite | às vezes |
A diferença entre src/assets/ e public/ confunde no começo. Em src/assets,
a imagem é importada no código (import logo from './assets/logo.svg'), o Vite
a processa e coloca um hash no nome. Em public/, o arquivo é copiado tal e
qual para a raiz do site: o favicon.svg chega como /favicon.svg, com esse
nome exato, porque o index.html o referencia direto.
O package.json do template é curto:
{
"name": "clinica-vet",
"private": true,
"version": "0.0.0",
"type": "module",
"scripts": {
"dev": "vite",
"build": "vite build",
"lint": "oxlint",
"preview": "vite preview"
},
"dependencies": {
"react": "^19.2.8",
"react-dom": "^19.2.8"
},
"devDependencies": {
"@types/react": "^19.2.17",
"@types/react-dom": "^19.2.3",
"@vitejs/plugin-react": "^6.0.4",
"oxlint": "^1.75.0",
"vite": "^8.2.0"
}
}package.json é o manifesto do projeto: registra nome, comandos e pacotes. O
campo type: module diz ao Node para interpretar import e export como
módulos JavaScript. Um módulo é um arquivo que pode importar ou exportar
partes do programa. Já scripts cria atalhos nomeados para comandos maiores.
Só duas dependências de produção — pacotes necessários para a aplicação
funcionar — aparecem: react e react-dom. As devDependencies são
ferramentas usadas para desenvolver, analisar e construir o projeto. O ^
permite atualizações compatíveis dentro das regras de versionamento do npm. O
artigo sobre npm e package.json destrincha cada
campo e o arquivo de lock.
Como index.html, main.jsx e App.jsx se conectam?
O navegador abre index.html; esse HTML carrega main.jsx; então main.jsx
liga React à div raiz e pede para renderizar App. São três arquivos com três
responsabilidades, conectados numa sequência.
O index.html não fica escondido dentro de public/: ele é o ponto de
partida e permanece na raiz. O DOM é a árvore de objetos que o navegador
cria a partir desse HTML; React precisa encontrar um nó dessa árvore para montar
a interface.
<!doctype html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<link rel="icon" type="image/svg+xml" href="/favicon.svg" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>clinica-vet</title>
</head>
<body>
<div id="root"></div>
<script type="module" src="/src/main.jsx"></script>
</body>
</html>Duas linhas do <body> fazem o projeto inteiro funcionar. A div vazia com
id="root" é o buraco onde o React vai despejar a interface. E o <script type="module"> carrega o src/main.jsx, que é quem faz o despejo:
import { StrictMode } from 'react'
import { createRoot } from 'react-dom/client'
import './index.css'
import App from './App.jsx'
createRoot(document.getElementById('root')).render(
<StrictMode>
<App />
</StrictMode>,
)As quatro importações preparam o caminho. StrictMode vem do React;
createRoot, da integração do React com o DOM; index.css entra pelos seus
efeitos visuais; e App é o componente principal da tela. Importar significa
usar algo exportado por outro módulo.
document.getElementById('root') procura no DOM o elemento cujo identificador é
root. createRoot recebe esse elemento e cria uma raiz controlada pelo React.
O .render() descreve o que entra nela. StrictMode é um modo de
desenvolvimento que repete algumas verificações para revelar efeitos
problemáticos; ele não adiciona essa repetição ao build de produção.
null representa ausência: a busca não encontrou elemento com aquele ID. O
conserto é fazer os dois lados concordarem. Restaure id="root" no HTML ou use
o novo identificador no getElementById, salve e atualize a página.
O erro é este, reproduzido no Node com jsdom simulando a página — a mensagem é a
mesma que aparece no console do navegador. Se quiser rodar por conta própria,
faça numa pasta separada, fora do projeto, com o que ele precisa instalado:
npm init -y && npm pkg set type=module && npm i jsdom react react-dom.
import { JSDOM } from 'jsdom'
import { createRoot } from 'react-dom/client'
// o index.html do projeto foi editado e o id virou "app"
const pagina = new JSDOM('<!doctype html><body><div id="app"></div></body>')
globalThis.window = pagina.window
globalThis.document = pagina.window.document
createRoot(document.getElementById('root'))Error: Target container is not a DOM element. at process.env.NODE_ENV.exports.createRoot (/private/tmp/pata-serena/teste-root/node_modules/react-dom/cjs/react-dom-client.development.js:28014:15) at file:///private/tmp/pata-serena/teste-root/montar.mjs:9:1 at ModuleJob.run (node:internal/modules/esm/module_job:439:25) at async node:internal/modules/esm/loader:633:26 at async asyncRunEntryPointWithESMLoader (node:internal/modules/run_main:101:5)
Node.js v24.16.0
Como instalar as dependências e abrir o projeto?
Dentro da pasta criada, npm install baixa os pacotes registrados no
package.json; depois, npm run dev inicia um servidor local para o navegador.
O primeiro prepara a bancada, e o segundo liga essa bancada.
Instale as dependências:
npm install9 packages are looking for funding
run npm fund for details
found 0 vulnerabilities
Vinte e quatro pacotes e 55 MB foram para node_modules. O npm também criou
package-lock.json, que registra as versões exatas instaladas para outras
máquinas repetirem a mesma árvore. “Audited” indica que o npm comparou os
pacotes com sua base de vulnerabilidades conhecidas; não é uma garantia geral de
segurança.
O tempo não se repete exatamente na sua máquina: depende da rede e do cache, uma cópia local que evita baixar novamente o que o npm já tem.
Experimente você mesmo: qual endereço o Vite vai escolher?
Antes de iniciar, anote sua previsão: a primeira tentativa deve usar a porta 5173. Uma porta é como uma entrada numerada do computador para um serviço. Se ela estiver ocupada, espere que o Vite imprima outro número. Agora suba o servidor:
npm run devPort 5173 is in use, trying another one… Port 5174 is in use, trying another one…
VITE v8.2.2 ready in 83 ms
➜ Local: http://localhost:5175/ ➜ Network: use –host to expose
Duas coisas nessa saída. A primeira: 83 milissegundos para o servidor ficar de
pé — o Vite não empacota o projeto para começar, ele serve cada arquivo sob
demanda. A segunda: nesta máquina as portas 5173 e 5174 já estavam ocupadas, e o
Vite subiu na 5175. Compare com sua previsão e abra a URL que o seu terminal
imprimiu, não a que o tutorial diz. Local significa que esse endereço está
disponível na sua própria máquina.
Como trocar o template por uma tela real?
Remova os arquivos usados apenas na demonstração e substitua App.jsx pela
agenda. Assim, o esqueleto mínimo vira uma tela com dados do cotidiano.
Antes de escrever sua primeira linha, apague os recursos que não serão usados:
rm -rf src/assets public/icons.svgrm -rf apaga sem pedir confirmação. Confira se o terminal está dentro de
clinica-vet e se os dois caminhos estão corretos antes de executá-lo.
Depois esvazie o src/App.css (ele só estiliza a tela de boas-vindas) e reduza
o src/index.css ao mínimo:
:root {
font-family: system-ui, sans-serif;
line-height: 1.6;
}
body {
margin: 0;
padding: 2rem;
}Agora src/App.jsx vira a agenda. Ele usa JSX, uma
sintaxe parecida com HTML escrita dentro do JavaScript:
import './App.css'
const consultas = [
{ id: 1, hora: '08:30', pet: 'Amora', tutor: 'Renata', servico: 'Vacina V10' },
{ id: 2, hora: '09:15', pet: 'Thor', tutor: 'Marcelo', servico: 'Retorno' },
{ id: 3, hora: '10:00', pet: 'Nina', tutor: 'Juliana', servico: 'Castração' },
]
function App() {
return (
<main>
<h1>Agenda de hoje</h1>
<ul>
{consultas.map((consulta) => (
<li key={consulta.id}>
{consulta.hora} — {consulta.pet} ({consulta.tutor}): {consulta.servico}
</li>
))}
</ul>
</main>
)
}
export default Appconsultas é um array (uma lista) de objetos; cada objeto reúne os dados de um
horário. App é a função componente que retorna a interface. Dentro da lista,
map percorre cada consulta e chama a callback, a função recebida entre
parênteses, para criar um <li>. A prop key identifica cada item para o React;
sem ela, o console avisa. Veja o motivo em listas com map e
key.
Falta ajustar o index.html, que ainda está em inglês e com o título da pasta.
Troque lang="en" por lang="pt-BR" e o <title> por algo que faça sentido —
esse texto é o que aparece na aba do navegador e no resultado de busca.
Depois da limpeza sobram onze dos quinze arquivos do template — mais o
package-lock.json, que o npm install escreveu. O README.md, o .gitignore
e o .oxlintrc.json ficam: o primeiro você reescreve, os outros dois já estão
prontos e corretos.
Como o Vite atualiza a tela sem recarregar a página?
Ele usa HMR (Hot Module Replacement, ou substituição de módulo a quente): envia somente o módulo alterado ao navegador enquanto o servidor continua de pé.
Com o npm run dev rodando, troque o texto do <h1> para
Agenda de hoje na Pata Serena e salve. O terminal ganha uma linha na hora:
# terminal do npm run dev, logo depois de salvar o App.jsxO Vite enviou ao navegador só o módulo alterado, e o React trocou o componente. Em edições compatíveis, o React Refresh preserva até o estado, a memória temporária da tela; certas mudanças ainda exigem recarregá-la.
Para ver o mecanismo, peça ao servidor o mesmo arquivo que o navegador pede:
curl -s http://localhost:5175/src/App.jsxTrês descobertas numa resposta só. O <h1> já vem com o texto novo, sem
reiniciar nada. O JSX não chegou ao navegador: virou chamadas de _jsxDEV,
porque navegador nenhum entende JSX. E a primeira linha — aquela emenda comprida
antes de qualquer código seu — injeta o createHotContext, que é o canal por
onde a atualização quente viaja.
Como criar e conferir o build de produção?
Rode npm run build para gerar em dist/ os arquivos otimizados para
publicação. Build é esse processo de transformar o código-fonte na versão
que a hospedagem entrega:
npm run build✓ built in 295ms
O resultado é a pasta dist/, e é ela inteira que sobe para a hospedagem:
Quatro arquivos. O JavaScript tem 190,77 kB e cai para 60,16 kB com gzip, uma compressão usada na transferência. Quase todo esse peso vem do React; a agenda ocupa poucas centenas de bytes.
Os nomes com letras aleatórias no meio (index-CZtxkWAz.js) não são aleatórios:
são um resumo do conteúdo do arquivo. Prove: acrescente esta quarta consulta ao
array, exatamente assim, e rode o build de novo.
{ id: 4, hora: '11:30', pet: 'Fumaça', tutor: 'Beatriz', servico: 'Banho medicinal' },npm run build✓ built in 91ms
O JavaScript mudou de nome (CZtxkWAz virou CF_umR9A) porque o conteúdo
mudou. O CSS manteve o hash, porque não foi tocado. E as letras não são sorteadas
a cada execução: elas saem do próprio conteúdo, então, com o App.jsx e o
index.css iguais aos daqui, o seu build imprime esses mesmos dois nomes. É essa
propriedade que permite ao navegador guardar os arquivos para sempre: quando você
publica uma correção, o nome muda e o cache velho é ignorado sozinho.
O dist/index.html também foi reescrito — o <script src="/src/main.jsx"> some
e entram as tags que apontam para os arquivos com hash:
cat dist/index.htmlO npm run preview abre um servidor local que entrega dist/ sem HMR. Ele serve
para conferir o build antes de publicar, não para hospedar o site em produção:
npm run previewComo corrigir Node antigo e porta ocupada no Vite?
Confira primeiro a versão do Node; depois, use a URL exata mostrada pelo Vite. Essas duas checagens separam um problema do ambiente de um erro no seu código.
O primeiro erro aparece quando o runtime Node, o programa que executa as
ferramentas JavaScript, é antigo demais. O Vite 8 exige
^20.19.0 || >=22.12.0, e o npm install avisa — só que o aviso passa
despercebido no meio da instalação:
npm installEBADENGINE significa que a versão do runtime não atende ao pacote. Como é
warn e não error, a instalação termina; o npm run dev falha depois:
npm run devNode.js v18.20.8
Traduzindo: uma ferramenta do Vite usa styleText, que só existe no Node 20.12
em diante. Não é bug do seu código nem do Vite — é o Node velho. A correção é
atualizar, e o caminho sem dor é o
nvm, que troca a versão do Node por projeto.
O segundo tropeço é a porta. Você já viu o Vite pulando de 5173 para 5175 sozinho, o que é ótimo — até você abrir a URL errada e jurar que o projeto não carregou. Para descobrir quem ocupou a porta:
lsof -nP -iTCP:5173 -sTCP:LISTENUm node de uma sessão anterior. PID é o identificador numérico do processo;
encerre-o com kill 47557, trocando pelo PID da sua saída. Se o back-end aceita
requisições de uma origem específica por CORS, uma regra do navegador, use
--strictPort para o Vite falhar em vez de trocar a porta:
npm run dev -- --port 5173 --strictPortFalhar alto é melhor que subir na porta errada: assim você resolve agora, e não depois de meia hora depurando um CORS que nunca esteve quebrado.
O que aprender depois de criar o projeto React?
O próximo passo é entender melhor o JSX, o map e a key usados em App.jsx.
A trilha de React conduz essa sequência; o guia de
React mostra o mapa completo.
Até aqui: você criou o esqueleto com create-vite, instalou os pacotes,
ligou HTML e React, trocou o template por uma agenda e gerou o build em dist/.
Antes de avançar, troque o título do App.jsx, salve e confirme a atualização
por HMR. Depois rode npm run build e verifique que a pasta dist foi criada.
Abra npm run preview e confira a mesma tela no build final. Esses três sinais —
HMR, build sem erro e preview funcionando — confirmam que o projeto foi montado
por inteiro, não apenas que o comando inicial criou uma pasta.
Prefere aprender em vídeo?
Tem uma aula sobre este assunto no nosso canal.
Perguntas frequentes
Preciso instalar o Vite antes de criar o projeto?
Posso criar o projeto dentro de uma pasta que já existe?
Qual a diferença entre o template react e o react-ts?
Dá para usar yarn ou pnpm em vez do npm?
Dúvidas e comentários
Travou em algum passo? Pergunte aqui — a equipe e outros alunos respondem.
Entrar para perguntarÉ o mesmo login gratuito dos cursos.
Nenhuma dúvida por aqui ainda — a primeira pode ser a sua.
Todo o código deste artigo foi executado em Node 24.16.0 · Vite 8.2.2 · React 19.2.8, e as saídas exibidas são as reais — como produzimos este conteúdo.
Fontes consultadas
- Vite — Getting Started — vite.dev
- Vite — Building for Production — vite.dev
- React — Creating a React App — react.dev



