Estruturação de Folders para projetos Next.js

O desenvolvimento web é uma área na computação que se mostra extremamente volátil. A cada ano um novo framework mostrando o quanto é importante estar se renovando e se adaptando às novas tecnologias. Contudo, com essas novas arquiteturas, não houve tempo para amadurecer boas práticas universais, problema que se revela ao realizar projetos grandes utilizando essas ferramentas. Mesmo assim, certas ideias se provaram eficazes, especialmente em relação a estruturação das pastas em um projeto.

O framework de javascript para o frontend mais popular, atualmente, é o React, através do qual foi desenvolvido o Next.js, um framework do React que funciona como servidor para distribuir esse frontend da forma desejada pelo desenvolvedor. Utilizaremos ele como referência para apresentar nossa visão do que seria uma estrutura escalável e de fácil manutenção, mas as ideias se mantém para qualquer arquitetura que possibilita a implementação de componentes funcionais.

É importante ressaltar que estruturação de diretórios é algo extremamente dependente do projeto, sendo essencial ter flexibilidade para adequar sua estrutura a seu projeto. O mais importante é garantir que outros desenvolvedores consigam entender e modificar sem muito esforço, além disso, que seja imediato o entendimento do padrão que ele deve seguir.

Copy to Clipboard

App

Essa pasta é obrigatória para o Next.js, substituindo a pasta “pages” e sendo a responsável pela definição das rotas das páginas. Os arquivos nesta pasta devem ser o mais compacto o quanto possível, por isso, o UI será importado da pasta “containers”. É aqui onde encontrará se a página será construída estaticamente no build (SSG), renderizada pelo servidor (SSR), pelo cliente (CSR) ou de forma incremental (ISR), além de definir os contextos associados à página (digamos por exemplo, só pode entrar na página quem ter feito login). Devendo também ser usada para incluir arquivos que o Next.js oferece, o que foge do nosso escopo nos aprofundarmos neles.

Além disso, caso não queira utilizar o folder “styles”, inserir estilos globais nesta pasta (para alimentar ao _app.tsx ou _app.js) pode ser a melhor opção para projetos menores e com poucas restrições de estilização ou que não utilizem de bibliotecas externas que requerem configuração.

Assets e Public

Os objetivos destas páginas são similares, possibilitar acesso a arquivos estáticos. Em Next.js, arquivos na pasta public são disponibilizados para serem acessíveis externo ao servidor. Normalmente, dentro dessa pasta, tem pastas como “imgs”, “favicon”, “videos”, etc. É interessante uma só pasta para favicon especialmente se pensa em criar um PWA, possibilitando o usuário fixar em seu celular o site como se fosse um aplicativo.

Caso queira arquivos estáticos exclusivos ao servidor, como por exemplo fontes, imagens e icones, seria o caso de usar a pasta “assets”. Isso vale para todas pastas, é importante que dentro delas não haja arquivos demais. Se estiver assim, talvez seja o caso subdividir em novas pastas internas.

Components

Consistem em componentes funcionais que podem ser usados em qualquer parte do site. Por exemplo: botões, tabelas, inputs, etc. Essa pasta pode ficar enorme, por isso é interessante fazer subpastas para cada utilidade associada ao componente. Por exemplo, uma pasta “form” teria componentes associados a realizar um form, outra “modal” para os diversos modais.

Em geral, os componentes são apenas interfaces para o usuário. Caso tenham funções complexas, necessitem usar hooks, requisições específicas e/ou diversos componentes exclusivos a esse funcionalidade seriam destinadas a serem inseridas na pasta features.

Containers

Eu diria que, de todas as pastas, essa é a mais importante que seja bem organizada. É aqui que será “construída” a página. Dentro desta pasta pode-se incluir pastas associadas a cada contexto. Por exemplo:

Copy to Clipboard

Nesse exemplo, observamos que no contexto de um Admin, temos 3 páginas, uma das quais é o Users. Além disso, temos templates, onde é criado alguma estrutura que será repetida entre algumas dessas páginas, por exemplo, um Sidebar, um Footer, etc.

Além disso, há os hooks, que são funções que normalmente misturam useState, useEffect, useMemo, useSWR, etc para fazer algo mais complexo. Por exemplo: poderia haver um “useAdmin”, função que se responsabiliza por obter os dados do Admin, ou “useUser”, que pode retornar funções de deletar, adicionar ou modificar usuário para o banco de dados.

Já em Users, temos index.tsx, que é a página em si. Tems também a pasta fragments. Ela contém os diversos fragmentos que são unidos em index.tsx para fazer a página. São, basicamente, os componentes exclusivos dessa página, que podem consistir no agrupamentos de outros componentes globais.

Há, também, os hooks associados especificamente a esta página. Na prática, em projetos onde backend e frontend estão sendo desenvolvidos, simultâneamente, esses hooks podem “fingir” carregar dados (chamamos de “mocks”) para conseguir fazer a interface mesmo sem API. Quando a API for construída, só precisa-se editar esses hooks e nada quebra.

A essência do container é estruturar a página. A parte de requisição de dados, por exemplo, é interessante deixar em “services”, deixando um hook ser responsável por utilizar essa funcionalidade e possibilitando o container de utilizar esses dados.

De certo modo, isso é uma extensão de um padrão de design de código chamado de “Facade Pattern”. Vamos ver esse padrão motivando a criação da pasta lib, config, etc. Basicamente, imagina que você utiliza um site para fornecer dados de tempo, mas agora o serviço deles morreu e você precisa usar outro totalmente diferente. A ideia é que tudo associado a esse serviço esteja em só um arquivo e não distribuído em todo seu projeto para que, caso queira mudar, seja totalmente trivial.

Config

Dados de ambiente como senhas, urls (end points), etc são inseridas no arquivo env, além de outros dados que não queira disponível ao público. Contudo, se usar esses dados de forma direta pode-se ter um problema. Imagina que se queira trocar o nome da variável, ou por motivos de segurança se quer que ela seja obtida por uma requisição a outro servidor. Para todas essas alterações precisaria fazer grandes alterações no código. Por isso, a página “configuração”, para exportar esses dados de configuração, ou até mesmo especificações.

Contexts

Contextos são basicamente componentes que você envolve em seus containers, para garantir que certas condições estão sendo seguidas. Por exemplo, você quer que apenas Admin possa acessar uma página, então cria-se um arquivo AdminProtectedRoute.tsx, dentro do qual fará a verificação se o usuário é realmente um admin. Note que o “children” só será retornado se tiver garantia. Pode até incluir uma tela de loading enquanto ainda não tem a verificação. A pasta contexts contém os diversos contextos do aplicativo.

Features

Em geral, essa pasta tem mais praticidade em projetos grandes. Um feature é, basicamente, uma mini-biblioteca no seu projeto. Essa biblioteca pode retornar diversos componentes, hooks e funções associadas ao nome desse feature. Por exemplo, pode-se criar a pasta SMS e nela você pode retornar um modal que pede um código enviado ao celular da pessoa, além disso, funções de reenvio de código, etc. Assim, pode-se inclusive retornar componentes, mas com funcionalidades robustas e escopo delimitado. Dentro de cada funcionalidade podemos ter pastas de componentes, hooks e services, que estão delimitadas ao escopo desta.

Lib

Quando sua aplicação é grande, trocar uma biblioteca por outra ou substituí-la por uma solução própria, tornará necessária a alteração da aplicação inteira. Se modificar todos esses arquivos, pode gerar diversos conflitos, especialmente considerando que está usando git, e cada um tem sua branch. Todo esse retrabalho pode ser evitado, se utilizar essa pasta. Nela estará exportando as diversas bibliotecas. Por exemplo, pode-se ter um arquivo React-Charts.js, todos imports no resto do projeto vão se referenciar a este arquivo.

Utils

Funções de utilidade são basicamente funções de javascript com objetivos muito bem definidos. Por exemplo, uma função que transforma um conjunto de números no formato de telefone válido. Novamente, se tiver muitos arquivos é inteligente criar subpastas para organizar as diversas funções em suas respectivas aplicabilidades.

Hooks

Pastas para inserir hooks que podem ser utilizados em qualquer lugar no projeto. Por exemplo, useDebounce (caso queira rodar uma função apenas se não tiver input do usuário após algum período), useLocalStorage, etc.

Providers

Uma das features mais importante do React para o desenvolvimento de código limpo são os providers. Os providers são utilizados criando um contexto no qual armazenará variáveis. Todo componente dentro desse contexto poderá acessar esses dados diretamente. Dessa forma não precisará passar variáveis de filho para filho. Essa pasta deve conter todos providers desejados e arquivos que os agrupam conforme necessários (se for o caso).

Services

Qualquer requisição a uma API, seja ela REST (na Web) como local (banco de dados por exemplo), deverá ser inserida nesta pasta. De certo modo, é uma forma direta de separar o trabalho do Frontend e do Backend, de tal forma que, nenhum dependa necessariamente do outro para realizar seu trabalho. Pode ter arquivos ou pastas especificas para cada serviço, por exemplo, firebase, wordpress, etc.

Styles

Para inserir arquivos css globais, além de configurações de css associados a certas bibliotecas. Por exemplo, em MUI, pode ter uma pasta themes para inserir as configurações gerais associados.

Conclusão

Ao apresentar essa estrutura de arquivos, estamos oferecendo ideias que facilitam o desenvolvimento de projetos grandes. Esses ideias devem ser adaptadas para a necessidade de cada projeto. A importância de padrões são os princípios por trás deles, oferecendo maturidade ao desenvolvedor para tomar decisões escaláveis e manuteníveis. Além disso, para questões de ser não se aprofundar muito em casos de uso, não foram mencionados certas pastas, associados a realização de testes.

Existem diversas outras pastas que pode-se incluir. Um exemplo seria para utilizar o Storybook, uma ferramenta muito útil para desenvolvimento frontend. Através dessa ferramenta pode-se visualizar e testar os diversos componentes e features desenvolvidos de forma prática e organizada.

Através de boas práticas, a consistência de entrega do desenvolvedor, a redução do retrabalho, além da sintonia entre as diversas partes da equipe, são aprimorados. Projetos que buscam iniciar sem considerar princípios bem estruturados, iniciam rápido e progressivamente vão perdendo eficácia. Pastas desorganizadas e enormes impossibilitam a fácil reutilização de código, dificultando a sua manutenção e a sua alteração. Em geral, foi apresentado uma possível estrutura e o que, na nossa experiência, trouxe menos dor de cabeça, contudo, o maior foco deve ser nas ideias que ela oferece.