Voltar para Artigos
React & Next.js

Como Estruturar Arquiteturas Limpas no Next.js com App Router e Tailwind CSS

Fábio Flauzino
Fábio Flauzino
Senior Front-End Engineer
22 de jul. de 2026
5 min de leitura
Como Estruturar Arquiteturas Limpas no Next.js com App Router e Tailwind CSS

Introdução

Uma das maiores mudanças trazidas pelo Next.js 13+ foi a forma como pensamos a arquitetura das aplicações. Com o App Router, deixamos de separar apenas páginas e componentes para começar a definir claramente onde cada parte da aplicação deve ser executada: no servidor ou no navegador.

Essa mudança pode parecer pequena, mas impacta diretamente a performance, a organização do projeto e a experiência de desenvolvimento.

Uma boa arquitetura não serve apenas para o computador entender seu código. Ela existe para que outros desenvolvedores — inclusive você no futuro — consigam evoluí-lo com segurança.

Neste artigo, compartilho algumas práticas que utilizo para estruturar aplicações em Next.js, aproveitando React Server Components, Tailwind CSS e uma organização de pastas que facilita a escalabilidade do projeto.


O Problema

É comum encontrar projetos onde um único componente é responsável por:

  • buscar dados da API;
  • controlar estados;
  • manipular eventos;
  • renderizar a interface;
  • aplicar regras de negócio.

Enquanto a aplicação é pequena, isso funciona.

Mas conforme novas funcionalidades surgem, começam a aparecer problemas como:

  • excesso de JavaScript enviado ao navegador;
  • componentes gigantes;
  • reutilização difícil;
  • alto acoplamento entre interface e lógica;
  • manutenção cada vez mais complexa.

Além disso, equipes maiores acabam enfrentando conflitos constantes porque qualquer alteração afeta várias partes da aplicação.


Definindo Fronteiras

O App Router incentiva um conceito muito importante: separar responsabilidades.

Em vez de criar componentes que fazem tudo, cada camada passa a ter uma função específica.

Uma regra simples costuma funcionar muito bem:

Tudo é Server Component até existir um motivo para virar Client Component.

Ou seja, só utilize "use client" quando realmente precisar de:

  • useState;
  • useEffect;
  • eventos (onClick, onChange, etc.);
  • acesso ao window;
  • APIs do navegador.

Todo o restante pode permanecer sendo renderizado no servidor.


Buscando Dados no Servidor

Os Server Components são ideais para carregar dados, já que essa execução acontece antes mesmo da página chegar ao navegador.

// app/dashboard/page.tsx

import { fetchUserData } from "@/lib/api";
import UserCard from "./components/UserCard";

export default async function DashboardPage() {
  const user = await fetchUserData();

  return (
    <main className="mx-auto max-w-7xl p-6">
      <h1 className="mb-8 text-3xl font-bold">
        Dashboard
      </h1>

      <UserCard user={user} />
    </main>
  );
}

Observe que nenhuma lógica de estado é necessária.

O componente apenas busca os dados e renderiza a página.

Como consequência:

  • menos JavaScript é enviado ao navegador;
  • melhor performance;
  • melhor SEO;
  • menor tempo de carregamento.

Isolando a Interatividade

Quando a interface precisa responder às ações do usuário, utilizamos um Client Component.

"use client";

import { useState } from "react";

type User = {
  name: string;
};

interface UserCardProps {
  user: User;
}

export default function UserCard({ user }: UserCardProps) {
  const [active, setActive] = useState(false);

  return (
    <div className="rounded-2xl border border-neutral-800 p-6">
      <h2 className="text-xl font-semibold">
        {user.name}
      </h2>

      <button
        onClick={() => setActive(!active)}
        className="mt-4 rounded-lg bg-blue-600 px-4 py-2 text-white"
      >
        {active ? "Usuário ativo" : "Ativar usuário"}
      </button>
    </div>
  );
}

Perceba que esse componente não sabe como os dados foram obtidos.

Sua única responsabilidade é oferecer uma interface interativa.


Organização de Pastas

Outro ponto importante é organizar o projeto pensando em funcionalidades, e não apenas em tipos de arquivos.

Uma estrutura semelhante a esta costuma escalar muito bem:

app/
├── dashboard/
│   ├── page.tsx
│   ├── loading.tsx
│   ├── error.tsx
│   └── components/
│       ├── UserCard.tsx
│       └── UserStats.tsx
│
components/
├── ui/
├── layout/
└── common/

lib/
├── api.ts
├── auth.ts
└── utils.ts

services/
hooks/
types/

Essa organização facilita encontrar arquivos relacionados à mesma funcionalidade e reduz a necessidade de navegar por dezenas de diretórios diferentes.


Tailwind CSS Sem Poluir o Código

Uma crítica comum ao Tailwind é que os componentes acabam ficando cheios de classes utilitárias.

Isso normalmente acontece quando não existe uma camada de componentes reutilizáveis.

Em vez de repetir dezenas de classes em toda aplicação, prefira criar componentes reutilizáveis como:

  • Button
  • Card
  • Badge
  • Modal
  • Input

Assim, toda a identidade visual fica centralizada e qualquer alteração pode ser feita em um único lugar.

Essa abordagem também facilita a adoção de bibliotecas como shadcn/ui, que seguem exatamente essa filosofia.


Algumas Boas Práticas

Ao trabalhar com App Router, algumas decisões costumam trazer ótimos resultados:

  • mantenha Server Components como padrão;
  • utilize Client Components apenas quando necessário;
  • concentre chamadas de API no servidor;
  • extraia componentes reutilizáveis;
  • organize arquivos por funcionalidade;
  • mantenha regras de negócio fora dos componentes visuais;
  • evite estados globais quando um estado local resolve o problema.

Esses pequenos cuidados fazem bastante diferença conforme o projeto cresce.


Conclusão

O App Router não trouxe apenas uma nova forma de criar rotas.

Ele mudou a maneira como estruturamos aplicações React.

Separar claramente responsabilidades entre servidor e cliente resulta em aplicações mais rápidas, mais organizadas e muito mais fáceis de evoluir.

Aliado ao Tailwind CSS, a uma boa organização de pastas e ao uso de componentes reutilizáveis, é possível construir projetos que permanecem limpos mesmo após meses de desenvolvimento.

Nos próximos artigos, vou mostrar como conectar essa arquitetura com fluxos de automação utilizando n8n e, posteriormente, integrar modelos de Inteligência Artificial para criar aplicações ainda mais inteligentes.

Gostou deste artigo? Compartilhe com sua rede.