Site de portfólio pessoal
Um portfólio moderno e focado em conteúdo criado com Next.js 16, React 19 e TypeScript. Criado do zero com desenvolvimento assistido por IA.
Durante anos, eu disse a mim mesmo que criaria um site pessoal. O domínio acumulou poeira. A ideia nunca saiu do aplicativo de anotações.
Então, parei de planejar e comecei a criar. Dois dias depois, este site estava no ar.
O que mudou
Eu costumava pensar que entregar um site significava semanas de trabalho. Escolher fontes. Depuração de layouts. Escrever um texto que nunca parecia certo. Dessa vez, eu tinha uma abordagem diferente: criar com IA, entregar rapidamente e melhorar depois.
O resultado: 51 confirmações em 48 horas. Um blog com narração em áudio. Um formulário de contato que realmente funciona. Um sistema de design que posso ampliar. Não é perfeito, mas é real.
O que me surpreendeu não foi a velocidade. Foi a sensação do trabalho. Menos pesquisa, mais construção. Menos dúvidas, mais entrega. As ferramentas cuidaram das partes tediosas para que eu pudesse me concentrar no que importava.
Esta página é a história completa. Como foi montado, o que está por trás e o que eu faria diferente da próxima vez.

Pilha técnica
| Category | Technology | Version | Purpose |
|---|---|---|---|
| Framework | Next.js (App Router) | 16.0.10 | Server components, routing, SSR |
| UI Library | React | 19.2.3 | Component architecture |
| Language | TypeScript | 5.x | Type safety |
| Styling | Tailwind CSS | 4.x | Utility-first styling |
| Components | shadcn/ui | New York | Pre-built accessible components |
| Content | next-mdx-remote | 5.0.0 | MDX rendering with frontmatter |
| Parsing | gray-matter | 4.0.3 | Frontmatter extraction |
| Icons | Lucide React | 0.561.0 | SVG icon library |
| SendGrid | 8.1.6 | Contact form delivery | |
| Analytics | Google Analytics 4 | — | Traffic and behavior tracking |
| Audio | ElevenLabs Audio Native | — | Text-to-speech for blog posts |

Arquitetura
O aplicativo segue as convenções do Next.js App Router com uma separação clara entre páginas, componentes, conteúdo e utilitários.
ftchvs_26/
├── app/ # Páginas do roteador de aplicativos Next.js
│ ├─── layout.tsx # Layout raiz com fontes e navegação
│ ├─── page.tsx # Página inicial
│ ├─── globals.css # Estilos globais e tokens de design
│ ├─── about/ # Página sobre
│ ├─── blog/ # Listagem de blogs e páginas [slug]
│ ├─── projects/ # Listagem de projetos e páginas [slug]
│ ├─── contato/ # Formulário de contato com rota de API
│ └─── currículo/ # Página de currículo
├─── componentes/ # Componentes React
│ ├─── nav.tsx # Navegação com folha móvel
│ ├─── theme-toggle.tsx # Alternância entre os modos escuro e claro
│ ├─── tts-player.tsx # Reprodutor de áudio da ElevenLabs
│ ├─── mdx-content.tsx # Renderizador MDX
│ └─── ui/ # componentes shadcn/ui
Conteúdo/ # arquivos de conteúdo MDX
│ ├─── blog/ # Postagens de blog
│ ├─── páginas/ # Páginas estáticas (sobre)
│ └─── projetos/ # Estudos de caso de projetos
├─── lib/ # Utilitários
│ ├─── content.ts # Carregamento e análise de conteúdo
│ ├─── constants.ts # Configuração do site
│ ├─── schema.tsx # Dados estruturados JSON-LD
│ └── utils.ts # Funções auxiliares
└─── public/ # Ativos estáticos
Fluxo de dados
O conteúdo flui dos arquivos MDX por meio de lib/content.ts para as páginas:
- Arquivos MDX armazenam o conteúdo com o frontmatter YAML
- **O gray-matter analisa o frontmatter em objetos tipados
- O next-mdx-remote serializa o MDX para renderização do React
- Componentes de página buscam conteúdo no momento da solicitação
- Componentes MDX renderizam o HTML final

Sistema de design de tipografia
Criei um sistema de tipografia baseado em tokens para criar um estilo consistente em todas as páginas. Cada token é mapeado para classes específicas do Tailwind.
| Token | CSS Class | Styles | Usage |
|---|---|---|---|
| Page Title | .text-page-title | text-xl font-medium mb-6 | H1 headings |
| Section Title | .text-section-title | text-lg font-medium mt-8 mb-4 | H2 headings |
| Subsection | .text-subsection-title | text-base font-medium mt-6 mb-3 | H3 headings |
| Body | .text-body | text-[15px] leading-relaxed text-foreground/90 | Paragraphs |
| Body Container | .text-body-container | space-y-4 text-[15px] leading-relaxed | Multiple paragraphs |
| Meta | .text-meta | text-[14px] text-muted-foreground | Dates, tags, labels |
| Link | .text-link | underline underline-offset-2 hover:text-foreground | Inline links |
| List Item | .text-list-item | text-sm text-foreground/80 | Card and list text |
Pilha de fontes
- Geist Sans** - Tipo principal para o corpo do texto e a interface do usuário
- Geist Mono** - Blocos de código e o logotipo do terminal
Ambas as fontes são carregadas via next/font/google com display: swap para otimizar o desempenho.
Sistema de cores
As cores usam o espaço de cores OKLCH para ajustes perceptualmente uniformes. O sistema inclui:
- Modo claro - Escala de cinza neutra com acentos sutis
- Modo escuro** - Fundos profundos com texto de alto contraste
- Cores semânticas** - Primárias, secundárias, suaves, acentuadas, destrutivas, de sucesso
Principais recursos
Modo escuro
A preferência de tema persiste no localStorage e respeita as configurações do sistema na primeira visita. Um script de bloqueio no <head> impede o flash do tema errado.
<script dangerouslySetInnerHTML={{
__html: `(function(){
var t = localStorage.getItem('theme') ||
(matchMedia('(prefers-color-scheme:dark)').matches ? 'dark' : 'light');
if (t === 'dark') document.documentElement.classList.add('dark')
})();`,
}} />
Texto para fala
O player é carregado dinamicamente para evitar problemas de hidratação:
const TTSPlayer = dynamic(
() => import('@/components/tts-player').then((mod) => mod.TTSPlayer),
{ ssr: false }
);
Criando um clone de voz no ElevenLabs
Para criar seu próprio clone de voz para o reprodutor de áudio:
- Registre-se ou faça login no [ElevenLabs] (https://elevenlabs.io)
- Navegue até a seção Voices no dashboard (barra lateral esquerda)
- Clique em Adicionar uma nova voz
- Escolha Professional Voice Clone (recomendado para melhor qualidade) ou Instant Voice Clone (mais rápido, requer menos áudio)
- Faça upload de amostras de áudio:
- Professional Voice Clone: Pelo menos 1 hora de áudio de alta qualidade (o ideal é de 2 a 3 horas para obter melhores resultados)
- Clone de voz instantâneo**: Mínimo de 30 segundos de áudio limpo
- Divida gravações longas em amostras de aproximadamente 30 minutos para facilitar o upload
- Use gravações limpas e de alta qualidade sem ruído de fundo, eco ou sons indesejados
- Opcionalmente, grave diretamente na interface usando Record yourself com os scripts de amostra fornecidos
- Nomeie e rotule seu clone de voz
- Confirme que você tem o direito e o consentimento para clonar a voz
- Clique em Salvar voz para treinar o modelo
- Depois de treinado, copie o ID de voz das configurações de voz
- Configure-o no projeto atualizando
ELEVENLABS_VOICE_IDemlib/constants.ts
Amostra de voz
Aqui está uma amostra da voz clonada:
Para gerar a amostra de áudio, você pode usar o [playground Text to Speech da ElevenLabs] (https://elevenlabs.io/app/speech-synthesis/text-to-speech). Selecione sua voz clonada, digite o texto, clique em Generate (Gerar), faça download e salve o arquivo como voice-sample.mp3 no diretório public/audio/.
Narração de áudio do blog (Kokoro local)
Atualização — abr/2026. Os posts do blog e a página About agora usam Kokoro-82M, um modelo TTS com licença Apache 2.0 (82M de parâmetros, voz af_heart) rodando localmente na CPU via kokoro-onnx. A geração é determinística, gratuita e não depende de nenhuma API externa. A amostra de voz acima continua sendo a voz clonada original do ElevenLabs — só a narração do blog foi migrada.
O setup é único: npm run setup:kokoro cria um diretório .kokoro/ gitignorado com um venv Python e o modelo ONNX (~340 MB). Depois, npm run generate-audio narra todo o conteúdo em inglês e atualiza public/audio/manifest.json com hashes por post. Em um Apple M4 Max o modelo roda a cerca de 5× o tempo real — um post de três minutos gera em torno de trinta segundos.
Formulário de contato
O formulário de contato usa o SendGrid para a entrega de e-mails com:
- Validação no lado do cliente
- Rota de API do lado do servidor em
/contact/api - Cabeçalho "Responder a" para respostas diretas
- Tratamento de erros com mensagens fáceis de usar
SEO
Cada página inclui:
- Metadados - Título, descrição, palavras-chave
- Open Graph** - Imagens e texto de compartilhamento social
- Twitter Cards** - formatação específica do Twitter
- JSON-LD** - Dados estruturados para pessoa, site, artigo, Breadcrumb
- Mapa do site** - Gerado automaticamente em
/sitemap.xml - Robôs** - Regras de rastreamento do mecanismo de pesquisa

Processo de compilação
Fase 1: Configuração do projeto
Iniciado com create-next-app usando o modelo do App Router. Adicionamos TypeScript, Tailwind CSS 4 e configuramos o shadcn/ui com a variante de estilo New York.
npx create-next-app@latest ftchvs_26 --typescript --tailwind --app
npx shadcn@latest init
Fase 2: Layout do núcleo
Criamos o layout raiz com as fontes Geist e uma navegação responsiva. A navegação inclui um menu para desktop e uma folha para celular (gaveta deslizante) do shadcn/ui.
O logotipo do terminal (>_) usa uma animação cintilante que se adapta aos modos claro e escuro.
Fase 3: sistema de conteúdo
Criado o lib/content.ts para lidar com o carregamento do MDX:
getAllPosts()- Busca todas as postagens do blog classificadas por datagetPostBySlug()- Busca um único post com MDX serializadogetAllProjects()- Busca todos os projetosgetProjectBySlug()- Busca um único projeto com MDX serializadogetPageBySlug()- Busca páginas estáticas como About
Cada função lê do sistema de arquivos, analisa o frontmatter com gray-matter e serializa o conteúdo com next-mdx-remote.
Fase 4: páginas
Criamos seis rotas principais:
- Home - Introdução com lista de textos recentes
- Sobre** - Histórico profissional carregado do MDX
- Blog** - Listagem de posts e páginas de posts individuais
- Projetos** - Mostra de projetos (esta página)
- Currículo** - Experiência profissional e habilidades
- Contato** - Formulário com integração com SendGrid
Fase 5: Integrações
SendGrid: Rota de API configurada para enviar e-mails do formulário de contato. O remetente deve ser verificado nas configurações de autenticação do SendGrid.
Google Analytics 4: Adicionado via @next/third-parties com carregamento condicional baseado em NEXT_PUBLIC_GA_ID.
Fase 6: Sistema de design
Implementação de tokens de design de tipografia em globals.css. Adicionadas variáveis de cor OKLCH para os modos claro e escuro. Criação de animação shimmer com propriedades personalizadas do CSS.
Fase 7: SEO
Adicionados metadados a todas as páginas. Criação de geradores de mapa do site e robots.txt. Implementação de dados estruturados JSON-LD para os esquemas Person, WebSite, Article e BreadcrumbList.
Fase 8: Implementação
Transferido para o GitHub e importado para o Vercel. Variáveis de ambiente configuradas:
NEXT_PUBLIC_SITE_URL- URL canônicoNEXT_PUBLIC_GA_ID- Google AnalyticsSENDGRID_API_KEY- Entrega de e-mailCONTACT_EMAIL_TO- Endereço do destinatárioCONTACT_EMAIL_FROM- Remetente verificado

Registro de alterações
Uma linha do tempo detalhada do desenvolvimento, extraída do histórico de commits do Git. Último primeiro.
Estatísticas de desenvolvimento
- Total de commits**: 51
- Tempo de desenvolvimento**: ~2 dias
- Principais recursos**: 8
- Correções de bugs: 12
- Refatores**: 15
- Atualizações de estilo**: 16
13 de dezembro de 2025 - Dia 2: polimento e recursos
Noite: Polimento final
| Compromisso | Descrição |
|---|---|
20107f4 | Major: Melhorias abrangentes de SEO |
e3ec3c2 | Introdução simplificada da página sobre |
| Configuração centralizada do ID de voz | |
| Configuração de voz personalizada da ElevenLabs | |
f8533bf | Maior: Otimização do ElevenLabs para reduzir os custos de API |
f8d31cf | Posicionamento do player TTS acima do resumo |
7da002a | Atualização da tipografia da lista de blogs |
872a6ba | Revisão da postagem do blog para maior clareza e narrativa |
Evening: Otimização de áudio
| Commit | Descrição |
|---|---|
fc9cea4 | Usado ref para inserir HTML (correção de hidratação) |
fb9303f | Usado iframe embed para áudio nativo |
7384478 | Restaurado o Suspense wrapper para compatibilidade com MDX |
ff9ca71 | Maior: API do Audio Native com projetos de conteúdo completo |
76e6e08 | Removido o Suspense para o Audio Native do SSR |
36c512c | Carregar Audio Native via useEffect |
| ID de usuário público codificado com força | 5073332 |
ea4e5c7 | Utilizado próximo/script para carregamento do Audio Native |
8a5ee20 | Simplificado para integração do Audio Native |
32fa6ca | Adicionado script para limpar o cache de áudio |
61382ca | Adicionado cache de áudio para reduzir as chamadas à API |
0ab2855 | Pré-geração de áudio TTS no carregamento da página |
d59988f | Adicionado registro de depuração ao player TTS |
Final da tarde: Sprint de integração de áudio
| Commit | Descrição |
|---|---|
d018e8e | Removido o rodapé da ElevenLabs do player |
156eda5 | Tornou o seletor de velocidade mais minimalista |
b87be4f | Removido o cabeçalho da ElevenLabs do player |
1d6af2f | Exibição imediata da interface completa do player de áudio |
1a90ebf | Atualizamos o player TTS com os componentes de interface do usuário da ElevenLabs |
738ebb4 | Substituiu o Audio Native pela API TTS |
fa6fd42 | Adicionada configuração e regras do Cursor IDE |
5152aec | MDXRemote com Suspense para React 19 |
c7b54b2 | Maior: Adicionada a integração do ElevenLabs Audio Native TTS |
Tarde: Tipografia e currículo
| Compromisso | Descrição |
|---|---|
2ff75d4 | Atualização da declaração de aprendizado na postagem do blog |
0c9b17b | Atualizada a tagline do currículo |
3e6b185 | Adicionado o prefixo dash aos itens de Habilidades e Educação |
3371a03 | Removida a seção "Looking For" do currículo |
649010d | Melhoria da tipografia e do alinhamento do currículo |
| Atualizados os tamanhos de fonte para itens de lista, links e meta texto |
Late Morning: Estratégia de conteúdo
| Commit | Descrição | Conteúdo
|--------|-------------|
| Adicionada segmentação de público-alvo à página Sobre
| 45e61a2 | Mensagem de "eu mesmo construindo ferramentas" atenuada |
| b7a8cd8 | Atualizações do README para novo posicionamento
| ae9f3a3 | Posicionamento refinado do growth marketer em todas as páginas |
| 701a36f | Reposicionamento do site como "growth marketer que codifica" |
| db41419 | Substituição da página de projetos pelo espaço reservado "Em breve" |
Morning: Sistema de design
| Commit | Descrição |
|---|---|
4b7f5ff | Atualização do README com tokens de tipografia, SendGrid, documentos do GA4 |
d850b7e | Menu de navegação reordenado: Sobre, Blog, Projetos, Currículo, Contato |
a17536b | Maior: Implementação do sistema de token de design de tipografia |
a91398f | Atualização do favicon para o ícone de prompt do terminal (>_) |
| Remoção da borda do anel de foco no logotipo do terminal | |
af50e11 | Animação shimmer refatorada para suporte ao modo claro/escuro |
12 de dezembro de 2025 - Dia 1: Fundação
| Compromisso | Descrição |
|---|---|
0b4e0a2 | Major: SendGrid integrado ao formulário de contato com interface de usuário aprimorada. |
| Seleção de texto corrigida ao clicar no logotipo do terminal | |
2ab20b1 | Geração estática desativada para MDX (correção de compatibilidade com o React 19) |
| Adicionadas páginas, componentes e recursos principais | |
891648c | Documentação README mesclada |
08bc5e0 | Maior: Site inicial do portfólio Next.js com estrutura de blog e projetos. |
7850991 | Inicialização do repositório no GitHub |
20aed18 | Confirmação inicial do aplicativo Create Next |
Principais marcos:
- Estrutura completa do App Router com 6 rotas
- Sistema de conteúdo MDX com análise de matéria cinza
- Integração da biblioteca de componentes shadcn/ui
- Formulário de contato com envio de e-mail
- Navegação responsiva com gaveta móvel

Capturas de tela





O que eu aprendi
O desenvolvimento assistido por IA é real. O Cursor e o Claude lidaram com o boilerplate, detectaram erros e sugeriram padrões que eu não teria encontrado sozinho. O gargalo deixou de ser a digitação e passou a ser o pensamento.
**Os erros de digitação detectaram bugs antes que eles chegassem ao navegador. O tempo de configuração inicial economizou horas de depuração.
**A definição de classes de tipografia uma vez significou um estilo consistente sem decisões constantes. As alterações se propagam automaticamente.
O MDX é flexível. Os esquemas de frontmatter permitem que o conteúdo conduza a interface do usuário. A adição de um novo campo leva minutos, não refatorações.
Próximas etapas
- Adicionar mais projetos como estudos de caso
- Implementar pesquisa e filtragem de posts do blog
- Adicionar feed RSS para assinantes do blog
- Explorar a ISR (Incremental Static Regeneration) para atualizações de conteúdo
Outcomes
- •Criado do zero com desenvolvimento assistido por IA (Cursor + Claude)
- •Implementação do sistema de token de design de tipografia personalizada
- •ElevenLabs Audio Native integrado para conversão de texto em fala
- •Configuração do SendGrid para a funcionalidade do formulário de contato
- •Implementado no Vercel com SEO abrangente