Executando verificação de segurança...
10

Da Bagunça ao Nirvana: Como Parei de Sofrer com Estrutura de Projetos Next.js

Fala, galera do TabNews! 👋

Lembram quando eu postei aqui sobre a melhor forma de estruturar projetos React com Vite? Pois é... aquele post já tem uns bons anos nas costas, e adivinha? Eu evoluí (espero que vocês também 😅).

Hoje venho compartilhar minha nova solução para o problema eterno de todo dev: como organizar um projeto fullstack sem virar um completo caos.

A Jornada do Sofrimento (aka: Minha Evolução)

Quando comecei com React, era aquela bagunça clássica:

  • Componentes misturados com lógica de negócio
  • Pasta utils virando um depósito de tudo
  • Imports quilométricos tipo ../../../components/form/input/TheOneInput.tsx
  • Aquela sensação de "onde diabos eu coloco esse arquivo?"

Daí veio o post sobre React + Vite, onde eu organizei tudo bonitinho. Funcionou bem... até eu começar a fazer projetos fullstack de verdade.

Enter Next.js: O Plot Twist

O Next.js mudou o jogo (e quebrou minha estrutura antiga 💀). De repente eu tinha:

  • Server Components e Client Components
  • Server Actions
  • Rotas que são pastas
  • Middleware para autenticação
  • Um monte de conceitos novos

E minha estrutura antiga? Não fazia mais sentido.

Então, depois de muito café, refatorações dolorosas e discussões acaloradas com o duck de borracha na minha mesa, eu criei algo que finalmente funciona.

O Template que Mudou Minha Vida (dramático, eu sei)

Criei um template fullstack Next.js que uso em TODOS os meus projetos agora. E o melhor? É open source!

👉 github.com/igorroc/fullstack-next-template

O Que Tem Nessa Belezura?

Stack Completa:

  • ✅ Next.js 16 (o mais novo!)
  • ✅ TypeScript (porque somos adultos responsáveis)
  • ✅ Prisma + PostgreSQL (banco de dados de verdade)
  • ✅ NextUI (componentes bonitos sem esforço)
  • ✅ Docker (pra subir o banco sem choro)
  • ✅ Autenticação completa (com JWT e tudo mais)

Mas o Real MVP é a Arquitetura:

src/
├── app/                 # Next.js App Router
│   ├── auth/           # Rotas de autenticação
│   └── profile/        # Páginas protegidas
│
├── components/         # UI Components (Client)
│   ├── auth/          # Formulários, etc
│   └── profile/       # Componentes de perfil
│
├── features/          # Business Logic (Server Actions)
│   ├── auth/         # login.ts, register.ts
│   └── users/        # get-all-users.ts
│
└── lib/              # Utilitários compartilhados
    ├── utils/        # validators.ts
    ├── auth.ts       # Funções de autenticação
    └── db.ts         # Prisma client

Por Que Essa Estrutura é Diferente?

1. Separação Clara de Responsabilidades

Chega de ficar pensando "onde eu coloco isso?":

  • Lógica de negócio?features/
  • Componentes visuais?components/
  • Utilitários?lib/
  • Páginas?app/

É tipo aqueles organizadores de gaveta: cada coisa tem seu lugar.

2. Organização por Feature (não por tipo)

Em vez de ter:

❌ components/Button.tsx
❌ components/Form.tsx
❌ actions/login.ts
❌ actions/register.ts

Você tem:

✅ features/auth/
   ├── login.ts
   ├── register.ts
   └── index.ts

✅ components/auth/
   ├── login-form.tsx
   ├── register-form.tsx
   └── index.ts

Tudo relacionado à autenticação fica junto. Quando você precisa mexer em auth, sabe exatamente onde ir.

3. Imports Limpos (graças aos Barrel Exports)

Sabe aqueles imports horríveis?

import { loginAction } from "@/actions/auth/login"import { registerAction } from "@/actions/auth/register"import { logoutAction } from "@/actions/auth/logout"

Agora é assim:

import { loginAction, registerAction, logoutAction } from "@/features/auth"

Cada pasta tem um index.ts que exporta tudo. É tipo um porteiro que facilita sua vida.

4. kebab-case em TUDO

Sim, eu escolhi uma side nesse eterno debate:

  • login-form.tsx
  • LoginForm.tsx

Por quê? Consistência. URLs são kebab-case, então por que não os arquivos? Menos coisa pra pensar, mais coisa pra programar.

Exemplo Prático: Adicionando uma Nova Feature

Digamos que você quer adicionar um sistema de produtos. Aqui vai o passo a passo:

1. Cria a Server Action:

// src/features/products/get-products.ts
"use server"
import db from "@/lib/db"

export async function getProducts() {
  return await db.product.findMany()
}

2. Exporta:

// src/features/products/index.ts
export { getProducts } from "./get-products"

3. Cria o Componente:

// src/components/products/product-list.tsx
"use client"
import { Card } from "@nextui-org/react"

export function ProductList({ products }) {
  return (
    <div className="grid gap-4">
      {products.map(p => (
        <Card key={p.id}>{p.name}</Card>
      ))}
    </div>
  )
}

4. Usa na Página:

// src/app/products/page.tsx
import { getProducts } from "@/features/products"
import { ProductList } from "@/components/products"

export default async function ProductsPage() {
  const products = await getProducts()
  return <ProductList products={products} />
}

Pronto! Tudo organizado, fácil de encontrar, fácil de manter.

O Que Já Vem Pronto?

Para não começar do zero (porque a vida é curta), o template já vem com:

  • 🔐 Sistema de autenticação completo (register, login, logout)
  • 🛡️ Middleware de proteção de rotas
  • 👤 Página de perfil com lista de usuários
  • 🗄️ PostgreSQL no Docker (basta um npm run compose:up)
  • 🎨 NextUI configurado (dark mode e tudo)
  • 📝 TypeScript super configurado
  • 🔄 Migrations automáticas com Prisma

É literalmente dar um git clone e começar a programar.

As Lições Que Aprendi (e Você Não Precisa Aprender na Dor)

1. Clean Architecture Não é Overengineering

Já ouvi muito: "isso é muito complexo para um projeto simples!"

Spoiler: não é. É justamente o contrário. Quando você tem uma estrutura clara desde o início, ADICIONAR coisas fica trivial. É tipo ter uma casa com os cômodos definidos - você sabe onde colocar cada móvel.

2. Separar Client e Server Components é CRUCIAL

Next.js 13+ tem essa distinção importante. Manter os Server Components nas páginas (app/) e Client Components separados (components/) te salva de muita dor de cabeça.

3. Barrel Exports São Seus Amigos

Aquele index.ts em cada pasta pode parecer boilerplate inútil, mas quando você tem 50 imports em um arquivo e pode resumir tudo em 3 linhas, você agradece.

4. Nomenclatura Consistente > Nomenclatura Perfeita

Não importa se você escolhe kebab-case ou PascalCase. Importa que você escolha um e siga até o fim. A consistência reduz a carga cognitiva.

Performance e Produção

Uma dúvida comum: "isso não adiciona muita complexidade?"

Resposta curta: não.

Resposta longa: essa estrutura é como ter um armário organizado. No início você gasta 5 minutos a mais organizando, mas depois economiza horas procurando as coisas.

E em produção? O Next.js otimiza tudo igual. A estrutura é só para você, dev, não sofrer.

Como Usar (TL;DR)

  1. Clone o repo:
git clone https://github.com/igorroc/fullstack-next-template.git meu-projeto
cd meu-projeto
bun install
  1. Configure o .env:
DATABASE_DB="meu_db"
DATABASE_PASSWORD="senha_super_secreta"
POSTGRES_PRISMA_URL="postgresql://postgres:senha_super_secreta@localhost:5432/meu_db"
AUTHENTICATION_SECRET_KEY="seu_jwt_secret_aqui"
  1. Sobe o banco:
bun compose:up
bun migrate
  1. Roda:
bun dev

Pronto! Você tem um fullstack app rodando com autenticação, banco de dados e tudo mais.

Bun vs npm: Por Que Eu Escolhi a Velocidade

Uma coisa que você deve ter notado nos exemplos: eu uso tanto npm quanto bun nos comandos. Mas vou ser sincero: eu prefiro Bun. E vou te contar por quê.

O Que Diabos é Bun?

Para quem não conhece, o Bun é um runtime JavaScript que veio para dar um chega pra lá no Node.js. E quando eu digo "veio com força", não é exagero. O negócio é ABSURDAMENTE rápido.

A Diferença na Prática

Vou te mostrar números reais do meu próprio setup:

Instalando dependências deste template:

  • npm: ~45 segundos ⏰
  • bun: ~8 segundos ⚡

Rodando o TypeScript check:

  • npm: ~3.2 segundos
  • bun: ~1.8 segundos

É tipo comparar uma Ferrari com um Fusca. Ambos te levam até o destino, mas um é MUITO mais divertido.

Por Que Eu Uso Bun?

1. Velocidade = Produtividade

Aqueles segundos economizados se acumulam. Já fiz as contas: em um dia de trabalho, economizo uns 15-20 minutos só de tempo de build e install. Isso é um café a mais, um episódio de podcast, ou simplesmente menos tempo olhando pra tela preta esperando.

2. TypeScript Nativo

O Bun entende TypeScript out of the box. Sem configuração, sem babel, sem transpilar. É tipo mágica, mas é só engenharia bem feita.

3. APIs Modernas

As APIs do Bun são limpas e intuitivas. Se você já sofreu com fs.readFileSync vs fs.promises.readFile, vai entender o alívio.

4. Drop-in Replacement

O mais legal? Você não precisa mudar NADA no seu código. É literalmente trocar npm por bun nos comandos:

# Com npm
npm install
npm run dev
npm run build

# Com Bun (mesma coisa, mais rápido)
bun install
bun run dev
bun run build

"Mas E a Compatibilidade?"

Boa pergunta! Aqui vai minha experiência REAL:

Funciona perfeitamente:

  • Next.js (obviamente, é o que usamos aqui)
  • Prisma
  • NextUI / Tailwind
  • React
  • A maioria dos pacotes npm

Pode ter problemas:

  • Alguns pacotes nativos antigos
  • Ferramentas específicas de Node.js
  • Alguns builders e bundlers específicos

Minha estratégia: Uso Bun em desenvolvimento (porque velocidade) e tenho npm/node na CI/CD como fallback (porque empresas gostam de estabilidade).

"Devo Migrar Pro Bun?"

Depende do seu cenário:

Use Bun se:

  • ✅ Você está começando um projeto novo
  • ✅ Quer velocidade de desenvolvimento
  • ✅ Gosta de ferramentas modernas
  • ✅ Tem um projeto pessoal ou startup
  • ✅ Não tem restrições corporativas

Fique com npm se:

  • ✅ Está em produção crítica há anos
  • ✅ Tem dependências muito específicas/antigas
  • ✅ Trabalha numa empresa que só aceita "battle-tested"
  • ✅ Não quer lidar com possíveis edge cases

Minha Recomendação Honesta

Experimente o Bun! Sério. Baixa, testa nesse template, vê se funciona pro seu caso. Se funcionar (e provavelmente vai), você nunca mais vai querer voltar pro npm install lento.

E se não funcionar? npm tá ali, fiel como sempre, esperando você de braços abertos.

Como Instalar o Bun

Simples demais:

# No macOS/Linux
curl -fsSL https://bun.sh/install | bash

# No Windows (PowerShell)
irm bun.sh/install.ps1 | iex

Pronto! Agora é só usar bun no lugar de npm.

Conclusão (com Reflexão Filosófica)

Organizar código é tipo organizar a vida: todo mundo tem um jeito, mas alguns jeitos definitivamente funcionam melhor que outros.

Esse template é o resultado de anos tentando diferentes abordagens, lendo sobre arquitetura, quebrando a cara e refatorando. É o que eu gostaria de ter quando comecei.

E o melhor? É só um ponto de partida. Pegue, use, modifique, melhore. Contribua com issues e PRs. Vamos construir isso juntos!


Links úteis:


E você, como organiza seus projetos Next.js? Usa uma estrutura parecida? Diferente? Conta aí nos comentários!

Ah, e se usar o template, manda um salve! Adoro saber que tá ajudando alguém. 🚀


PS: Sim, eu sei que existem outras formas de organizar projetos. Sim, todas são válidas. Não, não precisa começar uma guerra nos comentários. 😅

PPS: Se você ainda usa o Vite + React e tá feliz, continua! Nem todo projeto precisa ser fullstack. Use a ferramenta certa pro trabalho certo.

PPPS: Sobre a guerra Bun vs npm/Node: eu escolhi meu lado (Team Bun 🚀), mas você não precisa escolher o mesmo. O importante é que o código rode, não com o que ele roda. Paz e amor (mas velocidade também). ✌️

Carregando publicação patrocinada...
1

Opa, que solução bacana que você chegou nesse problema que eu também passei uns bons meses pensando sobre, até depois de muita pesquisa chegar em uma arquitetura parecida com a sua haha.

Mas uma pergunta pertinente, por qual razão você preferiu separar os components das features?

Por que em vez de:

├── components/       # UI Components (Client)
│   ├── auth/         # Formulários, etc
│   └── profile/      # Componentes de perfil
│
├── features/         # Business Logic (Server Actions)
│   ├── auth/         # login.ts, register.ts
│   └── users/        # get-all-users.ts

Você não usou:

├── features/
│   ├── auth/    
│   │   └── components/  # UI Components (Client)
│   │   └── actions/     # login.ts, register.ts
│   ├── users/
│   │   └── components/  # ...
│   │   └── actions/     # get-all-users.ts

Assim alocando todo o código relacionado a auth e user a um único lugar em vez de dois distintos?

1
1

Boa pergunta. Como eu tô usando mais server actions, eu quase não uso as routes de api.

Mas se eu fosse montar algo, provavelmente seguiria a seguinte base:

/api/v1 para ter uma api rest versionada, chamando la dentro a action responsável por aquele método.

Por exemplo, a rota para retornar todos os produtos da plataforma seria: /api/v1/products/route.ts e dentro do método GET dessa rota, eu chamaria a action getProducts que vai estar dentro de /src/features/products/get-products.ts