UX Engineering
Meu portfólio com Next.js, Tailwind CSS, GSAP e Lenis
Veja decisões reais do meu portfólio com Next.js, Tailwind CSS, GSAP, ScrollTrigger e Lenis, conectando organização de código, movimento e experiência.

Um portfólio com animações pode chamar atenção e, ao mesmo tempo, dificultar a leitura dos projetos. Ao construir meu portfólio com Next.js, precisei conciliar essas duas forças: expressar personalidade e permitir que alguém entenda meu trabalho, encontre uma case e entre em contato.
Este texto descreve a implementação do projeto na data da publicação. Não é uma auditoria de performance nem um relato de aumento de conversão. Vou mostrar decisões verificáveis no código, o motivo pelo qual fazem sentido e pontos que ainda merecem revisão.
A estrutura do portfólio com Next.js
A página inicial compõe seções separadas: apresentação, serviços, galeria, projetos selecionados, processo, perguntas frequentes, projetos pessoais, depoimentos e contato. O arquivo da página organiza essa sequência; cada seção mantém sua responsabilidade visual e de conteúdo.
Essa divisão ajuda a discutir produto antes de discutir animação. Projetos de clientes e produtos pessoais, por exemplo, aparecem em áreas distintas. Quem está avaliando uma entrega profissional consegue seguir uma narrativa diferente de quem quer conhecer meus experimentos.
As páginas de cases reaproveitam estrutura e dados. O blog usa arquivos Markdown e MDX. Essa escolha permite alterar textos sem duplicar o layout de cada página e mantém o conteúdo perto do versionamento do projeto.
Tailwind CSS e CSS Modules cumprem papéis diferentes
O projeto usa Tailwind para composição, espaçamento e estilos de vários componentes. Algumas áreas com efeitos específicos usam CSS Modules. Não tentei transformar toda regra em uma única convenção por princípio.
Em uma revisão de interface, classes utilitárias ajudam a enxergar a relação entre largura, espaçamento e breakpoint. O Tailwind trabalha com uma abordagem responsiva mobile-first: os estilos sem prefixo são a base, e os prefixos aplicam variações a partir dos breakpoints. Documentação de responsividade.
Minha decisão prática é manter regras que mudam juntas próximas. Uma composição simples pode ficar clara no JSX; uma sequência com pseudo-elementos e estados complexos pode ser mais fácil de revisar num módulo de CSS. O critério é legibilidade durante a manutenção, não a quantidade de classes.
O texto fixo dos projetos usa sticky
Na seção de projetos selecionados, a coluna de apresentação permanece visível enquanto os cards passam ao lado. O código usa position: sticky com um afastamento de 96px do topo.
Isso atende a uma intenção de UX: conservar contexto durante a exploração. O visitante continua vendo o título e os atalhos enquanto examina diferentes trabalhos.
Sticky depende do contêiner e dos ancestrais de rolagem. Uma regra de overflow ou uma coluna sem altura disponível pode mudar o resultado. Referência de posicionamento CSS.
Eu começaria por essa solução nativa sempre que a necessidade fosse apenas manter uma coluna visível. Só adicionaria uma cena com pin quando houvesse uma sequência que realmente dependesse do progresso do scroll.
GSAP e ScrollTrigger coordenam a narrativa
O projeto centraliza o registro de ScrollTrigger e oferece um hook para criar animações em um contexto delimitado. No encerramento, esse contexto é revertido.
gsap.context() permite agrupar animações e reverter o trabalho registrado. Isso é especialmente útil em interfaces que montam e desmontam trechos. Documentação de gsap.context.
No código do portfólio, a configuração é adiada por um frame e registrada dentro do contexto. A limpeza cancela esse frame e chama revert(). O detalhe é importante: uma animação criada depois precisa continuar pertencendo ao ciclo de vida que vai encerrá-la.
Eu considero esse cuidado parte da qualidade visual. Elementos presos, estilos acumulados e wrappers de pin abandonados aparecem para a pessoa como falhas de layout, embora a causa esteja na gestão de recursos.
Lenis e GSAP precisam acompanhar o mesmo tempo
O hook useLenis conecta a atualização do scroll ao ScrollTrigger e usa o ticker do GSAP para avançar o Lenis. O trecho central da implementação é:
// Trecho do hook: lenis, gsap e ScrollTrigger já foram inicializados.
lenis.on('scroll', ScrollTrigger.update);
const tick = (time: number) => {
lenis.raf(time * 1000);
};
gsap.ticker.add(tick);
// Na limpeza do efeito:
gsap.ticker.remove(tick);
lenis.off('scroll', ScrollTrigger.update);
lenis.destroy();
O ticker recebe segundos; lenis.raf espera milissegundos. A integração oficial mostra essa conversão. Ao usar um ticker externo, não adicionaria outro loop automático para atualizar a mesma instância. README do Lenis.
O hook atual evita inicializar Lenis quando a largura inicial é inferior a 768px. Isso é uma decisão específica deste projeto, não uma conclusão de que suavização é sempre ruim no mobile. Também há um limite: a condição é avaliada na montagem, não como uma política completa de mudança de breakpoint durante toda a sessão.
O hero tem uma alternativa ao efeito visual
O fundo do hero é carregado dinamicamente, com renderização de servidor desativada para esse componente visual. Enquanto ele não está pronto, existe uma composição alternativa em CSS. O componente também considera preferência por movimento reduzido e escolhe qualidade diferente para mobile.
A intenção é separar mensagem e decoração. O título e a ação principal não deveriam depender da disponibilidade de um shader para fazer sentido.
Isso não prova que o carregamento está otimizado. O próximo passo de avaliação seria medir o custo em aparelhos reais, observar o primeiro conteúdo relevante e revisar consumo durante a permanência na página. Carregamento dinâmico é uma técnica, não um resultado de performance.
Quando eu usaria isso em um projeto real?
Usaria uma combinação semelhante em um portfólio ou apresentação de produto onde o movimento ajuda a contar uma história. Em um serviço de consulta rápida, escolheria uma experiência mais direta, com menos trechos controlados pelo scroll.
Minha divisão inicial seria:
| Responsabilidade | Ferramenta usada aqui |
|---|---|
| Rotas e composição de páginas | Next.js |
| Layout e variações responsivas | Tailwind e CSS Modules |
| Sequências de animação | GSAP |
| Relação entre sequência e rolagem | ScrollTrigger |
| Suavização da rolagem | Lenis |
- Responsabilidade
- Rotas e composição de páginas
- Ferramenta usada aqui
- Next.js
- Responsabilidade
- Layout e variações responsivas
- Ferramenta usada aqui
- Tailwind e CSS Modules
- Responsabilidade
- Sequências de animação
- Ferramenta usada aqui
- GSAP
- Responsabilidade
- Relação entre sequência e rolagem
- Ferramenta usada aqui
- ScrollTrigger
- Responsabilidade
- Suavização da rolagem
- Ferramenta usada aqui
- Lenis
Cada dependência precisa justificar sua responsabilidade. Se a demanda for apenas um hover, CSS resolve sem introduzir uma timeline.
Erros comuns
Presumir que uma regra global resolve movimento reduzido
O projeto tem tratamento de movimento reduzido em partes da interface, mas o hook atual de Lenis não impede sua inicialização por essa preferência. Eu revisaria essa política antes de declarar conformidade completa. Uma regra CSS não encerra automaticamente loops JavaScript.
Levar a composição desktop inteira ao mobile
Colunas estreitas, alturas grandes e elementos fixos podem prejudicar a leitura. Eu reavaliaria a ordem e o espaço disponível, incluindo tablets em retrato, em vez de apenas diminuir a fonte.
Confundir intenção com resultado medido
Dizer que uma técnica foi escolhida para reduzir custo é diferente de provar que ela reduziu LCP ou INP. Este artigo descreve implementação; resultados exigem medição comparável.
Conclusão
Construir meu portfólio com Next.js envolve mais do que reunir bibliotecas. A organização das cases, a persistência do contexto e a previsibilidade das interações precisam funcionar juntas. O movimento vale a pena quando reforça esse caminho; quando começa a competir com a leitura, é a composição que precisa ser revista.



