Portfólio Blog Sobre

Frontend Showcase

Estrutura React de referência para produtos SaaS, pronta para servir de ponto de partida: login simulado, casca de sistema corporativo, painel com gráficos, uma tabela única com exportação para Excel e PDF, e uma paleta de comandos aberta pelo teclado.

React 19 TypeScript 6 Vite 8 (Rolldown) Tailwind v4 (Oxide) TanStack Router TanStack Table v8 shadcn/ui Recharts Zustand Vitest Playwright
Frontend Showcase

O Frontend Showcase é o par do Backend Showcase, agora do lado do navegador: uma estrutura React pronta para servir de ponto de partida de um produto novo. Ela junta, num app que roda de verdade, as peças que todo SaaS (Software as a Service, software vendido por assinatura e usado pelo navegador) acaba precisando. São elas: a tela de login, a casca de um sistema corporativo, um painel com gráficos, listagens com busca e exportação, e uma paleta de comandos aberta pelo teclado. Cada peça dessas tem um tutorial próprio em algum lugar. O que falta é vê-las juntas, e é isso que o projeto mostra.

A escolha de não ter servidor é proposital. O login é simulado no navegador, a sessão fica no sessionStorage (armazenamento que dura enquanto a aba fica aberta) e as listagens usam dados de exemplo gravados no código. O foco é a forma do frontend: como as telas, o estado, o tema e a navegação se encaixam. O contrato REST (Representational State Transfer, o jeito mais comum de um navegador pedir dados a um servidor) equivalente, com autenticação, paginação e documentação, está no projeto irmão.

Conceitos fundamentais

ConceitoO que é
SaaS (Software as a Service)Produto que roda no navegador e é vendido por assinatura, sem instalação na máquina do usuário.
ERP (Enterprise Resource Planning, sistema corporativo de gestão)Sistema administrativo de empresa. Aqui interessa a casca dele: menu lateral que recolhe, barra fixa no topo e a área de conteúdo no meio.
SPA (Single Page Application, aplicação de página única)Site que troca de tela sem recarregar o navegador. A primeira visita baixa o app inteiro; daí em diante a navegação é instantânea.
Design tokens (variáveis de design)Valores de cor, arredondamento, espaçamento e fonte declarados uma vez, num lugar só. Cada componente lê o token em vez de fixar o valor dentro de si.
oklch (espaço de cor)Forma de escrever cor em que clarear 5% clareia igual em qualquer matiz. Nenhuma cor chama mais atenção que a outra.
Vertical Slice (fatia vertical)Organização do código por funcionalidade (auth, shell, records), não por camada técnica. Cada pasta contém tudo que a funcionalidade precisa.
EtiquetasMarcadores coloridos que classificam um valor (um status, um segmento, uma despesa). A mesma cor significa a mesma coisa na tabela, no formulário e no relatório exportado.
Paleta de comandosCaixa de busca que abre por atalho de teclado e leva a qualquer tela ou ação, sem passar pelo menu.
Busca aproximada (fuzzy-match)Pesquisa que encontra o resultado mesmo com o termo digitado pela metade ou com letra trocada. Digitar “clien” acha “Clientes”.
Carregamento sob demanda (lazy import)Biblioteca pesada que só é baixada quando o usuário aciona a função que precisa dela. Quem nunca exporta uma planilha nunca baixa o código que gera planilhas.
Pacote inicial (bundle)O arquivo de JavaScript que o navegador baixa para abrir o app. Quanto menor, mais rápido a primeira tela aparece.
store (repositório de estado)Lugar único onde fica um dado que várias telas leem, como quem está logado. Cada tela lê da store em vez de receber o dado de mão em mão.
ZustandBiblioteca pequena que cria e gerencia essas stores no React, sem a cerimônia de configuração das alternativas mais antigas.
build (construção)Etapa que monta os arquivos finais do app a partir do código-fonte, antes de publicar.
deploy (publicação)Envio do resultado do build para o servidor que atende o público.

Stack

Backend
Tecnologia Papel
Node.js 24 LTS Runtime local, o ambiente que executa as ferramentas de build
pnpm 11 Gerenciador de pacotes
TypeScript 6 Linguagem
Vitest 3 + Testing Library Testes unitários das peças mais frágeis
Playwright 1 Um teste ponta a ponta que percorre o caminho feliz do app
Biome 2.4 Lint e formatação numa ferramenta só
Frontend
Tecnologia Papel
React 19.2 (com React Compiler) Interface, com o compilador cuidando das otimizações
Vite 8 (motor Rolldown) Build de produção em menos de dois segundos
Tailwind CSS 4.2 (motor Oxide) Estilo escrito em tokens
TanStack Router 1 Rotas derivadas da árvore de arquivos, cada tela num pedaço separado
TanStack Table 8 Motor da tabela: ordenação, filtro e paginação
shadcn/ui Componentes base (Button, Card, Sheet, Command)
Recharts 3 Gráficos do painel, pintados com as cores do tema
react-hook-form 7 + Zod 4 Formulários validados por schema
Zustand 5 Estado global, biblioteca pequena
cmdk 1.1 Paleta de comandos
sonner 2 Avisos flutuantes, acessíveis e empilháveis
date-fns 4 + react-day-picker 9 Calendário em pt-BR sem carregar a biblioteca de datas inteira
ExcelJS 4 + jsPDF 4 Exportação para Excel e PDF, carregada só no clique

A base é Vite com TanStack Router, por dois motivos. O build de produção sai em menos de dois segundos, graças ao motor Rolldown. E o que uma rota faz está escrito no arquivo dela, sem convenção escondida em outro lugar.

Arquitetura: fatia vertical

O código segue Vertical Slice: cada funcionalidade tem os próprios componentes, hooks, schemas, estado e testes na mesma pasta. Quem vai mexer no login abre features/auth/ e encontra ali tudo que o login precisa, sem caçar o formulário numa pasta de componentes e a validação em outra.

A segunda escolha grande responde onde o usuário age. Um sistema corporativo acumula botões, e aqui cada tipo de ação tem um lugar fixo. O que é do usuário fica na barra do topo. O que é da tela fica no cabeçalho da página. O que é do formulário fica numa barra que surge no rodapé quando alguém edita algo. O salto para outra tela fica na paleta de comandos. Quem aprende a regra uma vez sabe onde procurar em qualquer tela.

Padrão Descrição Detalhes
Fatia por funcionalidade auth, shell, action, records, operations e theme, cada uma completa na própria pasta Nenhuma importa código de dentro da outra
Quatro lugares para agir Barra do topo, cabeçalho da página, rodapé do formulário e paleta de comandos Cada lugar cobre uma intenção: usuário, tela, formulário, salto rápido
Barra de salvar que aparece ao editar Salvar e Cancelar deslizam à vista no primeiro campo alterado Com o formulário intocado ela fica escondida do leitor de tela e desativada
Uma tabela para tudo Toda listagem do app passa pelo mesmo componente <DataTable<T>> Ordenação, busca, paginação, carregamento, lista vazia e exportação num contrato só
Tokens declarados uma vez Cor, arredondamento, espaçamento e fonte ficam declarados no bloco @theme do Tailwind Tema claro, escuro e o do sistema, guardados no navegador
Paleta com os ícones do menu O ícone que abre a tela pelo mouse é o mesmo que aparece na busca por teclado A memória visual serve nos dois caminhos

1. Login simulado e guarda de rota

A primeira tela prova que dá para montar um fluxo de login inteiro sem servidor, preservando o que o usuário sente: o botão que gira, o erro que aparece, o sucesso que leva ao painel e a sessão que continua depois de um F5. O estado fica numa store Zustand que grava no sessionStorage. Uma identidade de demonstração é aceita, e o resto é recusado depois de 600 ms de espera simulada.

O arquivo _authed.tsx guarda as rotas internas: quem não fez login volta para /login, e o logout limpa a sessão. É o padrão clássico de uma SPA, sem rede.

Tela de login com credenciais demo pré-preenchidas

Login do Frontend Showcase

Três decisões valem leitura:

  • As credenciais ficam num arquivo só (src/features/auth/demo-credentials.ts). Quem faz uma cópia do projeto acha em segundos o lugar de trocar a senha de demonstração, em vez de caçá-la no meio do componente da tela.
  • sessionStorage no lugar de localStorage: a sessão acaba quando a aba fecha. Combina com a promessa de um login simulado, e evita abrir o projeto seis meses depois já logado, como se ele guardasse alguma coisa de verdade.
  • 600 ms de espera simulada: sem esse atraso, a resposta chega no mesmo instante do clique, e o botão nunca chega a mostrar que está carregando. Com ele, a tela passa pelos mesmos estados de um sistema com rede no meio.

2. Uma tabela para todas as listagens

Listagem é a tela que mais se repete num sistema corporativo. Quando cada uma monta o próprio HTML (HyperText Markup Language, a marcação que estrutura a página), a busca de uma funciona diferente da outra e a terceira nem tem paginação. Aqui toda lista passa por um componente só. Ele é simples o bastante para servir o cadastro de clientes e o módulo financeiro sem mudar de forma.

Listagem financeira com etiquetas coloridas e botões na barra

Financeiro com etiquetas coloridas e paginação

Oito decisões sustentam esse componente:

Padrão Descrição Detalhes
Ordenação em três estados Clicar no cabeçalho cicla entre ordem original, crescente e decrescente O terceiro clique volta ao início; nenhum menu extra para o usuário aprender
Busca única Um campo filtra a tabela inteira, com botão de limpar dentro dele O texto de exemplo do campo muda conforme a tela
Paginação no navegador Seletor de 5, 10 ou 20 linhas por página Sem ida ao servidor, porque não há servidor: tudo é processado na máquina do usuário
Linhas alternadas Uma branca, uma cinza-claro, para o olho não pular de linha O realce do mouse e o da seleção passam por cima sem apagar o padrão
Carregando e vazio fazem parte do contrato O componente já desenha os dois estados por padrão A tela vazia aceita um conteúdo próprio, em vez de uma frase genérica
Espaço livre na barra de ferramentas Os botões de criar entram à esquerda; busca e exportação ficam à direita A tabela recebe os botões como conteúdo, sem uma propriedade nova para cada caso
Exportação carregada no clique As bibliotecas de Excel e de PDF só são baixadas quando alguém exporta Elas ficam fora do pacote inicial e só são baixadas no clique
O nome do arquivo identifica a tela de origem Mesmas cores, título e rodapé da tela, com um código curto no nome O padrão é showcase-<arquivo>-<código>.<extensão>
PDF exportado mantendo as etiquetas coloridas

PDF exportado com etiquetas coloridas

Princípio de design. A cor de uma etiqueta significa a mesma coisa em todo lugar. Segmento é azul, verde ou âmbar. Status é verde ou cinza. No financeiro, entrada é verde e saída é rosa. A regra vale na célula da tabela, na caixa de seleção do formulário e dentro da planilha exportada. Uma função chamada ExportRichCell redesenha as etiquetas no Excel e no PDF, para o relatório parecer uma continuação da tela.

3. Tema azul-petróleo e tokens semânticos

Tema escuro virou item comum, e a escolha que resta é qual cor entra no lugar do preto absoluto, que produz o contraste mais alto possível contra o texto claro. A inspiração veio do lucide.dev, cujo tema escuro usa um azul-petróleo. A partir dele, subi a luminosidade em 1% e ajustei o matiz, o que dá à paleta uma assinatura própria.

O que sustenta essa paleta são os tokens semânticos. Cor, arredondamento e tipografia ficam declarados uma única vez, no bloco @theme do Tailwind. Cada componente lê essas variáveis em vez de guardar o valor por conta própria. Assim, trocar de tema é trocar a tabela de tokens, e nenhum componente precisa ser tocado.

No tema escuro, a luminosidade organiza a tela. O menu lateral fica um degrau mais escuro que o fundo, e a atenção vai para o conteúdo. O card fica um degrau mais claro que o fundo, com espaçamento em volta. A borda usa um tom intermediário, que separa sem chamar mais atenção que o texto. Os elementos escuros dos dois temas têm a mesma matiz, com croma muito baixo (entre 0.003 e 0.008). Por isso trocar de tema parece uma transição contínua, e não um corte de cor.

Os tokens valem além da tela. Os gráficos do painel leem a mesma paleta, e ela é aplicada também nas planilhas exportadas. Mudar um token muda a cor do card, da barra do gráfico e da célula do Excel de uma vez só.

A preferência de tema fica no localStorage, e um script no <head> a lê antes da primeira pintura da tela. Sem esse script, quem escolheu o tema escuro veria um flash branco a cada carregamento, no intervalo entre o HTML chegar e o React montar.

A rota /design-system, disponível com o app rodando, mostra os tokens ao vivo: superfícies, ações, cores dos gráficos, etiquetas, variantes de botão, espaçamento, arredondamento e a razão de cada escolha. Ela traz também uma seção Segurança em produção, que detalha o que muda ao trocar o login simulado por um servidor real.

4. Paleta de comandos

O atalho ⌘K no Mac, ou ⌃K no Linux e no Windows, abre uma caixa de busca que percorre as páginas e as ações rápidas do app. A busca é aproximada: digitar “clien” já encontra “Clientes”, e uma letra trocada não atrapalha.

Os ícones dessa caixa são os mesmos do menu lateral. Quem usa o mouse e quem usa o teclado enxergam o mesmo símbolo para a mesma tela. A lista sai da mesma configuração de navegação que desenha o menu, então uma tela nova aparece nos dois lugares sem trabalho extra.

A inspiração vem de GitHub, Linear e Vercel, produtos em que a paleta virou o caminho principal de navegação.

5. Segurança: o que o navegador não esconde

Um projeto sem servidor deixa à vista um limite que produtos reais costumam esconder: tudo que chega ao navegador é público. Não existe chave de acesso neste projeto, e não haveria onde guardá-la. Qualquer variável de ambiente escrita como import.meta.env.VITE_* sai legível no JavaScript entregue ao usuário. Um segredo de verdade fica no servidor.

A guarda de rota tem o mesmo limite. O _authed.tsx melhora a navegação de quem não fez login, e não faz mais que isso. Quem editar o sessionStorage pelas ferramentas do navegador alcança as telas do mesmo jeito. Neste projeto isso é aceitável, porque não existe dado real atrás delas. Num sistema de verdade, quem decide o que o usuário pode ver é o servidor, a cada requisição. A tela só mostra a decisão que já foi tomada lá.

Deixar as duas coisas escritas evita que alguém copie a estrutura e leve a conveniência da simulação para dentro de um produto com dado de cliente.

Decisões pequenas com efeito grande

Padrão Descrição Detalhes
Pacote inicial enxuto As bibliotecas de exportação entram só no clique Quem nunca exporta não baixa nenhuma delas
A mesma cor em todas as camadas A mesma paleta na tabela, no formulário, no gráfico e no relatório ExportRichCell leva o token para dentro do Excel e do PDF
Arquivo nomeado por domínio e operação customer.schema.ts, login.form.tsx Achar o arquivo de uma funcionalidade vira uma busca óbvia
Rota desconhecida devolve o app O Worker responde com o index.html em qualquer caminho que não reconhece Sem isso, um F5 em /dashboard daria erro 404
Testes nas peças de maior risco Vitest nas peças frágeis, Playwright no caminho feliz A cobertura segue o risco de cada peça

O deploy sai em Cloudflare Workers. O build estático da pasta dist/ é servido pelo Workers Static Assets, publicado em fs.thiagocaja.dev, e cada push na main dispara build e deploy.

A linha not_found_handling: "single-page-application" no wrangler.jsonc faz o Worker devolver o index.html sempre que o caminho pedido não existe como arquivo. Uma SPA depende disso: /dashboard não é uma pasta no servidor, é uma rota que existe só dentro do React. Sem essa linha, um F5 nessa tela daria erro 404.

Convenções

  • Código em inglês: identificadores, nomes de arquivo e caminhos de rota. Quem chega no projeto não traduz termos no meio da leitura.
  • Interface em pt-BR: rótulos, textos de acessibilidade, campos, botões e títulos de página, na língua do usuário final.
  • Arquivos nomeados por domínio e operação (customer.schema.ts, login.form.tsx).
  • Variáveis de sim ou não começam por um prefixo que faz a pergunta: isDirty, isSaving, hasError, shouldRemember.

Status

AspectoEstado
MaturidadePronta para servir de ponto de partida, em evolução
Cobertura de testesUnit (Vitest) + um teste ponta a ponta (Playwright)
DisponibilidadeLive em Cloudflare Workers (fs.thiagocaja.dev)
RepositórioPrivado, código sob solicitação
MonetizaçãoNenhuma, projeto de portfólio

Backend e frontend formam um par: um serve o contrato REST com autenticação, paginação e documentação viva; o outro consome esse contrato numa interface React. Juntos mostram a mesma aplicação de ponta a ponta, cada lado como um artefato que se sustenta sozinho.