[{"data":1,"prerenderedAt":4551},["ShallowReactive",2],{"lastUpdated":3,"blog-pt-list":4,"blog-en-list":2491},"2026-09-08",[5,1071,1405,1584,1874,2217,2291,2379],{"id":6,"title":7,"body":8,"category":75,"date":1056,"description":1057,"extension":1058,"featured":1059,"meta":1060,"navigation":1059,"path":1061,"seo":1062,"stem":1063,"tags":1064,"__hash__":1070},"blog/blog/de-69-a-97-no-lighthouse.md","De 66 a 97 no Core Web Vitals: O que identificamos para melhorar na vitrine do PN Imóveis",{"type":9,"value":10,"toc":1039},"minimark",[11,15,36,43,46,217,220,225,236,248,250,258,277,283,460,463,465,469,488,499,509,512,514,518,528,554,557,562,602,604,608,611,622,635,648,650,654,657,753,755,759,766,773,780,800,898,902,946,948,952,955,986,988,992,1021,1023,1027,1035],[12,13,7],"h1",{"id":14},"de-66-a-97-no-core-web-vitals-o-que-identificamos-para-melhorar-na-vitrine-do-pn-imóveis",[16,17,18,19,26,27,31,32,35],"p",{},"A nota de Performance da home do ",[20,21,25],"a",{"href":22,"rel":23},"https://pnimoveis.com.br",[24],"nofollow","pnimoveis.com.br"," no Lighthouse foi de ",[28,29,30],"strong",{},"66 para 97",". O LCP — o tempo até o maior elemento visível aparecer — caiu de ",[28,33,34],{},"6,8 s para 2,0 s"," num celular médio em rede 4G lenta. Acessibilidade subiu de 84 para 96, SEO de 83 para 100, e o site passou a ter prévia de link decente no WhatsApp, coisa que simplesmente não existia.",[16,37,38],{},[39,40],"img",{"alt":41,"src":42},"Lighthouse Score: De 66 para 97","/blog/de-69-a-97-no-lighthouse/image.png",[16,44,45],{},"Este texto conta o caminho: o que encontramos, por que cada coisa doía, o que mudamos e como resolvemos cada gargalo.",[47,48,49,65],"table",{},[50,51,52],"thead",{},[53,54,55,59,62],"tr",{},[56,57,58],"th",{},"Métrica",[56,60,61],{},"Antes",[56,63,64],{},"Depois (Produção)",[66,67,68,84,99,113,127,142,157,172,187,202],"tbody",{},[53,69,70,76,79],{},[71,72,73],"td",{},[28,74,75],{},"Performance",[71,77,78],{},"66",[71,80,81],{},[28,82,83],{},"97",[53,85,86,91,94],{},[71,87,88],{},[28,89,90],{},"Acessibilidade",[71,92,93],{},"84",[71,95,96],{},[28,97,98],{},"96",[53,100,101,106,108],{},[71,102,103],{},[28,104,105],{},"Boas Práticas",[71,107,98],{},[71,109,110],{},[28,111,112],{},"100",[53,114,115,120,123],{},[71,116,117],{},[28,118,119],{},"SEO",[71,121,122],{},"83",[71,124,125],{},[28,126,112],{},[53,128,129,134,137],{},[71,130,131],{},[28,132,133],{},"First Contentful Paint",[71,135,136],{},"2,5 s",[71,138,139],{},[28,140,141],{},"1,2 s",[53,143,144,149,152],{},[71,145,146],{},[28,147,148],{},"Largest Contentful Paint",[71,150,151],{},"6,8 s",[71,153,154],{},[28,155,156],{},"2,0 s",[53,158,159,164,167],{},[71,160,161],{},[28,162,163],{},"Speed Index",[71,165,166],{},"4,1 s",[71,168,169],{},[28,170,171],{},"2,9 s",[53,173,174,179,182],{},[71,175,176],{},[28,177,178],{},"Cumulative Layout Shift",[71,180,181],{},"0,004",[71,183,184],{},[28,185,186],{},"0",[53,188,189,194,197],{},[71,190,191],{},[28,192,193],{},"Total Blocking Time",[71,195,196],{},"170 ms",[71,198,199],{},[28,200,201],{},"130 ms",[53,203,204,209,212],{},[71,205,206],{},[28,207,208],{},"Bytes transferidos na home",[71,210,211],{},"2,8 MB",[71,213,214],{},[28,215,216],{},"~0,6 MB",[218,219],"hr",{},[221,222,224],"h2",{"id":223},"o-paciente","O paciente",[16,226,227,228,231,232,235],{},"O PN Imóveis é a vitrine pública de uma imobiliária de Pernambuco: home com destaques, busca com filtros, página de cada imóvel, e um painel administrativo para os corretores. Stack: ",[28,229,230],{},"Nuxt 4"," (Vue) com ",[28,233,234],{},"PrimeVue",", API no mesmo processo (Nitro), PostgreSQL via Supabase self-hosted, tudo numa VPS com Coolify. As fotos dos imóveis ficam num bucket do Supabase Storage servido por um subdomínio atrás do Cloudflare.",[16,237,238,239,242,243,247],{},"Uma decisão antiga do projeto importa para a história: o app inteiro era uma ",[28,240,241],{},"SPA"," — ",[244,245,246],"code",{},"ssr: false",". O servidor entregava uma casca vazia de HTML e o navegador montava tudo com JavaScript. Fazia sentido para o painel administrativo; para a vitrine, veremos que não.",[218,249],{},[221,251,253,254,257],{"id":252},"o-relatório-dizia-pouco-o-curl-disse-tudo","O relatório dizia pouco. O ",[244,255,256],{},"curl"," disse tudo.",[16,259,260,261,265,266,265,269,272,273,276],{},"O ponto de partida foi o Lighthouse: Performance 66, LCP 6,8 s, e uma lista de auditorias com nomes genéricos — ",[262,263,264],"em",{},"\"Use efficient cache lifetimes (1.352 KiB)\"",", ",[262,267,268],{},"\"Reduce unused JavaScript (692 KiB)\"",[262,270,271],{},"\"Improve image delivery (1.190 KiB)\"",". O PDF é o resumo; não diz ",[262,274,275],{},"quais"," recursos estavam impactando a página.",[16,278,279,280,282],{},"Então fizemos o básico: baixamos a página com ",[244,281,256],{}," e inspecionamos a resposta bruta.",[47,284,285,301],{},[50,286,287],{},[53,288,289,292,295,298],{},[56,290,291],{},"Recurso",[56,293,294],{},"Tamanho",[56,296,297],{},"Cache",[56,299,300],{},"O problema",[66,302,303,321,340,362,381,403,423,441],{},[53,304,305,310,315,318],{},[71,306,307],{},[28,308,309],{},"O HTML da home",[71,311,312],{},[28,313,314],{},"501 KB",[71,316,317],{},"Nenhum",[71,319,320],{},"Uma SPA deveria entregar ~10 KB de casca. Vinham 462 KB de CSS embutido.",[53,322,323,328,334,337],{},[71,324,325],{},[28,326,327],{},"JS de entrada",[71,329,330,333],{},[28,331,332],{},"504 KB"," (146 KB gzip)",[71,335,336],{},"1 ano",[71,338,339],{},"Registrava globalmente todos os ~90 componentes do PrimeVue.",[53,341,342,347,353,355],{},[71,343,344],{},[28,345,346],{},"Imagem do hero",[71,348,349,352],{},[28,350,351],{},"359 KB"," (JPEG 1920px)",[71,354,317],{},[71,356,357,358,361],{},"Era o LCP — e estava como ",[244,359,360],{},"background-image"," no CSS.",[53,363,364,369,375,378],{},[71,365,366],{},[28,367,368],{},"Foto de card",[71,370,371,374],{},[28,372,373],{},"500–565 KB"," (WebP 2000px)",[71,376,377],{},"4 horas",[71,379,380],{},"Exibida num card de 400 px.",[53,382,383,388,394,396],{},[71,384,385],{},[28,386,387],{},"Fontes e logos",[71,389,390,393],{},[28,391,392],{},"17–85 KB"," cada",[71,395,317],{},[71,397,398,399,402],{},"Servidos sem ",[244,400,401],{},"Cache-Control",".",[53,404,405,412,417,420],{},[71,406,407],{},[28,408,409],{},[244,410,411],{},"/robots.txt",[71,413,414],{},[28,415,416],{},"67 KB",[71,418,419],{},"—",[71,421,422],{},"Devolvia o HTML da SPA. O crawler pedia regras e recebia uma página.",[53,424,425,430,436,438],{},[71,426,427],{},[28,428,429],{},"Sprite de ícones",[71,431,432,435],{},[28,433,434],{},"238 KB"," SVG",[71,437,419],{},[71,439,440],{},"Montado no DOM de toda página pública, dentro do bundle JS.",[53,442,443,450,455,457],{},[71,444,445],{},[28,446,447],{},[244,448,449],{},"\u003Clink rel=\"prefetch\">",[71,451,452],{},[28,453,454],{},"158 links",[71,456,419],{},[71,458,459],{},"Um por chunk do build, disputando banda com o que a página precisava agora.",[16,461,462],{},"Cada linha dessa tabela traduzia uma auditoria genérica em algo concreto. E a primeira linha explicava sozinha boa parte do peso.",[218,464],{},[221,466,468],{"id":467},"problema-1-meio-megabyte-de-css-escondido-no-html","Problema 1: meio megabyte de CSS escondido no HTML",[16,470,471,472,475,476,479,480,483,484,487],{},"O módulo do PrimeVue para Nuxt tem uma opção, ligada por padrão, que pré-injeta no ",[244,473,474],{},"\u003Chead>"," o CSS do tema de cada componente. Sem uma lista explícita dos componentes que o app realmente utiliza, ele injeta ",[28,477,478],{},"todos"," — autocomplete, cascadeselect, treetable, terminal, dock, organograma… componentes que a vitrine nunca renderizou. Eram 184 blocos ",[244,481,482],{},"\u003Cstyle>",", totalizando 462 KB em ",[28,485,486],{},"toda"," resposta HTML, para uma SPA cujo HTML não tinha conteúdo nenhum a estilizar.",[16,489,490,491,494,495,498],{},"Isso é o pior tipo de peso: não cacheia (é o próprio HTML), precisa ser baixado e processado antes de qualquer renderização, e não aparece de forma clara no Lighthouse — o relatório computava como ",[262,492,493],{},"\"JavaScript não usado\""," e ",[262,496,497],{},"\"JavaScript não minificado\"",", pois o mesmo CSS existia como strings dentro do bundle JS.",[16,500,501,502,505,506,402],{},"A correção inicial foi desligar a injeção global (",[244,503,504],{},"loadStyles: false",") e declarar apenas os componentes realmente usados. O HTML caiu de 501 KB para ",[28,507,508],{},"10 KB",[16,510,511],{},"Mais tarde, quando a vitrine migrou para SSR, a injeção voltou a ser feita de forma seletiva — apenas para os ~12 componentes que a vitrine renderiza na resposta inicial. O painel administrativo continua como SPA e injeta o CSS em runtime conforme necessário.",[218,513],{},[221,515,517],{"id":516},"problema-2-o-lcp-era-um-fundo-css-atrás-de-três-idas-e-voltas","Problema 2: o LCP era um fundo CSS atrás de três idas e voltas",[16,519,520,521,523,524,527],{},"O maior elemento visível da home é a foto do hero. Ela estava definida como ",[244,522,360],{}," em uma ",[244,525,526],{},"\u003Cdiv>",". Para o navegador descobrir que precisava dessa imagem, a cadeia de dependências era longa:",[529,530,531,535,538,545,548,551],"ol",{},[532,533,534],"li",{},"Baixar e parsear o HTML.",[532,536,537],{},"Baixar e executar o JS da aplicação.",[532,539,540,541,544],{},"Aguardar o retorno da API da home (",[244,542,543],{},"await"," bloqueante no setup).",[532,546,547],{},"Montar a árvore do DOM.",[532,549,550],{},"Aplicar as regras de CSS para identificar a URL da imagem.",[532,552,553],{},"Iniciar o download de um JPEG de 359 KB.",[16,555,556],{},"Em redes 4G lentas com RTT de ~150 ms, o hero só começava a baixar após quase 4 segundos.",[558,559,561],"h3",{"id":560},"as-correções","As correções:",[563,564,565,586,592],"ul",{},[532,566,567,577,578,581,582,585],{},[28,568,569,572,573,576],{},[244,570,571],{},"\u003Cimg>"," nativo com ",[244,574,575],{},"fetchpriority=\"high\"",":"," Permite que o ",[262,579,580],{},"preload scanner"," do navegador identifique e solicite a imagem imediatamente ao receber o HTML. Adicionamos ",[244,583,584],{},"srcset"," responsivo (800/1200/1600 px), reduzindo o download inicial em celulares para apenas 34 KB.",[532,587,588,591],{},[28,589,590],{},"Conversão para WebP de 1600 px:"," Redução de 359 KB para 98 KB sem perda de qualidade perceptível.",[532,593,594,597,598,601],{},[28,595,596],{},"Desbloqueio da API:"," O hero e a barra de busca tornaram-se estáticos na renderização inicial; a busca por destaques mudou para ",[244,599,600],{},"useLazyFetch"," com esqueletos de carregamento.",[218,603],{},[221,605,607],{"id":606},"problema-3-fotos-gigantes-em-cards-pequenos-e-cache-curto","Problema 3: fotos gigantes em cards pequenos e cache curto",[16,609,610],{},"No fluxo de cadastro, o upload comprimia as imagens para WebP com 2000 px de largura. Excelente para a galeria detalhada do imóvel, mas excessivo para os cards da home, que exibem a imagem em um container de 400 px. Eram cerca de 500 KB por card, multiplicados por 6 destaques na página inicial.",[16,612,613,614,617,618,621],{},"Além disso, o Supabase Storage retornava os objetos com o cabeçalho ",[244,615,616],{},"Cache-Control: max-age=3600"," (4 horas). Ajustamos para um cache imutável de 1 ano (",[244,619,620],{},"max-age=31536000, immutable",") via Cloudflare e headers de origem.",[623,624,625],"blockquote",{},[16,626,627,630,631,634],{},[28,628,629],{},"E se precisarmos alterar uma imagem existente?"," Aplicamos a estratégia padrão de ",[28,632,633],{},"Cache Busting",": quando uma foto é substituída ou atualizada, seu nome ou versão na URL é alterado (por exemplo, adicionando uma hash ou número de versão no caminho). Como a URL muda, o navegador reconhece o recurso como um novo arquivo e baixa a versão atualizada imediatamente, contornando o cache de 1 ano sem qualquer atrito.",[16,636,637,638,494,641,644,645,647],{},"As dimensões de cada imagem também passaram a ser enviadas no DTO da API, alimentando os atributos ",[244,639,640],{},"width",[244,642,643],{},"height"," da tag ",[244,646,571],{},", eliminando qualquer risco de layout shift.",[218,649],{},[221,651,653],{"id":652},"problema-4-o-acúmulo-de-pequenos-detalhes","Problema 4: o acúmulo de pequenos detalhes",[16,655,656],{},"Individualmente, pequenos gargalos parecem inofensivos; acumulados, representavam centenas de kilobytes e dezenas de ms de bloqueio:",[563,658,659,672,685,701,720,737],{},[532,660,661,664,665,667,668,671],{},[28,662,663],{},"Arquivos estáticos sem cache:"," O servidor Nitro não adicionava ",[244,666,401],{}," nos arquivos de ",[244,669,670],{},"public/",". Configuramos regras de roteamento: fontes com cache imutável de 1 ano, imagens estáticas e ícones com 30 dias.",[532,673,674,677,678,680,681,684],{},[28,675,676],{},"158 prefetches desnecessários:"," O Nuxt gerava ",[244,679,449],{}," para todos os chunks assíncronos do build. Removemos esse comportamento via hook no manifest do build. O prefetch passou a ocorrer dinamicamente apenas quando um ",[244,682,683],{},"\u003CNuxtLink>"," entra na viewport.",[532,686,687,690,691,694,695,697,698,402],{},[28,688,689],{},"Sprite SVG de 238 KB:"," Os ícones da ficha técnica estavam agrupados em um componente Vue gigante com todos os ",[244,692,693],{},"\u003Csymbol>"," inline. O sprite foi extraído para um arquivo SVG estático em ",[244,696,670],{},", otimizado via SVGO (metade do tamanho) e referenciado via ",[244,699,700],{},"\u003Cuse href=\"/icons/sprite.svg#nome\">",[532,702,703,709,710,713,714,494,717,402],{},[28,704,705,706,576],{},"Fonte de ícones com ",[244,707,708],{},"font-display: block"," Substituída por hospedagem local do arquivo ",[244,711,712],{},".woff2"," com ",[244,715,716],{},"font-display: swap",[244,718,719],{},"\u003Clink rel=\"preload\">",[532,721,722,725,726,729,730,732,733,736],{},[28,723,724],{},"Acessibilidade e SEO:"," Correção de botões sem rótulo acessível (",[244,727,728],{},"aria-label","), ajuste da hierarquia de cabeçalhos (",[244,731,221],{}," a ",[244,734,735],{},"h4","), meta tags de SEO e ajuste de contraste no tom de verde da marca para atingir o mínimo de 4,5:1.",[532,738,739,742,743,265,746,265,749,752],{},[28,740,741],{},"Headers de segurança:"," Adição de HSTS, ",[244,744,745],{},"X-Frame-Options",[244,747,748],{},"X-Content-Type-Options",[244,750,751],{},"Referrer-Policy"," e COOP.",[218,754],{},[221,756,758],{"id":757},"a-virada-ssr-seletivo-onde-realmente-importa","A virada: SSR seletivo onde realmente importa",[16,760,761,762,765],{},"Com o aplicativo ajustado, veio a decisão de arquitetura crucial: ",[28,763,764],{},"adotar Server-Side Rendering (SSR)"," para a vitrine pública.",[16,767,768,769,772],{},"O principal motivo não foi apenas o LCP, mas a ",[28,770,771],{},"experiência de compartilhamento",". Quando um corretor ou cliente enviava o link de um imóvel no WhatsApp ou redes sociais, o resultado era um card genérico sem foto, preço ou título. Como os bot de prévia não executam JavaScript, a SPA entregava uma página em branco.",[16,774,775,776,779],{},"Utilizando o ",[244,777,778],{},"routeRules"," do Nuxt, configuramos a aplicação de forma híbrida:",[563,781,782,788],{},[532,783,784,787],{},[28,785,786],{},"SSR por padrão:"," Para a home, busca, landing pages e páginas de imóveis.",[532,789,790,795,796,799],{},[28,791,792,794],{},[244,793,246],{}," (SPA):"," Para o painel administrativo, telas de autenticação e a página de favoritos (que depende exclusivamente de ",[244,797,798],{},"localStorage",").",[801,802,807],"pre",{"className":803,"code":804,"language":805,"meta":806,"style":806},"language-typescript shiki shiki-themes github-light github-dark","// sample\nexport default defineNuxtConfig({\n  routeRules: {\n    '/dashboard/**': { ssr: false },\n    '/checkout': { ssr: false },\n    '/catalog/**': { swr: 3600 }\n  }\n})\n","typescript","",[244,808,809,818,836,842,859,871,886,892],{"__ignoreMap":806},[810,811,814],"span",{"class":812,"line":813},"line",1,[810,815,817],{"class":816},"sJ8bj","// sample\n",[810,819,821,825,828,832],{"class":812,"line":820},2,[810,822,824],{"class":823},"szBVR","export",[810,826,827],{"class":823}," default",[810,829,831],{"class":830},"sScJk"," defineNuxtConfig",[810,833,835],{"class":834},"sVt8B","({\n",[810,837,839],{"class":812,"line":838},3,[810,840,841],{"class":834},"  routeRules: {\n",[810,843,845,849,852,856],{"class":812,"line":844},4,[810,846,848],{"class":847},"sZZnC","    '/dashboard/**'",[810,850,851],{"class":834},": { ssr: ",[810,853,855],{"class":854},"sj4cs","false",[810,857,858],{"class":834}," },\n",[810,860,862,865,867,869],{"class":812,"line":861},5,[810,863,864],{"class":847},"    '/checkout'",[810,866,851],{"class":834},[810,868,855],{"class":854},[810,870,858],{"class":834},[810,872,874,877,880,883],{"class":812,"line":873},6,[810,875,876],{"class":847},"    '/catalog/**'",[810,878,879],{"class":834},": { swr: ",[810,881,882],{"class":854},"3600",[810,884,885],{"class":834}," }\n",[810,887,889],{"class":812,"line":888},7,[810,890,891],{"class":834},"  }\n",[810,893,895],{"class":812,"line":894},8,[810,896,897],{"class":834},"})\n",[558,899,901],{"id":900},"cuidados-indispensáveis-ao-introduzir-ssr","Cuidados indispensáveis ao introduzir SSR:",[529,903,904,917,930],{},[532,905,906,909,910,912,913,916],{},[28,907,908],{},"Hydration Mismatches:"," Garantimos que a leitura de ",[244,911,798],{}," e operações não determinísticas (como ordenação aleatória) ocorram apenas no hook ",[244,914,915],{},"onMounted",", evitando divergências entre o HTML gerado no servidor e o DOM do cliente.",[532,918,919,922,923,926,927,402],{},[28,920,921],{},"Reatividade no Servidor:"," Substituição de ",[244,924,925],{},"watch"," que preenchia listas após fetches por propriedades ",[244,928,929],{},"computed",[532,931,932,935,936,494,939,942,943,402],{},[28,933,934],{},"Serviços Globais do PrimeVue:"," Manutenção explícita dos componentes ",[244,937,938],{},"\u003CToast />",[244,940,941],{},"\u003CConfirmDialog />"," para garantir o registro dos serviços injetados via ",[244,944,945],{},"useToast()",[218,947],{},[221,949,951],{"id":950},"otimizações-de-compressão-e-bundling-na-entrega","Otimizações de compressão e bundling na entrega",[16,953,954],{},"Para garantir que o ganho estivesse consolidado do servidor até o cliente, aplicamos otimizações adicionais no pipeline do Nitro e Rollup:",[563,956,957,966,972],{},[532,958,959,962,963,402],{},[28,960,961],{},"Compressão Brotli no SSR:"," Adicionamos um plugin no Nitro para comprimir o HTML diretamente com Brotli/Gzip antes de enviar ao proxy. O HTML enviado passou de 196 KB para ",[28,964,965],{},"24 KB",[532,967,968,971],{},[28,969,970],{},"Consolidação de Chunks (Rollup):"," Originalmente, o build gerava 101 chunks JS (74 deles com menos de 10 KB). Ajustamos o agrupamento do Rollup para fundir pequenos arquivos sem comprometer o code-splitting. O total de chunks caiu de 101 para 71, e a home passou a solicitar apenas 19 arquivos JS em vez de 44.",[532,973,974,977,978,981,982,985],{},[28,975,976],{},"Preload prioritário do Hero:"," Injeção do header HTTP ",[244,979,980],{},"Link: rel=preload"," com suporte a ",[244,983,984],{},"imagesrcset"," para colocar a imagem principal no topo da fila de rede antes de qualquer outro recurso.",[218,987],{},[221,989,991],{"id":990},"o-que-aprendemos","O que aprendemos",[529,993,994,1003,1009,1015],{},[532,995,996,999,1000,1002],{},[28,997,998],{},"Meça o tráfego real com dados brutos:"," Relatórios automatizados dão o ponto de partida, mas inspeções com ",[244,1001,256],{}," e análise de headers revelam onde estão os megabytes ocultos.",[532,1004,1005,1008],{},[28,1006,1007],{},"SSR na vitrine é sobre conversão:"," Prévia correta no WhatsApp e indexação em buscadores são requisitos de negócio, não apenas métricas técnicas.",[532,1010,1011,1014],{},[28,1012,1013],{},"Cuidado com a hidratação:"," Qualquer estado dependente do navegador deve nascer neutro no servidor e ser hidratado após a montagem do componente.",[532,1016,1017,1020],{},[28,1018,1019],{},"Gerenciamento de dependências de UI:"," Módulos de componentes de interface devem ser configurados para incluir apenas o CSS e os scripts estritamente utilizados no caminho crítico.",[218,1022],{},[221,1024,1026],{"id":1025},"resumo-em-uma-frase","Resumo em uma frase",[16,1028,1029,1030,1032,1033,402],{},"Transformamos uma SPA que entregava 500 KB de CSS inline em uma aplicação SSR com HTML comprimido em 24 KB, imagem principal pré-carregada e cards com suporte total a previews sociais — elevando a nota no Lighthouse de ",[28,1031,30],{}," e reduzindo o LCP de ",[28,1034,34],{},[1036,1037,1038],"style",{},"html pre.shiki code .sJ8bj, html code.shiki .sJ8bj{--shiki-default:#6A737D;--shiki-dark:#6A737D}html pre.shiki code .szBVR, html code.shiki .szBVR{--shiki-default:#D73A49;--shiki-dark:#F97583}html pre.shiki code .sScJk, html code.shiki .sScJk{--shiki-default:#6F42C1;--shiki-dark:#B392F0}html pre.shiki code .sVt8B, html code.shiki .sVt8B{--shiki-default:#24292E;--shiki-dark:#E1E4E8}html pre.shiki code .sZZnC, html code.shiki .sZZnC{--shiki-default:#032F62;--shiki-dark:#9ECBFF}html pre.shiki code .sj4cs, html code.shiki .sj4cs{--shiki-default:#005CC5;--shiki-dark:#79B8FF}html .default .shiki span {color: var(--shiki-default);background: var(--shiki-default-bg);font-style: var(--shiki-default-font-style);font-weight: var(--shiki-default-font-weight);text-decoration: var(--shiki-default-text-decoration);}html .shiki span {color: var(--shiki-default);background: var(--shiki-default-bg);font-style: var(--shiki-default-font-style);font-weight: var(--shiki-default-font-weight);text-decoration: var(--shiki-default-text-decoration);}html .dark .shiki span {color: var(--shiki-dark);background: var(--shiki-dark-bg);font-style: var(--shiki-dark-font-style);font-weight: var(--shiki-dark-font-weight);text-decoration: var(--shiki-dark-text-decoration);}html.dark .shiki span {color: var(--shiki-dark);background: var(--shiki-dark-bg);font-style: var(--shiki-dark-font-style);font-weight: var(--shiki-dark-font-weight);text-decoration: var(--shiki-dark-text-decoration);}",{"title":806,"searchDepth":820,"depth":820,"links":1040},[1041,1042,1044,1045,1048,1049,1050,1053,1054,1055],{"id":223,"depth":820,"text":224},{"id":252,"depth":820,"text":1043},"O relatório dizia pouco. O curl disse tudo.",{"id":467,"depth":820,"text":468},{"id":516,"depth":820,"text":517,"children":1046},[1047],{"id":560,"depth":838,"text":561},{"id":606,"depth":820,"text":607},{"id":652,"depth":820,"text":653},{"id":757,"depth":820,"text":758,"children":1051},[1052],{"id":900,"depth":838,"text":901},{"id":950,"depth":820,"text":951},{"id":990,"depth":820,"text":991},{"id":1025,"depth":820,"text":1026},"2026-09-18","Como ajustei o LCP de 6,8s para 2,0s e a nota de performance do Nuxt 4 De 66 a 97 corrigindo CSS inline, SSR seletivo, imagens e cache.","md",true,{},"/blog/de-69-a-97-no-lighthouse",{"title":7,"description":1057},"blog/de-69-a-97-no-lighthouse",[1065,1066,1067,1068,1069],"Nuxt","Web Performance","Lighthouse","SSR","Core Web Vitals","c32YhHvI09aC-MITnkxNcfXAK-fGqntNeekO2dUnh9Q",{"id":1072,"title":1073,"body":1074,"category":1392,"date":1393,"description":1394,"extension":1058,"featured":1059,"meta":1395,"navigation":1059,"path":1396,"seo":1397,"stem":1398,"tags":1399,"__hash__":1404},"blog/blog/my-bullet-journal.md","My Bullet Journal: De Cadernos Físicos ao meu Próprio App",{"type":9,"value":1075,"toc":1383},[1076,1080,1084,1094,1100,1104,1118,1128,1132,1158,1161,1201,1235,1238,1243,1246,1249,1252,1256,1262,1276,1284,1290,1306,1310,1321,1331,1338,1341,1347,1358,1370,1380],[12,1077,1079],{"id":1078},"my-bullet-journal-organização-e-produtividade-com-um-toque-pessoal","My Bullet Journal: Organização e Produtividade com um Toque Pessoal",[221,1081,1083],{"id":1082},"introdução","Introdução",[16,1085,1086,1087],{},"Uma introdução bem feita é essencial para engajar o leitor, mas vou ser sincero: ainda estou aprimorando essa habilidade. 😅\n",[1088,1089,1090],"small",{},[1091,1092,1093],"i",{},"brinks - eu não gosto de encheção de linguiça",[16,1095,1096,1097,402],{},"Portanto, vamos direto ao ponto: se você já sabe o que é um Bullet Journal, pule direto para a ",[28,1098,1099],{},"O \"Meu Método\" de Bullet Journal",[221,1101,1103],{"id":1102},"o-que-é-um-bullet-journal","O que é um Bullet Journal?",[16,1105,1106,1107,1112,1113,1117],{},"O Bullet Journal é um sistema de organização pessoal criado pelo designer ",[20,1108,1111],{"href":1109,"rel":1110},"https://bulletjournal.com/pages/rydercarroll?srsltid=AfmBOooExbpDfg5uTPXKTmKXL-zdRhqlt3shWao5VZ2ZQHLUU9tFOJTx",[24],"Ryder Carroll",". Ele combina, de forma brilhante (🚨",[1114,1115,1116],"del",{},"OPINIÃO!","), elementos de um diário, planner tradicional e lista de tarefas.",[16,1119,1120,1121,1124,1125,799],{},"A maior vantagem do método é ter sido projetado para ser ",[28,1122,1123],{},"100% flexível e adaptável"," às necessidades de quem o utiliza. Em vez de se forçar a caber nas caixas de um planner pronto, você cria o sistema que funciona para o seu cérebro. O método utiliza símbolos específicos para categorizar tarefas, eventos e notas, facilitando a visualização rápida e o acompanhamento das atividades diárias (o famoso ",[262,1126,1127],{},"Rapid Logging",[558,1129,1131],{"id":1130},"por-que-o-bullet-journal-funciona","Por que o Bullet Journal Funciona?",[563,1133,1134,1140,1146,1152],{},[532,1135,1136,1139],{},[28,1137,1138],{},"Flexibilidade Total:"," O método é altamente personalizável. Você só cria o que realmente vai usar.",[532,1141,1142,1145],{},[28,1143,1144],{},"Centralização:"," Mantém todas as suas tarefas, eventos e pensamentos em um único lugar, eliminando o caos de post-its e cadernos espalhados.",[532,1147,1148,1151],{},[28,1149,1150],{},"Expressão Criativa:"," Permite que você expresse sua criatividade por meio de layouts personalizados, desenhos e trackers visuais.",[532,1153,1154,1157],{},[28,1155,1156],{},"Mindfulness Intencional:"," O simples ato de escrever à mão (e revisar as tarefas) ajuda a aumentar a consciência sobre onde você gasta seu tempo, promovendo foco e presença.",[221,1159,1099],{"id":1160},"o-meu-método-de-bullet-journal",[1162,1163,1171,1172,1171,1185],"div",{"className":1164},[1165,1166,1167,1168,1169,1170],"flex","flex-col","md:flex-row","items-center","gap-6","mb-8","\n    ",[39,1173],{"src":1174,"alt":1175,"className":1176},"/blog/my-bullet-journal/rules.jpeg","Regras do meu Bullet Journal",[1177,1178,1179,1180,1181,1182,1183,1184],"w-40","h-40","md:w-48","md:h-48","object-cover","object-center","rounded-xl","shrink-0",[1162,1186,1189,1190,1193],{"className":1187},[1188],"flex-1","A ideia era adaptar o método tradicional do Bullet Journal para um formato que se encaixe melhor na minha rotina e preferências. Eu criei até regras que eu achava interessante para mim",[1191,1192],"br",{},[1088,1194,1195,1196,1200],{},"Dica:* A melhor parte do Bullet Journal é que não existem regras fixas globais, mas é essencial criar as ",[1197,1198,1199],"b",{},"suas"," regras e segui-las com consistência.",[563,1202,1203,1213,1223,1229],{},[532,1204,1205,1208,1209,1212],{},[28,1206,1207],{},"Cíclos de 15 Dias:"," Em vez do planejamento mensal tradicional, estabeleça uma divisão em períodos de 15 dias (quase como uma ",[262,1210,1211],{},"sprint"," de desenvolvimento).",[532,1214,1215,1218,1219,1222],{},[28,1216,1217],{},"Métricas e Dashboards:"," Em cada período, crie gráficos e ",[262,1220,1221],{},"trackers"," personalizados para visualizar seu progresso de forma intuitiva e acompanhar seus objetivos, como a boa velha tentativa de ir à academia. 💪",[532,1224,1225,1228],{},[28,1226,1227],{},"Revisão Diária:"," Não inicie o dia sem revisar o que foi planejado e o que foi realizado no dia anterior. Essa prática ajuda a manter o foco e a disciplina, além de promover uma reflexão constante sobre prioridades e hábitos.",[532,1230,1231,1234],{},[28,1232,1233],{},"Todo list básico:"," Aqui, apesar de básico tinha regras específicas, como: riscar tarefas concluídas, migrar tarefas não concluídas para o próximo período e evitar a tentação de criar listas intermináveis de tarefas que nunca são concluídas.",[16,1236,1237],{},"Abaixo, compartilho algumas fotos do meu \"Bullet Journal\" físico na época em que eu fazia tudo no papel. Perceba que tem páginas completas e outras incompletas:",[1239,1240],"slide-show",{":slides":1241,":timer":1242},"[{\"url\":\"/blog/my-bullet-journal/init001.jpeg\"},{\"url\":\"/blog/my-bullet-journal/ini002.jpeg\"},{\"url\":\"/blog/my-bullet-journal/init003.jpeg\"},{\"url\":\"/blog/my-bullet-journal/init04.jpeg\"},{\"url\":\"/blog/my-bullet-journal/full001.jpeg\"},{\"url\":\"/blog/my-bullet-journal/full002.jpeg\"},{\"url\":\"/blog/my-bullet-journal/full003.jpeg\"},{\"url\":\"/blog/my-bullet-journal/full200.jpeg\"},{\"url\":\"/blog/my-bullet-journal/full300.jpeg\"},{\"url\":\"/blog/my-bullet-journal/happy new year.jpeg\"},{\"url\":\"/blog/my-bullet-journal/stopped.jpeg\"}]","10",[16,1244,1245],{},"Após escrever as regras, percebi uma coisa: estou criando um sistema \"iqual\" ao sistema que eu já uso no trabalho/dia a dia? 😂",[16,1247,1248],{},"Sprints, scrum e etc? Bem, parece que sim, parece que não. 🤔",[16,1250,1251],{},"No fim das contas, o método é uma adaptação do Bullet Journal, mas com uma pegada mais \"ágil\" e orientada a resultados. Ele me ajuda a manter o foco e a disciplina, mas também tinha um lado criativo que me motivava a continuar usando.",[221,1253,1255],{"id":1254},"o-problema-spoiler-que-preguiça","O Problema: Spoiler... Que Preguiça!",[16,1257,1258,1259,402],{},"Tudo parecia perfeito, certo? Lindo, organizado, gratificante. Mas havia um grande problema: ",[28,1260,1261],{},"manutenção",[563,1263,1264,1270],{},[532,1265,1266,1269],{},[28,1267,1268],{},"A Fricção do Setup:"," O tempo de configuração era altíssimo. Criar layouts personalizados, desenhar tabelas, alinhar tudo com a régua...Isso mesmo régua, canetinhas coloridas e tudo isso é demorado. Quando você tem uma agenda de trabalho intensa, gastar horas desenhando o planner da semana seguinte se torna insustentável.",[532,1271,1272,1275],{},[28,1273,1274],{},"A Queda da Consistência:"," Por causa dessa fricção, eu começava os períodos com entusiasmo, mas abandonava o acompanhamento após algumas semanas pela simples falta de tempo de \"desenhar o sistema\".",[623,1277,1278],{},[16,1279,1280,1283],{},[28,1281,1282],{},"A Realidade:"," Eu gostava muito do trabalho manual, mas a \"obrigação\" de redesenhar métricas a cada 15 dias acabou me desestimulando. O sentimento de dever cumprido ao ver o tracker preenchido não compensava mais a preguiça colossal de prepará-lo.",[16,1285,1286,1287],{},"Isso me levou à inevitável reflexão de engenheiro de software: ",[28,1288,1289],{},"\"Por que não automatizar a parte chata e ficar só com a parte útil?\"",[16,1291,1292],{},[1088,1293,1294,1297,1298,1301,1302,1305],{},[1091,1295,1296],{},"\"Ah, mas você podia ter comprado um planner pronto ou baixado um app qualquer (Todo list) da vida\"."," Sim, poderia. Mas eu queria a ",[28,1299,1300],{},"minha"," metodologia, com as ",[28,1303,1304],{},"minhas"," regras, sem me adaptar ao fluxo de terceiros.",[221,1307,1309],{"id":1308},"a-solução-my-journal-app","A Solução: My Journal APP",[16,1311,1312,1313,1320],{},"Foi dessa dor e dessa preguiça que nasceu o ",[28,1314,1315],{},[20,1316,1319],{"href":1317,"rel":1318},"https://journal.rdpedrosa.dev/",[24],"My Journal","!",[16,1322,1323,1324,1327,1328,402],{},"Trata-se de uma aplicação web inteira criada por mim, ainda em desenvolvimento ",[262,1325,1326],{},"(18/03/2026)",", com o único propósito de organizar minha vida pessoal e profissional de forma eficiente, sem o atrito do papel, ",[262,1329,1330],{},"de ter que montar a estrutura da semana toda vez",[16,1332,1333,1334,1337],{},"Ele é ",[28,1335,1336],{},"100% inspirado"," no método que eu utilizava no caderno. Ele força as mesmas regras (como a visualização e controle de hábitos) mas em um formato digital. Toda aquela parte de desenhar tabelas, calcular gráficos e preparar a próxima semana foi automatizada. Eu só abro, logo o que preciso e fecho.",[16,1339,1340],{},"O sistema está rodando no ar, hospedado diretamente no meu cluster, é um WPA, ou seja roda como um aplicativo web progressivo direto no PC, usa indexDB logo a base de dados é local.",[16,1342,1343,1344],{},"A melhor parte? Eu decidi abrir o acesso. ",[28,1345,1346],{},"É gratuito e você pode usar se quiser:",[16,1348,1349,1350,1353,1354],{},"👉 ",[28,1351,1352],{},"Acesse aqui:"," ",[20,1355,1357],{"href":1317,"rel":1356},[24],"journal.rdpedrosa.dev",[16,1359,1360,1361,1366,1367],{},"E para não deixar ninguém perdido, também escrevi um ",[20,1362,1365],{"href":1363,"rel":1364},"https://journal.rdpedrosa.dev/journal-guide",[24],"Guia de Utilização"," explicando a lógica do sistema e como eu penso a rotina lá dentro. Fiquem à vontade para explorar e, claro, me enviar feedbacks ou sugestões. ",[262,1368,1369],{},"pode vir, mas vem com calma ok? ❤️",[16,1371,1372,1373,1376,1377,1379],{},"Lembrem-se: é um software \"opinativo\", feito originalmente para resolver o ",[262,1374,1375],{},"meu"," problema e seguir a ",[262,1378,1300],{}," cabeça. Pode ser que não se adapte perfeitamente à sua vida... ou pode ser exatamente a ferramenta que você estava procurando. Quem sabe, né?",[1239,1381],{":slides":1382,":timer":1242},"[{\"url\":\"/blog/my-bullet-journal/my-journal-new.png\"},{\"url\":\"/blog/my-bullet-journal/daily-review.png\"}]",{"title":806,"searchDepth":820,"depth":820,"links":1384},[1385,1386,1389,1390,1391],{"id":1082,"depth":820,"text":1083},{"id":1102,"depth":820,"text":1103,"children":1387},[1388],{"id":1130,"depth":838,"text":1131},{"id":1160,"depth":820,"text":1099},{"id":1254,"depth":820,"text":1255},{"id":1308,"depth":820,"text":1309},"Productivity","2026-03-18","Descubra como adaptei o método do Bullet Journal para organizar minha rotina e por que decidi transformar essa experiência em um aplicativo web gratuito.",{},"/blog/my-bullet-journal",{"title":1073,"description":1394},"blog/my-bullet-journal",[1400,1401,1402,1403],"Produtividade","Bullet Journal","Engenharia de Software","App","YrwGy1M_WCaAhBn5x855Ni5aVyfhQFyQvz-0apfRjBw",{"id":1406,"title":1407,"body":1408,"category":1572,"date":1573,"description":1574,"extension":1058,"featured":1059,"meta":1575,"navigation":1059,"path":1576,"seo":1577,"stem":1578,"tags":1579,"__hash__":1583},"blog/blog/volta-a-universidade-engenharia-ia.md","De Volta à Universidade: Engenharia de IA Aplicada",{"type":9,"value":1409,"toc":1568},[1410,1413,1420,1454,1458,1461,1471,1474,1482,1486,1489,1546,1558,1565],[16,1411,1412],{},"Para vocês, pode ser novidade; para mim, já faz mais de um mês. Será que estou atrasado em contar?",[16,1414,1415,1416,1419],{},"A grande notícia é: ",[28,1417,1418],{},"Voltei à universidade!"," 🎓",[1162,1421,1171,1426,1443],{"className":1422},[1165,1166,1423,1424,1425,1169,1170],"lg:flex-row","items-start","md:items-center",[39,1427],{"src":1428,"alt":1429,"className":1430},"/embaixador-pos-ia.png","Rafa Pedrosa - Embaixador Pós-Graduação IA Aplicada UNIPDS",[1431,1432,1433,1434,1435,1436,1437,1183,1438,1439,1440,1441,1442],"w-full","max-w-sm","mx-auto","md:max-w-xs","md:float-right","md:ml-8","mb-6","shadow-lg","dark:shadow-none","border","border-gray-100","dark:border-gray-800",[810,1444,1445,1446],{},"\nHá mais de um ano, venho me questionando profundamente sobre minha carreira: Como elevar meu patamar como profissional? Qual é a melhor forma de apresentar minhas capacidades e maturidade técnica para o mercado? \n",[16,1447,1448,1449,1453],{},"Como mencionei em ",[20,1450,1452],{"href":1451},"https://www.linkedin.com/feed/update/urn:li:activity:7432901732835377152/?originTrackingId=88KEM0WNmyxrrKzHPl8XNg%3D%3D","neste post",", utilizar mais as redes como o LinkedIn para mostrar projetos e a rotina do dia a dia é o caminho ideal para mostrar o \"quem eu sou\". Mas e o lado técnico? Como validar e aprofundar a base construída ao longo dos anos?\n",[221,1455,1457],{"id":1456},"o-novo-desafio-pós-graduação","O Novo Desafio: Pós-graduação",[16,1459,1460],{},"Decidi buscar uma nova formação acadêmica para consolidar meus mais de 13 anos de experiência na área de tecnologia.",[16,1462,1463,1464,1467,1468,402],{},"Atualmente, estou cursando a pós-graduação em ",[28,1465,1466],{},"Engenharia de IA Aplicada"," pela UNIPDS. Estou me dedicando ao máximo, não apenas pelo título, mas com o objetivo genuíno de contribuir com uma comunidade que vai muito além de um simples curso ",[262,1469,1470],{},"lato sensu",[16,1472,1473],{},"Durante o próximo ano, meu foco total será desenvolver habilidades práticas e aprofundadas em Inteligência Artificial. E o mais legal de tudo isso: estou participando ativamente como \"Embaixador\", apoiando outros alunos, tirando dúvidas e facilitando a rica troca de conhecimento entre nós.",[623,1475,1476],{},[16,1477,1478,1481],{},[28,1479,1480],{},"💡 Insight:"," A decisão de voltar a estudar formalmente não substitui a experiência prática, mas fornece um alicerce sólido e estruturado para dominar tecnologias complexas como a Inteligência Artificial.",[221,1483,1485],{"id":1484},"título-acadêmico-ou-cursos-online","Título Acadêmico ou Cursos Online?",[16,1487,1488],{},"Essa é uma daquelas perguntas que sempre geram polêmica. O que vale mais: um título acadêmico formal ou cinco certificados de cursos online rápidos?",[47,1490,1491,1505],{},[50,1492,1493],{},[53,1494,1495,1499,1502],{},[56,1496,1498],{"align":1497},"left","Critério",[56,1500,1501],{"align":1497},"Título Acadêmico (Pós-graduação)",[56,1503,1504],{"align":1497},"Cursos Online Curtos",[66,1506,1507,1520,1533],{},[53,1508,1509,1514,1517],{},[71,1510,1511],{"align":1497},[28,1512,1513],{},"Profundidade",[71,1515,1516],{"align":1497},"Imersão estruturada, teórica e prática aprofundada",[71,1518,1519],{"align":1497},"Foco direto em habilidades rápidas e ferramentas específicas",[53,1521,1522,1527,1530],{},[71,1523,1524],{"align":1497},[28,1525,1526],{},"Networking",[71,1528,1529],{"align":1497},"Forte (interação contínua com professores, comunidade e embaixadores)",[71,1531,1532],{"align":1497},"Geralmente limitado ou assíncrono",[53,1534,1535,1540,1543],{},[71,1536,1537],{"align":1497},[28,1538,1539],{},"Validação",[71,1541,1542],{"align":1497},"Reconhecimento formal e chancelado por instituições",[71,1544,1545],{"align":1497},"Validação rápida, ideal para acompanhar tendências de curto prazo",[623,1547,1548],{},[16,1549,1550,1553,1554,1557],{},[28,1551,1552],{},"⚠️ Reflexão:"," Na minha visão, ",[28,1555,1556],{},"ambos são válidos e essenciais",". No entanto, para aqueles que acreditam que na computação não é mais necessário se atualizar através da universidade, ou que diplomas não contam história... fica o questionamento: será mesmo verdade?",[16,1559,1560,1561,1564],{},"Quais são as suas referências técnicas hoje? Elas focam apenas em cursos rápidos ou possuem também um forte embasamento formal? Acredito que a combinação de ambos, o famoso ",[262,1562,1563],{},"Lifelong Learning",", seja o verdadeiro diferencial para carreiras duradouras.",[16,1566,1567],{},"Reflita 🤔",{"title":806,"searchDepth":820,"depth":820,"links":1569},[1570,1571],{"id":1456,"depth":820,"text":1457},{"id":1484,"depth":820,"text":1485},"IA","2026-03-11","Por que decidi voltar à universidade após 13 anos de experiência na área de tecnologia para estudar Engenharia de Inteligência Artificial.",{},"/blog/volta-a-universidade-engenharia-ia",{"title":1407,"description":1574},"blog/volta-a-universidade-engenharia-ia",[1580,1581,1582,1563],"AI","Carreira","Machine Learning","rFnxF6tvp6pYmBQFctrgkOYMNXvsJALjUSnKRSkwLsI",{"id":1585,"title":1586,"body":1587,"category":1860,"date":1861,"description":1862,"extension":1058,"featured":1059,"meta":1863,"navigation":1059,"path":1865,"seo":1866,"stem":1867,"tags":1868,"__hash__":1873},"blog/blog/nuxt4-alicerce.md","Welcome to my first blog post! And my site is finally live!",{"type":9,"value":1588,"toc":1852},[1589,1593,1596,1603,1607,1614,1620,1623,1627,1636,1647,1655,1659,1662,1695,1777,1781,1788,1791,1814,1822,1826,1833,1846,1849],[12,1590,1592],{"id":1591},"construindo-meu-portfólio-com-nuxt-4-e-nuxt-content","Construindo meu Portfólio com Nuxt 4 e Nuxt Content",[16,1594,1595],{},"Fazia muito tempo que eu não tinha um site pessoal. Se a memória não me falha, a última vez foi há anos — talvez hospedado no saudoso GitHub Pages? Era algo bem simples, servia apenas para exibir meu currículo e nada mais.",[16,1597,1598,1599,1602],{},"Recentemente, me peguei pensando: ",[262,1600,1601],{},"\"Por que não fazer um novo? Mas, dessa vez, algo sério?\""," Decidi voltar a estudar (fazer uma pós graduação), ganhar mais espaço no mercado, me atualizar e, claro, me expor mais ao mundo. O primeiro passo óbvio era ter um espaço meu na internet.",[221,1604,1606],{"id":1605},"o-desafio-simplicidade-vs-funcionalidade","O Desafio: Simplicidade vs. Funcionalidade",[16,1608,1609,1610,1613],{},"A premissa era clara: o site precisava ser ",[28,1611,1612],{},"simples de manter",", mas eu queria que ele funcionasse como um CMS (Content Management System).",[16,1615,1616,1617,402],{},"Fazer um do zero, construindo banco de dados, API e painel administrativo? Claro! IA na veia! Mas, cinco minutos depois, a realidade bateu: ",[262,1618,1619],{},"\"Espera... que trabalheira. Tem que ser simples!\"",[16,1621,1622],{},"A alternativa clássica seria o WordPress. Porém, eu queria usar o ecossistema e as tecnologias com as quais já trabalho no meu dia a dia como Software Engineer. Comecei então a debater com ferramentas de Inteligência Artificial (mais específicamente o Gemini) para explorar ideias de arquitetura, mas nenhuma sugestão brilhava aos olhos. Eu queria uma página inicial estilo CV vivo, um espaço para portfólio e um \"Techbrain/Blog\".",[221,1624,1626],{"id":1625},"a-descoberta-do-nuxt-content","A Descoberta do Nuxt Content",[16,1628,1629,1630,1632,1633],{},"Até que me lembrei vagamente de uma extensão ou biblioteca do ",[28,1631,1065],{}," que havia visto no passado. Perguntei novamente para a IA sobre isso e a resposta foi imediata: ",[28,1634,1635],{},"Nuxt Content!",[16,1637,1638,1639,1642,1643,1646],{},"Entrei na documentação e vi que a ferramenta fazia ",[28,1640,1641],{},"exatamente"," o que eu queria: transformava arquivos Markdown (",[244,1644,1645],{},".md",") e Frontmatter YAML em HTML altamente otimizado. Era o casamento perfeito entre a experiência de desenvolvedor (DX) que eu buscava e a simplicidade de publicação que eu precisava.",[623,1648,1649],{},[16,1650,1651,1654],{},[28,1652,1653],{},"💡 O Pulo do Gato:"," Com o Nuxt Content v3, o site se comporta como um CMS estático completo. Zero banco de dados, zero dor de cabeça. O conteúdo é processado no tempo de build (SSG), garantindo performance nota 100 no Lighthouse.",[558,1656,1658],{"id":1657},"o-stack-técnico-que-escolhi","O Stack Técnico que Escolhi",[16,1660,1661],{},"Para dar vida a essa ideia, o alicerce foi construído com as melhores ferramentas do ecossistema Vue:",[563,1663,1664,1674,1683,1689],{},[532,1665,1666,1669,1670,1673],{},[28,1667,1668],{},"Nuxt 4:"," Aproveitando a nova estrutura de pastas (",[244,1671,1672],{},"app/",") que separa logicamente o código da aplicação das configurações.",[532,1675,1676,1679,1680,1682],{},[28,1677,1678],{},"Nuxt Content v3:"," O motor que lê os arquivos ",[244,1681,1645],{}," e os entrega tipados (Type Safety total) para os componentes Vue.",[532,1684,1685,1688],{},[28,1686,1687],{},"Tailwind CSS + Typography Plugin:"," Para garantir que o HTML gerado pelo Markdown ficasse bonito, responsivo e adaptado ao Dark Mode sem esforço manual.",[532,1690,1691,1694],{},[28,1692,1693],{},"Shiki:"," Syntax highlighting de alta qualidade para os blocos de código.",[801,1696,1698],{"className":803,"code":1697,"language":805,"meta":806,"style":806},"// Exemplo de como o código fica bonito no post!\nconst techStack: string[] = ['Nuxt 4', 'Nuxt Content', 'Tailwind', 'Shiki'];\nconst isHappy = techStack.every(tech => tech.isAwesome);\n",[244,1699,1700,1705,1748],{"__ignoreMap":806},[810,1701,1702],{"class":812,"line":813},[810,1703,1704],{"class":816},"// Exemplo de como o código fica bonito no post!\n",[810,1706,1707,1710,1713,1715,1718,1721,1724,1727,1730,1732,1735,1737,1740,1742,1745],{"class":812,"line":820},[810,1708,1709],{"class":823},"const",[810,1711,1712],{"class":854}," techStack",[810,1714,576],{"class":823},[810,1716,1717],{"class":854}," string",[810,1719,1720],{"class":834},"[] ",[810,1722,1723],{"class":823},"=",[810,1725,1726],{"class":834}," [",[810,1728,1729],{"class":847},"'Nuxt 4'",[810,1731,265],{"class":834},[810,1733,1734],{"class":847},"'Nuxt Content'",[810,1736,265],{"class":834},[810,1738,1739],{"class":847},"'Tailwind'",[810,1741,265],{"class":834},[810,1743,1744],{"class":847},"'Shiki'",[810,1746,1747],{"class":834},"];\n",[810,1749,1750,1752,1755,1758,1761,1764,1767,1771,1774],{"class":812,"line":838},[810,1751,1709],{"class":823},[810,1753,1754],{"class":854}," isHappy",[810,1756,1757],{"class":823}," =",[810,1759,1760],{"class":834}," techStack.",[810,1762,1763],{"class":830},"every",[810,1765,1766],{"class":834},"(",[810,1768,1770],{"class":1769},"s4XuR","tech",[810,1772,1773],{"class":823}," =>",[810,1775,1776],{"class":834}," tech.isAwesome);\n",[221,1778,1780],{"id":1779},"sobre-o-desenvolvimento","Sobre o Desenvolvimento",[16,1782,1783,1784,1787],{},"A parte mais interessante dessa jornada é que ela não foi tão longa no sentido de escrever código complexo ou integrar APIs obscuras. O verdadeiro desafio foi ",[28,1785,1786],{},"organizar as informações",", pensar na estrutura dos dados (quais campos o Frontmatter do Markdown precisava ter) e desenhar a Arquitetura de Informação do site.",[16,1789,1790],{},"Hoje, meu fluxo de trabalho para publicar um novo artigo ou projeto é simplesmente maravilhoso:",[529,1792,1793,1799,1802,1811],{},[532,1794,1795,1796,1798],{},"Copio e colo um template ",[244,1797,1645],{}," que deixei pronto.",[532,1800,1801],{},"Escrevo o conteúdo como se fosse um texto comum no meu editor favorito.",[532,1803,1804,1805,494,1808,402],{},"Dou um ",[244,1806,1807],{},"git commit",[244,1809,1810],{},"git push",[532,1812,1813],{},"O CI/CD do meu Home Lab local intercepta o push e faz o build/deploy automático.",[623,1815,1816],{},[16,1817,1818,1821],{},[28,1819,1820],{},"⚠️ Alerta de Spoiler:"," A configuração desse meu Home Lab com GitLab CI e Node é uma história longa e que, com certeza, vai render um post dedicado no futuro!",[221,1823,1825],{"id":1824},"conclusão","Conclusão",[16,1827,1828,1829,1832],{},"Estou extremamente satisfeito com o resultado. Construí exatamente o que eu queria, usando as ferramentas que eu adoro, e o processo de manutenção agora é fluido e indolor. Este portfólio não é apenas um site; é um ",[262,1830,1831],{},"living lab"," onde posso testar e mostrar um pouco de mim e do meu trabalho.",[16,1834,1835,1836,1841,1842,799],{},"Espero que tenha gostado do site e da fluidez da navegação! Me dê um feedback depois. Entre em contato pelo ",[20,1837,1840],{"href":1838,"rel":1839},"https://www.linkedin.com/in/rafadpedrosa",[24],"LinkedIn"," ou me mande um email (",[20,1843,1845],{"href":1844},"mailto:rafadpedrosa@gmail.com","rafadpedrosa@gmail.com",[16,1847,1848],{},"Um abraço!",[1036,1850,1851],{},"html pre.shiki code .sJ8bj, html code.shiki .sJ8bj{--shiki-default:#6A737D;--shiki-dark:#6A737D}html pre.shiki code .szBVR, html code.shiki .szBVR{--shiki-default:#D73A49;--shiki-dark:#F97583}html pre.shiki code .sj4cs, html code.shiki .sj4cs{--shiki-default:#005CC5;--shiki-dark:#79B8FF}html pre.shiki code .sVt8B, html code.shiki .sVt8B{--shiki-default:#24292E;--shiki-dark:#E1E4E8}html pre.shiki code .sZZnC, html code.shiki .sZZnC{--shiki-default:#032F62;--shiki-dark:#9ECBFF}html pre.shiki code .sScJk, html code.shiki .sScJk{--shiki-default:#6F42C1;--shiki-dark:#B392F0}html pre.shiki code .s4XuR, html code.shiki .s4XuR{--shiki-default:#E36209;--shiki-dark:#FFAB70}html .default .shiki span {color: var(--shiki-default);background: var(--shiki-default-bg);font-style: var(--shiki-default-font-style);font-weight: var(--shiki-default-font-weight);text-decoration: var(--shiki-default-text-decoration);}html .shiki span {color: var(--shiki-default);background: var(--shiki-default-bg);font-style: var(--shiki-default-font-style);font-weight: var(--shiki-default-font-weight);text-decoration: var(--shiki-default-text-decoration);}html .dark .shiki span {color: var(--shiki-dark);background: var(--shiki-dark-bg);font-style: var(--shiki-dark-font-style);font-weight: var(--shiki-dark-font-weight);text-decoration: var(--shiki-dark-text-decoration);}html.dark .shiki span {color: var(--shiki-dark);background: var(--shiki-dark-bg);font-style: var(--shiki-dark-font-style);font-weight: var(--shiki-dark-font-weight);text-decoration: var(--shiki-dark-text-decoration);}",{"title":806,"searchDepth":820,"depth":820,"links":1853},[1854,1855,1858,1859],{"id":1605,"depth":820,"text":1606},{"id":1625,"depth":820,"text":1626,"children":1856},[1857],{"id":1657,"depth":838,"text":1658},{"id":1779,"depth":820,"text":1780},{"id":1824,"depth":820,"text":1825},"Web Development","2026-02-25","Criando meu site \"CMS\" usando Markdown, Nuxt 4 e Tailwind CSS.",{"isOnReview":1864},false,"/blog/nuxt4-alicerce",{"title":1586,"description":1862},"blog/nuxt4-alicerce",[1065,1869,1870,1871,1872],"Vue","Frontend","Static site","CMS","7wlOgv06z28U6diu-YH2gZA_WnK_qlIzfqNKbAfxxsU",{"id":1875,"title":1876,"body":1877,"category":2204,"date":2205,"description":2206,"extension":1058,"featured":1059,"meta":2207,"navigation":1059,"path":2208,"seo":2209,"stem":2210,"tags":2211,"__hash__":2216},"blog/blog/assinaturas-clicksign-fastify-nuxt.md","Arquitetura de Assinaturas Digitais: Integrando Clicksign, Fastify e Nuxt",{"type":9,"value":1878,"toc":2197},[1879,1883,1886,1893,1897,1900,1917,1921,1989,1993,1996,2171,2175,2183,2191,2194],[12,1880,1882],{"id":1881},"this-is-a-test","this is a test",[12,1884,1876],{"id":1885},"arquitetura-de-assinaturas-digitais-integrando-clicksign-fastify-e-nuxt",[16,1887,1888,1889,1892],{},"No projeto ",[28,1890,1891],{},"Reserva Tamandaré",", o maior desafio técnico foi garantir a segurança e a integridade do fluxo de assinaturas de contratos de compra e venda.",[221,1894,1896],{"id":1895},"o-desafio","O Desafio",[16,1898,1899],{},"Precisávamos de um fluxo assíncrono onde:",[529,1901,1902,1905,1911,1914],{},[532,1903,1904],{},"O contrato é gerado no backend.",[532,1906,1907,1908,402],{},"Enviado para a API da ",[28,1909,1910],{},"Clicksign",[532,1912,1913],{},"Notificado via Webhook quando assinado.",[532,1915,1916],{},"Refletido instantaneamente no dashboard Nuxt do cliente.",[221,1918,1920],{"id":1919},"diagrama-lógico-da-solução","Diagrama Lógico da Solução",[47,1922,1923,1936],{},[50,1924,1925],{},[53,1926,1927,1930,1933],{},[56,1928,1929],{"align":1497},"Etapa",[56,1931,1932],{"align":1497},"Componente",[56,1934,1935],{"align":1497},"Ação",[66,1937,1938,1951,1964,1976],{},[53,1939,1940,1945,1948],{},[71,1941,1942],{"align":1497},[28,1943,1944],{},"Trigger",[71,1946,1947],{"align":1497},"Nuxt 3 (Client)",[71,1949,1950],{"align":1497},"Usuário confirma dados do contrato.",[53,1952,1953,1958,1961],{},[71,1954,1955],{"align":1497},[28,1956,1957],{},"Processo",[71,1959,1960],{"align":1497},"Fastify (API)",[71,1962,1963],{"align":1497},"Gera PDF e chama a Clicksign.",[53,1965,1966,1970,1973],{},[71,1967,1968],{"align":1497},[28,1969,1935],{},[71,1971,1972],{"align":1497},"Clicksign (External)",[71,1974,1975],{"align":1497},"Envia e-mail e gerencia assinaturas.",[53,1977,1978,1983,1986],{},[71,1979,1980],{"align":1497},[28,1981,1982],{},"Feedback",[71,1984,1985],{"align":1497},"Webhook (Node.js)",[71,1987,1988],{"align":1497},"Recebe evento e atualiza o PostgreSQL.",[221,1990,1992],{"id":1991},"implementação-técnica-trecho-de-código","Implementação Técnica (Trecho de Código)",[16,1994,1995],{},"Abaixo, um exemplo simplificado de como gerenciamos o webhook da Clicksign no Fastify:",[801,1997,1999],{"className":803,"code":1998,"language":805,"meta":806,"style":806},"// Webhook Handler\nfastify.post('/webhooks/clicksign', async (request, reply) => {\n  const { event, document } = request.body;\n\n  if (event.name === 'sign') {\n    // Atualiza status no banco de dados\n    await db.contracts.update({\n      where: { externalId: document.key },\n      data: { status: 'SIGNED' }\n    });\n    \n    return reply.code(200).send();\n  }\n});\n",[244,2000,2001,2006,2044,2068,2073,2090,2095,2108,2113,2124,2130,2136,2160,2165],{"__ignoreMap":806},[810,2002,2003],{"class":812,"line":813},[810,2004,2005],{"class":816},"// Webhook Handler\n",[810,2007,2008,2011,2014,2016,2019,2021,2024,2027,2030,2032,2035,2038,2041],{"class":812,"line":820},[810,2009,2010],{"class":834},"fastify.",[810,2012,2013],{"class":830},"post",[810,2015,1766],{"class":834},[810,2017,2018],{"class":847},"'/webhooks/clicksign'",[810,2020,265],{"class":834},[810,2022,2023],{"class":823},"async",[810,2025,2026],{"class":834}," (",[810,2028,2029],{"class":1769},"request",[810,2031,265],{"class":834},[810,2033,2034],{"class":1769},"reply",[810,2036,2037],{"class":834},") ",[810,2039,2040],{"class":823},"=>",[810,2042,2043],{"class":834}," {\n",[810,2045,2046,2049,2052,2055,2057,2060,2063,2065],{"class":812,"line":838},[810,2047,2048],{"class":823},"  const",[810,2050,2051],{"class":834}," { ",[810,2053,2054],{"class":854},"event",[810,2056,265],{"class":834},[810,2058,2059],{"class":854},"document",[810,2061,2062],{"class":834}," } ",[810,2064,1723],{"class":823},[810,2066,2067],{"class":834}," request.body;\n",[810,2069,2070],{"class":812,"line":844},[810,2071,2072],{"emptyLinePlaceholder":1059},"\n",[810,2074,2075,2078,2081,2084,2087],{"class":812,"line":861},[810,2076,2077],{"class":823},"  if",[810,2079,2080],{"class":834}," (event.name ",[810,2082,2083],{"class":823},"===",[810,2085,2086],{"class":847}," 'sign'",[810,2088,2089],{"class":834},") {\n",[810,2091,2092],{"class":812,"line":873},[810,2093,2094],{"class":816},"    // Atualiza status no banco de dados\n",[810,2096,2097,2100,2103,2106],{"class":812,"line":888},[810,2098,2099],{"class":823},"    await",[810,2101,2102],{"class":834}," db.contracts.",[810,2104,2105],{"class":830},"update",[810,2107,835],{"class":834},[810,2109,2110],{"class":812,"line":894},[810,2111,2112],{"class":834},"      where: { externalId: document.key },\n",[810,2114,2116,2119,2122],{"class":812,"line":2115},9,[810,2117,2118],{"class":834},"      data: { status: ",[810,2120,2121],{"class":847},"'SIGNED'",[810,2123,885],{"class":834},[810,2125,2127],{"class":812,"line":2126},10,[810,2128,2129],{"class":834},"    });\n",[810,2131,2133],{"class":812,"line":2132},11,[810,2134,2135],{"class":834},"    \n",[810,2137,2139,2142,2145,2147,2149,2152,2154,2157],{"class":812,"line":2138},12,[810,2140,2141],{"class":823},"    return",[810,2143,2144],{"class":834}," reply.",[810,2146,244],{"class":830},[810,2148,1766],{"class":834},[810,2150,2151],{"class":854},"200",[810,2153,799],{"class":834},[810,2155,2156],{"class":830},"send",[810,2158,2159],{"class":834},"();\n",[810,2161,2163],{"class":812,"line":2162},13,[810,2164,891],{"class":834},[810,2166,2168],{"class":812,"line":2167},14,[810,2169,2170],{"class":834},"});\n",[558,2172,2174],{"id":2173},"callouts-e-boas-práticas","Callouts e Boas Práticas",[623,2176,2177],{},[16,2178,2179,2182],{},[28,2180,2181],{},"Aviso:"," Sempre valide a assinatura (HMAC) dos webhooks recebidos para evitar ataques de injeção de dados.",[623,2184,2185],{},[16,2186,2187,2190],{},[28,2188,2189],{},"Dica:"," Utilize Redis para gerenciar o estado temporário dos documentos durante o fluxo de upload para garantir rapidez na UI.",[16,2192,2193],{},"Essa arquitetura permitiu uma redução de 40% no tempo de fechamento de contratos, provando que a integração eficiente de APIs é um diferencial de negócio.",[1036,2195,2196],{},"html pre.shiki code .sJ8bj, html code.shiki .sJ8bj{--shiki-default:#6A737D;--shiki-dark:#6A737D}html pre.shiki code .sVt8B, html code.shiki .sVt8B{--shiki-default:#24292E;--shiki-dark:#E1E4E8}html pre.shiki code .sScJk, html code.shiki .sScJk{--shiki-default:#6F42C1;--shiki-dark:#B392F0}html pre.shiki code .sZZnC, html code.shiki .sZZnC{--shiki-default:#032F62;--shiki-dark:#9ECBFF}html pre.shiki code .szBVR, html code.shiki .szBVR{--shiki-default:#D73A49;--shiki-dark:#F97583}html pre.shiki code .s4XuR, html code.shiki .s4XuR{--shiki-default:#E36209;--shiki-dark:#FFAB70}html pre.shiki code .sj4cs, html code.shiki .sj4cs{--shiki-default:#005CC5;--shiki-dark:#79B8FF}html .default .shiki span {color: var(--shiki-default);background: var(--shiki-default-bg);font-style: var(--shiki-default-font-style);font-weight: var(--shiki-default-font-weight);text-decoration: var(--shiki-default-text-decoration);}html .shiki span {color: var(--shiki-default);background: var(--shiki-default-bg);font-style: var(--shiki-default-font-style);font-weight: var(--shiki-default-font-weight);text-decoration: var(--shiki-default-text-decoration);}html .dark .shiki span {color: var(--shiki-dark);background: var(--shiki-dark-bg);font-style: var(--shiki-dark-font-style);font-weight: var(--shiki-dark-font-weight);text-decoration: var(--shiki-dark-text-decoration);}html.dark .shiki span {color: var(--shiki-dark);background: var(--shiki-dark-bg);font-style: var(--shiki-dark-font-style);font-weight: var(--shiki-dark-font-weight);text-decoration: var(--shiki-dark-text-decoration);}",{"title":806,"searchDepth":820,"depth":820,"links":2198},[2199,2200,2201],{"id":1895,"depth":820,"text":1896},{"id":1919,"depth":820,"text":1920},{"id":1991,"depth":820,"text":1992,"children":2202},[2203],{"id":2173,"depth":838,"text":2174},"Architecture","2026-02-21","Um mergulho técnico na implementação do fluxo de assinaturas digitais do Loteamento Reserva Tamandaré.",{"isOnReview":1059},"/blog/assinaturas-clicksign-fastify-nuxt",{"title":1876,"description":2206},"blog/assinaturas-clicksign-fastify-nuxt",[2212,2213,2214,2215],"API","Integration","Node.js","Security","YQWLhfTSUwxul-VOZ4xaQi7k8gylo8WFqpIx9YWvdGU",{"id":2218,"title":2219,"body":2220,"category":2281,"date":2205,"description":2282,"extension":1058,"featured":1864,"meta":2283,"navigation":1059,"path":2284,"seo":2285,"stem":2286,"tags":2287,"__hash__":2290},"blog/blog/rog-z13-workstation.md","Setup de Performance: ASUS ROG Z13 Flow como Workstation",{"type":9,"value":2221,"toc":2275},[2222,2224,2227,2234,2238,2241,2245,2265,2269,2272],[12,2223,1882],{"id":1881},[12,2225,2219],{"id":2226},"setup-de-performance-asus-rog-z13-flow-como-workstation",[16,2228,2229,2230,2233],{},"Para um Software Engineer focado em performance, o hardware é a extensão direta do cérebro. Minha escolha pelo ",[28,2231,2232],{},"ASUS ROG Z13 Flow"," reflete a busca por versatilidade e potência bruta.",[221,2235,2237],{"id":2236},"por-que-um-tablet-gamer","Por que um Tablet Gamer?",[16,2239,2240],{},"O Z13 Flow é, essencialmente, um \"Surface Pro com esteroides\". Com um processador Intel Core i9 e uma GPU dedicada, ele lida com builds pesados de Nuxt e múltiplos containers Docker sem suar.",[558,2242,2244],{"id":2243},"pontos-de-destaque","Pontos de Destaque:",[529,2246,2247,2253,2259],{},[532,2248,2249,2252],{},[28,2250,2251],{},"Portabilidade:"," Desenvolver de qualquer lugar sem sacrificar poder de processamento.",[532,2254,2255,2258],{},[28,2256,2257],{},"Resfriamento Líquido (Vapor Chamber):"," Crucial para manter o clock alto durante sessões intensas de codificação.",[532,2260,2261,2264],{},[28,2262,2263],{},"Ecrã de Alta Fidelidade:"," Essencial para refinamentos de UI com Tailwind CSS.",[221,2266,2268],{"id":2267},"o-fluxo-de-trabalho","O Fluxo de Trabalho",[16,2270,2271],{},"Utilizo o Z13 conectado a um setup de monitor ultrawide, onde ele atua como o cérebro central. A transição de \"mobile development\" para \"desktop workstation\" é instantânea, permitindo manter o fluxo criativo ininterrupto.",[16,2273,2274],{},"Este hardware é o parceiro ideal para explorar tecnologias como IA Generativa localmente e gerenciar arquiteturas complexas.",{"title":806,"searchDepth":820,"depth":820,"links":2276},[2277,2280],{"id":2236,"depth":820,"text":2237,"children":2278},[2279],{"id":2243,"depth":838,"text":2244},{"id":2267,"depth":820,"text":2268},"Hardware","Como transformei um tablet gamer em uma estação de trabalho de alto desempenho para desenvolvimento de software.",{"isOnReview":1059},"/blog/rog-z13-workstation",{"title":2219,"description":2282},"blog/rog-z13-workstation",[1392,2288,2281,2289],"Workstation","ROG Z13","4mF1EmF-PU-tWoNTMaqVgaJ3QioBNtuA-iesNn9wECY",{"id":2292,"title":2293,"body":2294,"category":75,"date":2371,"description":2372,"extension":1058,"featured":1864,"meta":2373,"navigation":1059,"path":2374,"seo":2375,"stem":2376,"tags":2377,"__hash__":2378},"blog/blog/otimizando-performance-nuxt.md","Otimizando Performance no Nuxt 3",{"type":9,"value":2295,"toc":2368},[2296,2298,2301,2304,2308,2327,2365],[12,2297,1882],{"id":1881},[12,2299,2293],{"id":2300},"otimizando-performance-no-nuxt-3",[16,2302,2303],{},"Alcançar performance máxima não é apenas sobre velocidade, é sobre experiência do usuário.",[221,2305,2307],{"id":2306},"dicas-rápidas","Dicas rápidas:",[529,2309,2310,2317,2320],{},[532,2311,2312,2313,2316],{},"Use ",[244,2314,2315],{},"NuxtImg"," para imagens.",[532,2318,2319],{},"Minimize o uso de bibliotecas externas pesadas.",[532,2321,2322,2323,2326],{},"Aproveite o ",[244,2324,2325],{},"Route Rules"," para cache SWR.",[801,2328,2330],{"className":803,"code":2329,"language":805,"meta":806,"style":806},"export default defineNuxtConfig({\n  routeRules: {\n    '/blog/**': { swr: 3600 }\n  }\n})\n",[244,2331,2332,2342,2346,2357,2361],{"__ignoreMap":806},[810,2333,2334,2336,2338,2340],{"class":812,"line":813},[810,2335,824],{"class":823},[810,2337,827],{"class":823},[810,2339,831],{"class":830},[810,2341,835],{"class":834},[810,2343,2344],{"class":812,"line":820},[810,2345,841],{"class":834},[810,2347,2348,2351,2353,2355],{"class":812,"line":838},[810,2349,2350],{"class":847},"    '/blog/**'",[810,2352,879],{"class":834},[810,2354,882],{"class":854},[810,2356,885],{"class":834},[810,2358,2359],{"class":812,"line":844},[810,2360,891],{"class":834},[810,2362,2363],{"class":812,"line":861},[810,2364,897],{"class":834},[1036,2366,2367],{},"html pre.shiki code .szBVR, html code.shiki .szBVR{--shiki-default:#D73A49;--shiki-dark:#F97583}html pre.shiki code .sScJk, html code.shiki .sScJk{--shiki-default:#6F42C1;--shiki-dark:#B392F0}html pre.shiki code .sVt8B, html code.shiki .sVt8B{--shiki-default:#24292E;--shiki-dark:#E1E4E8}html pre.shiki code .sZZnC, html code.shiki .sZZnC{--shiki-default:#032F62;--shiki-dark:#9ECBFF}html pre.shiki code .sj4cs, html code.shiki .sj4cs{--shiki-default:#005CC5;--shiki-dark:#79B8FF}html .default .shiki span {color: var(--shiki-default);background: var(--shiki-default-bg);font-style: var(--shiki-default-font-style);font-weight: var(--shiki-default-font-weight);text-decoration: var(--shiki-default-text-decoration);}html .shiki span {color: var(--shiki-default);background: var(--shiki-default-bg);font-style: var(--shiki-default-font-style);font-weight: var(--shiki-default-font-weight);text-decoration: var(--shiki-default-text-decoration);}html .dark .shiki span {color: var(--shiki-dark);background: var(--shiki-dark-bg);font-style: var(--shiki-dark-font-style);font-weight: var(--shiki-dark-font-weight);text-decoration: var(--shiki-dark-text-decoration);}html.dark .shiki span {color: var(--shiki-dark);background: var(--shiki-dark-bg);font-style: var(--shiki-dark-font-style);font-weight: var(--shiki-dark-font-weight);text-decoration: var(--shiki-dark-text-decoration);}",{"title":806,"searchDepth":820,"depth":820,"links":2369},[2370],{"id":2306,"depth":820,"text":2307},"2025-02-21","Como alcançar o score 100 no Lighthouse utilizando as melhores práticas do ecossistema Nuxt.",{"isOnReview":1059},"/blog/otimizando-performance-nuxt",{"title":2293,"description":2372},"blog/otimizando-performance-nuxt",[1065,1066,1067],"F1E5nY5I6RypFgLGHtTXNOYfBgpmBA1yzHAsEpkpaKg",{"id":2380,"title":2381,"body":2382,"category":2214,"date":2481,"description":2482,"extension":1058,"featured":1864,"meta":2483,"navigation":1059,"path":2484,"seo":2485,"stem":2486,"tags":2487,"__hash__":2490},"blog/blog/fastify-node-backend.md","Fastify: O futuro do Node.js backend",{"type":9,"value":2383,"toc":2478},[2384,2386,2389,2392,2396,2475],[12,2385,1882],{"id":1881},[12,2387,2381],{"id":2388},"fastify-o-futuro-do-nodejs-backend",[16,2390,2391],{},"O Fastify é um framework focado em baixo overhead e arquitetura baseada em plugins.",[221,2393,2395],{"id":2394},"exemplo-de-servidor","Exemplo de servidor:",[801,2397,2401],{"className":2398,"code":2399,"language":2400,"meta":806,"style":806},"language-javascript shiki shiki-themes github-light github-dark","const fastify = require('fastify')({ logger: true })\n\nfastify.get('/', async (request, reply) => {\n  return { hello: 'world' }\n})\n\nconst start = async () => {\n  try {\n    await fastify.listen({ port: 3000 })\n  } catch (err) {\n    fastify.log.error(err)\n    process.exit(1)\n  }\n}\nstart()\n","javascript",[244,2402,2403,2408,2412,2417,2422,2426,2430,2435,2440,2445,2450,2455,2460,2464,2469],{"__ignoreMap":806},[810,2404,2405],{"class":812,"line":813},[810,2406,2407],{},"const fastify = require('fastify')({ logger: true })\n",[810,2409,2410],{"class":812,"line":820},[810,2411,2072],{"emptyLinePlaceholder":1059},[810,2413,2414],{"class":812,"line":838},[810,2415,2416],{},"fastify.get('/', async (request, reply) => {\n",[810,2418,2419],{"class":812,"line":844},[810,2420,2421],{},"  return { hello: 'world' }\n",[810,2423,2424],{"class":812,"line":861},[810,2425,897],{},[810,2427,2428],{"class":812,"line":873},[810,2429,2072],{"emptyLinePlaceholder":1059},[810,2431,2432],{"class":812,"line":888},[810,2433,2434],{},"const start = async () => {\n",[810,2436,2437],{"class":812,"line":894},[810,2438,2439],{},"  try {\n",[810,2441,2442],{"class":812,"line":2115},[810,2443,2444],{},"    await fastify.listen({ port: 3000 })\n",[810,2446,2447],{"class":812,"line":2126},[810,2448,2449],{},"  } catch (err) {\n",[810,2451,2452],{"class":812,"line":2132},[810,2453,2454],{},"    fastify.log.error(err)\n",[810,2456,2457],{"class":812,"line":2138},[810,2458,2459],{},"    process.exit(1)\n",[810,2461,2462],{"class":812,"line":2162},[810,2463,891],{},[810,2465,2466],{"class":812,"line":2167},[810,2467,2468],{},"}\n",[810,2470,2472],{"class":812,"line":2471},15,[810,2473,2474],{},"start()\n",[1036,2476,2477],{},"html .default .shiki span {color: var(--shiki-default);background: var(--shiki-default-bg);font-style: var(--shiki-default-font-style);font-weight: var(--shiki-default-font-weight);text-decoration: var(--shiki-default-text-decoration);}html .shiki span {color: var(--shiki-default);background: var(--shiki-default-bg);font-style: var(--shiki-default-font-style);font-weight: var(--shiki-default-font-weight);text-decoration: var(--shiki-default-text-decoration);}html .dark .shiki span {color: var(--shiki-dark);background: var(--shiki-dark-bg);font-style: var(--shiki-dark-font-style);font-weight: var(--shiki-dark-font-weight);text-decoration: var(--shiki-dark-text-decoration);}html.dark .shiki span {color: var(--shiki-dark);background: var(--shiki-dark-bg);font-style: var(--shiki-dark-font-style);font-weight: var(--shiki-dark-font-weight);text-decoration: var(--shiki-dark-text-decoration);}",{"title":806,"searchDepth":820,"depth":820,"links":2479},[2480],{"id":2394,"depth":820,"text":2395},"2025-02-20","Por que o Fastify se tornou minha escolha padrão para APIs de alta performance.",{"isOnReview":1059},"/blog/fastify-node-backend",{"title":2381,"description":2482},"blog/fastify-node-backend",[2488,2489,75],"Fastify","Backend","ikyoi7_KrlLFwdNygX9ITRkgwiUUFZd8Mnw7q3vPFro",[2492,3356,3636,3786,4031,4313,4381,4460],{"id":2493,"title":2494,"body":2495,"category":75,"date":1056,"description":3349,"extension":1058,"featured":1059,"meta":3350,"navigation":1059,"path":3351,"seo":3352,"stem":3353,"tags":3354,"__hash__":3355},"blog_en/en/blog/de-69-a-97-no-lighthouse.md","From 69 to 97 on web vitals: What could be improved in the PN Imóveis showcase",{"type":9,"value":2496,"toc":3332},[2497,2501,2516,2521,2524,2674,2676,2680,2689,2698,2700,2707,2719,2725,2886,2889,2891,2895,2911,2918,2926,2929,2931,2935,2941,2961,2964,2968,2999,3001,3005,3008,3016,3027,3036,3038,3042,3045,3100,3102,3106,3112,3119,3125,3142,3202,3206,3244,3246,3250,3253,3281,3283,3287,3316,3318,3322,3330],[12,2498,2500],{"id":2499},"from-69-to-97-on-core-web-vitals-what-could-be-improved-in-the-pn-imóveis-showcase","From 69 to 97 on Core Web Vitals: What could be improved in the PN Imóveis showcase",[16,2502,2503,2504,2507,2508,2511,2512,2515],{},"The Performance score for the ",[20,2505,25],{"href":22,"rel":2506},[24]," homepage on Lighthouse went from ",[28,2509,2510],{},"69 to 97",". LCP — the time until the largest visible element renders — dropped from ",[28,2513,2514],{},"6.8s to 2.0s"," on a mid-tier mobile device over a slow 4G network. Accessibility rose from 84 to 96, SEO went from 83 to 100, and WhatsApp link previews started working properly with rich thumbnails and prices.",[16,2517,2518],{},[39,2519],{"alt":2520,"src":42},"Lighthouse Score: From 69 to 97",[16,2522,2523],{},"This post documents the journey: what we uncovered, why each bottleneck hurt, what we refactored, and how we solved each performance issue.",[47,2525,2526,2539],{},[50,2527,2528],{},[53,2529,2530,2533,2536],{},[56,2531,2532],{},"Metric",[56,2534,2535],{},"Before",[56,2537,2538],{},"After (Production)",[66,2540,2541,2554,2567,2580,2592,2606,2620,2634,2647,2659],{},[53,2542,2543,2547,2550],{},[71,2544,2545],{},[28,2546,75],{},[71,2548,2549],{},"69",[71,2551,2552],{},[28,2553,83],{},[53,2555,2556,2561,2563],{},[71,2557,2558],{},[28,2559,2560],{},"Accessibility",[71,2562,93],{},[71,2564,2565],{},[28,2566,98],{},[53,2568,2569,2574,2576],{},[71,2570,2571],{},[28,2572,2573],{},"Best Practices",[71,2575,98],{},[71,2577,2578],{},[28,2579,112],{},[53,2581,2582,2586,2588],{},[71,2583,2584],{},[28,2585,119],{},[71,2587,122],{},[71,2589,2590],{},[28,2591,112],{},[53,2593,2594,2598,2601],{},[71,2595,2596],{},[28,2597,133],{},[71,2599,2600],{},"2.5 s",[71,2602,2603],{},[28,2604,2605],{},"1.2 s",[53,2607,2608,2612,2615],{},[71,2609,2610],{},[28,2611,148],{},[71,2613,2614],{},"6.8 s",[71,2616,2617],{},[28,2618,2619],{},"2.0 s",[53,2621,2622,2626,2629],{},[71,2623,2624],{},[28,2625,163],{},[71,2627,2628],{},"4.1 s",[71,2630,2631],{},[28,2632,2633],{},"2.9 s",[53,2635,2636,2640,2643],{},[71,2637,2638],{},[28,2639,178],{},[71,2641,2642],{},"0.004",[71,2644,2645],{},[28,2646,186],{},[53,2648,2649,2653,2655],{},[71,2650,2651],{},[28,2652,193],{},[71,2654,196],{},[71,2656,2657],{},[28,2658,201],{},[53,2660,2661,2666,2669],{},[71,2662,2663],{},[28,2664,2665],{},"Transferred Bytes (Homepage)",[71,2667,2668],{},"2.8 MB",[71,2670,2671],{},[28,2672,2673],{},"~0.6 MB",[218,2675],{},[221,2677,2679],{"id":2678},"the-application","The Application",[16,2681,2682,2683,2685,2686,2688],{},"PN Imóveis is the public real estate portal for an agency based in Pernambuco, Brazil. It includes a homepage with featured properties, a search interface with filters, individual property pages, and an administrative dashboard for brokers. Stack: ",[28,2684,230],{}," (Vue) with ",[28,2687,234],{},", API running in the same Nitro process, self-hosted PostgreSQL via Supabase on a VPS managed by Coolify. Property photos are hosted on Supabase Storage behind a Cloudflare CDN.",[16,2690,2691,2692,2694,2695,2697],{},"An early architectural decision played a major role: the entire application was built as a Single Page Application (",[28,2693,241],{},") with ",[244,2696,246],{},". The server served a blank HTML shell, and the client browser rendered everything via JavaScript. While suitable for an admin panel, it proved problematic for a public showcase.",[218,2699],{},[221,2701,2703,2704,2706],{"id":2702},"the-audit-report-was-generic-curl-revealed-the-truth","The Audit Report Was Generic. ",[244,2705,256],{}," Revealed the Truth.",[16,2708,2709,2710,265,2713,2715,2716,402],{},"We started with a Lighthouse: Performance 69, LCP 6.8s, and generic audit recommendations like ",[262,2711,2712],{},"\"Use efficient cache lifetimes (1,352 KiB)\"",[262,2714,268],{},", and ",[262,2717,2718],{},"\"Improve image delivery (1,190 KiB)\"",[16,2720,2721,2722,2724],{},"To get actionable details, we fetched the raw page using ",[244,2723,256],{}," and inspected the response payload.",[47,2726,2727,2742],{},[50,2728,2729],{},[53,2730,2731,2734,2737,2739],{},[56,2732,2733],{},"Resource",[56,2735,2736],{},"Size",[56,2738,297],{},[56,2740,2741],{},"Finding",[66,2743,2744,2761,2779,2798,2816,2836,2853,2869],{},[53,2745,2746,2751,2755,2758],{},[71,2747,2748],{},[28,2749,2750],{},"Homepage HTML",[71,2752,2753],{},[28,2754,314],{},[71,2756,2757],{},"None",[71,2759,2760],{},"An SPA shell should be ~10 KB. It contained 462 KB of embedded inline CSS.",[53,2762,2763,2768,2773,2776],{},[71,2764,2765],{},[28,2766,2767],{},"Entry JS Bundle",[71,2769,2770,2772],{},[28,2771,332],{}," (146 KB gzipped)",[71,2774,2775],{},"1 year",[71,2777,2778],{},"Globally registered all ~90 PrimeVue components.",[53,2780,2781,2786,2791,2793],{},[71,2782,2783],{},[28,2784,2785],{},"Hero Image",[71,2787,2788,2790],{},[28,2789,351],{}," (1920px JPEG)",[71,2792,2757],{},[71,2794,2795,2796,402],{},"It was the LCP element, hidden inside CSS ",[244,2797,360],{},[53,2799,2800,2805,2810,2813],{},[71,2801,2802],{},[28,2803,2804],{},"Property Card Image",[71,2806,2807,2809],{},[28,2808,373],{}," (2000px WebP)",[71,2811,2812],{},"4 hours",[71,2814,2815],{},"Rendered in a small 400px card container.",[53,2817,2818,2823,2828,2830],{},[71,2819,2820],{},[28,2821,2822],{},"Fonts & Logos",[71,2824,2825,2827],{},[28,2826,392],{}," each",[71,2829,2757],{},[71,2831,2832,2833,2835],{},"Served without any ",[244,2834,401],{}," header.",[53,2837,2838,2844,2848,2850],{},[71,2839,2840],{},[28,2841,2842],{},[244,2843,411],{},[71,2845,2846],{},[28,2847,416],{},[71,2849,419],{},[71,2851,2852],{},"Returned the full SPA HTML page to web crawlers.",[53,2854,2855,2860,2864,2866],{},[71,2856,2857],{},[28,2858,2859],{},"Icon Sprite",[71,2861,2862,435],{},[28,2863,434],{},[71,2865,419],{},[71,2867,2868],{},"Inlined into the DOM on every public page inside the JS bundle.",[53,2870,2871,2877,2881,2883],{},[71,2872,2873],{},[28,2874,2875],{},[244,2876,449],{},[71,2878,2879],{},[28,2880,454],{},[71,2882,419],{},[71,2884,2885],{},"Prefetched every build chunk, competing for bandwidth with critical assets.",[16,2887,2888],{},"Each item translated a generic Lighthouse warning into a clear engineering target. The first row alone explained a major portion of the initial load delay.",[218,2890],{},[221,2892,2894],{"id":2893},"issue-1-half-a-megabyte-of-css-injected-into-empty-html","Issue 1: Half a Megabyte of CSS Injected into Empty HTML",[16,2896,2897,2898,2900,2901,2904,2905,2907,2908,2910],{},"The PrimeVue Nuxt module had a default setting that pre-injected component theme CSS into the ",[244,2899,474],{},". Without an explicit list of used components, it injected styles for ",[28,2902,2903],{},"every component"," — autocomplete, cascadeselect, treetable, terminal, dock, org chart, etc. This resulted in 184 ",[244,2906,482],{}," blocks totaling 462 KB on ",[28,2909,1763],{}," HTML response for an SPA shell that rendered no initial DOM content.",[16,2912,2913,2914,2917],{},"This payload was uncacheable (being raw HTML) and had to be parsed before any rendering could take place. Lighthouse categorized this as ",[262,2915,2916],{},"\"Unused JavaScript\""," because the same CSS strings existed inside the JS bundle.",[16,2919,2920,2921,2923,2924,402],{},"We disabled global style injection (",[244,2922,504],{},") and registered only the components required by the app. The initial HTML payload dropped from 501 KB to ",[28,2925,508],{},[16,2927,2928],{},"Later, when migrating the public pages to SSR, style injection was re-enabled selectively only for the ~12 components rendered during server side generation.",[218,2930],{},[221,2932,2934],{"id":2933},"issue-2-the-lcp-image-was-hidden-behind-3-network-roundtrips","Issue 2: The LCP Image Was Hidden Behind 3 Network Roundtrips",[16,2936,2937,2938,2940],{},"The largest contentful element on the homepage is the hero background photo. It was defined via CSS ",[244,2939,360],{},". To request this asset, the browser had to:",[529,2942,2943,2946,2949,2952,2955,2958],{},[532,2944,2945],{},"Download and parse the HTML shell.",[532,2947,2948],{},"Fetch and execute the JavaScript application bundle.",[532,2950,2951],{},"Wait for the blocking API fetch call on initial page setup.",[532,2953,2954],{},"Mount the DOM tree.",[532,2956,2957],{},"Parse the CSS rules to discover the image URL.",[532,2959,2960],{},"Trigger the 359 KB JPEG download.",[16,2962,2963],{},"On slow 4G networks with high RTT (~150ms), the hero image only began downloading after ~4 seconds.",[558,2965,2967],{"id":2966},"fixes-applied","Fixes Applied:",[563,2969,2970,2984,2990],{},[532,2971,2972,2980,2981,2983],{},[28,2973,2974,2975,2977,2978,576],{},"Native ",[244,2976,571],{}," tag with ",[244,2979,575],{}," Enabled the browser's preload scanner to discover and fetch the image immediately upon receiving HTML. Added responsive ",[244,2982,584],{}," (800/1200/1600 px), reducing mobile image downloads to 34 KB.",[532,2985,2986,2989],{},[28,2987,2988],{},"WebP Conversion:"," Optimized the 1600px asset from 359 KB down to 98 KB.",[532,2991,2992,2995,2996,2998],{},[28,2993,2994],{},"Non-blocking API Fetching:"," Made the hero section render immediately with static elements, while fetching property listings asynchronously via ",[244,2997,600],{}," with skeleton loading states.",[218,3000],{},[221,3002,3004],{"id":3003},"issue-3-oversized-images-in-small-cards-with-short-caching","Issue 3: Oversized Images in Small Cards with Short Caching",[16,3006,3007],{},"During upload, images were processed to 2000px WebP files — suitable for full-screen property galleries, but excessive for 400px property cards on search pages. This sent ~500 KB per card across 6 featured listings on the home page.",[16,3009,3010,3011,3013,3014,799],{},"Additionally, Supabase Storage served assets with ",[244,3012,616],{}," (4 hours). We updated headers and Cloudflare CDN caching rules to 1 year (",[244,3015,620],{},[623,3017,3018],{},[16,3019,3020,3023,3024,3026],{},[28,3021,3022],{},"What if an image needs to be updated?"," We follow standard ",[28,3025,633],{},": whenever an image is updated or replaced, its URL path or filename is updated with a new version or hash (e.g., adding a version suffix or content hash). Because the URL changes, browsers recognize it as a brand-new asset and fetch it immediately, safely bypassing the 1-year cache of the old URL.",[16,3028,3029,3030,3032,3033,3035],{},"Explicit ",[244,3031,640],{}," and ",[244,3034,643],{}," attributes were added to all property image components via API DTO payload data, eliminating layout shifts.",[218,3037],{},[221,3039,3041],{"id":3040},"issue-4-eliminating-micro-gargoyles","Issue 4: Eliminating Micro-Gargoyles",[16,3043,3044],{},"Small inefficiency sources accumulated into substantial overhead:",[563,3046,3047,3053,3061,3073,3085],{},[532,3048,3049,3052],{},[28,3050,3051],{},"Uncached Static Assets:"," Configured Nitro route rules to serve static fonts with 1-year immutable caching and static images/icons with 30-day cache durations.",[532,3054,3055,3058,3059,402],{},[28,3056,3057],{},"158 Automatic Prefetches:"," Disabled automatic route chunk prefetching via build manifest hooks. Replaced it with viewport-intersection prefetching via ",[244,3060,683],{},[532,3062,3063,3066,3067,3069,3070,402],{},[28,3064,3065],{},"238 KB SVG Icon Sprite:"," Replaced a large inline Vue icon component with an external SVG sprite hosted in ",[244,3068,670],{},", compressed with SVGO, and referenced using ",[244,3071,3072],{},"\u003Cuse href=\"/icons/sprite.svg#icon-name\">",[532,3074,3075,3078,3079,3081,3082,3084],{},[28,3076,3077],{},"Icon Font Blocking:"," Self-hosted the ",[244,3080,712],{}," font with ",[244,3083,716],{}," and preloading.",[532,3086,3087,3090,3091,3093,3094,3096,3097,3099],{},[28,3088,3089],{},"Accessibility & SEO:"," Added ",[244,3092,728],{}," attributes to icon-only buttons, fixed header hierarchy (",[244,3095,221],{}," to ",[244,3098,735],{},"), added canonical links and meta descriptions, and adjusted brand green text contrast to pass WCAG AA standards (4.5:1 minimum).",[218,3101],{},[221,3103,3105],{"id":3104},"the-pivot-selective-ssr-for-public-pages","The Pivot: Selective SSR for Public Pages",[16,3107,3108,3109,402],{},"With core optimizations in place, we switched the public showcase to ",[28,3110,3111],{},"Server-Side Rendering (SSR)",[16,3113,3114,3115,3118],{},"The primary driver was ",[28,3116,3117],{},"social link previews",". When brokers shared property links on WhatsApp or social networks, the previews rendered blank thumbnails without pricing or titles because preview bots do not execute client-side JavaScript.",[16,3120,3121,3122,3124],{},"Using Nuxt ",[244,3123,778],{},", we configured a hybrid rendering model:",[563,3126,3127,3133],{},[532,3128,3129,3132],{},[28,3130,3131],{},"SSR enabled by default:"," For homepage, search, landing pages, and individual property pages.",[532,3134,3135,3139,3140,799],{},[28,3136,3137,794],{},[244,3138,246],{}," For the administrative panel, authentication screens, and user favorites (which rely entirely on client ",[244,3141,798],{},[801,3143,3144],{"className":803,"code":804,"language":805,"meta":806,"style":806},[244,3145,3146,3150,3160,3164,3174,3184,3194,3198],{"__ignoreMap":806},[810,3147,3148],{"class":812,"line":813},[810,3149,817],{"class":816},[810,3151,3152,3154,3156,3158],{"class":812,"line":820},[810,3153,824],{"class":823},[810,3155,827],{"class":823},[810,3157,831],{"class":830},[810,3159,835],{"class":834},[810,3161,3162],{"class":812,"line":838},[810,3163,841],{"class":834},[810,3165,3166,3168,3170,3172],{"class":812,"line":844},[810,3167,848],{"class":847},[810,3169,851],{"class":834},[810,3171,855],{"class":854},[810,3173,858],{"class":834},[810,3175,3176,3178,3180,3182],{"class":812,"line":861},[810,3177,864],{"class":847},[810,3179,851],{"class":834},[810,3181,855],{"class":854},[810,3183,858],{"class":834},[810,3185,3186,3188,3190,3192],{"class":812,"line":873},[810,3187,876],{"class":847},[810,3189,879],{"class":834},[810,3191,882],{"class":854},[810,3193,885],{"class":834},[810,3195,3196],{"class":812,"line":888},[810,3197,891],{"class":834},[810,3199,3200],{"class":812,"line":894},[810,3201,897],{"class":834},[558,3203,3205],{"id":3204},"ssr-hydration-edge-cases-fixed","SSR Hydration Edge Cases Fixed:",[529,3207,3208,3219,3231],{},[532,3209,3210,3212,3213,3215,3216,3218],{},[28,3211,908],{}," Ensured ",[244,3214,798],{}," reads and non-deterministic operations (such as array shuffling) run inside ",[244,3217,915],{}," rather than top-level setup.",[532,3220,3221,3224,3225,3227,3228,3230],{},[28,3222,3223],{},"Server-side Reactivity:"," Replaced ",[244,3226,925],{}," handlers populating data arrays with reactive ",[244,3229,929],{}," properties.",[532,3232,3233,3236,3237,3032,3239,3241,3242,402],{},[28,3234,3235],{},"PrimeVue Context Services:"," Preserved ",[244,3238,938],{},[244,3240,941],{}," instances in the root layout to maintain global service registration for ",[244,3243,945],{},[218,3245],{},[221,3247,3249],{"id":3248},"delivery-optimization-compression-chunk-bundling","Delivery Optimization: Compression & Chunk Bundling",[16,3251,3252],{},"To ensure optimal transport performance, we tuned Nitro and Rollup:",[563,3254,3255,3263,3269],{},[532,3256,3257,3260,3261,402],{},[28,3258,3259],{},"Brotli Server Compression:"," Implemented a Nitro server plugin to pre-compress SSR HTML responses using Brotli/Gzip before reaching the reverse proxy, shrinking raw HTML payloads from 196 KB to ",[28,3262,965],{},[532,3264,3265,3268],{},[28,3266,3267],{},"Rollup Chunk Consolidation:"," Merged fragmented JS build files (74 of which were smaller than 10 KB). Reduced total build chunks from 101 to 71, and homepage JS requests from 44 down to 19.",[532,3270,3271,3274,3275,3277,3278,3280],{},[28,3272,3273],{},"Hero Image Preloading:"," Injected HTTP ",[244,3276,980],{}," headers with ",[244,3279,984],{}," so the browser prioritizes the hero image at the top of the network waterfall.",[218,3282],{},[221,3284,3286],{"id":3285},"key-takeaways","Key Takeaways",[529,3288,3289,3298,3304,3310],{},[532,3290,3291,3294,3295,3297],{},[28,3292,3293],{},"Inspect Raw Payloads:"," Automated summaries highlight symptoms, but raw ",[244,3296,256],{}," inspection exposes root causes like hidden inline styles.",[532,3299,3300,3303],{},[28,3301,3302],{},"SSR Drives Conversion:"," Rich WhatsApp previews and search engine indexing are core business requirements for public portals.",[532,3305,3306,3309],{},[28,3307,3308],{},"Hydrate Mindfully:"," Client-only state (storage, media queries, randomness) must initialize neutrally during SSR and update post-mount.",[532,3311,3312,3315],{},[28,3313,3314],{},"Target Critical CSS:"," Prune UI framework CSS imports so only active components load in critical rendering paths.",[218,3317],{},[221,3319,3321],{"id":3320},"summary","Summary",[16,3323,3324,3325,3327,3328,402],{},"We transformed an SPA serving 500 KB of unused inline CSS into a selective SSR application with 24 KB Brotli-compressed HTML, preloaded hero image, and full social preview support — raising Lighthouse Performance from ",[28,3326,2510],{}," and cutting LCP from ",[28,3329,2514],{},[1036,3331,1038],{},{"title":806,"searchDepth":820,"depth":820,"links":3333},[3334,3335,3337,3338,3341,3342,3343,3346,3347,3348],{"id":2678,"depth":820,"text":2679},{"id":2702,"depth":820,"text":3336},"The Audit Report Was Generic. curl Revealed the Truth.",{"id":2893,"depth":820,"text":2894},{"id":2933,"depth":820,"text":2934,"children":3339},[3340],{"id":2966,"depth":838,"text":2967},{"id":3003,"depth":820,"text":3004},{"id":3040,"depth":820,"text":3041},{"id":3104,"depth":820,"text":3105,"children":3344},[3345],{"id":3204,"depth":838,"text":3205},{"id":3248,"depth":820,"text":3249},{"id":3285,"depth":820,"text":3286},{"id":3320,"depth":820,"text":3321},"How I fixed LCP down from 6.8s to 2.0s and boosted Nuxt 4 performance from 69 to 97 by fixing inline CSS, selective SSR, images, and caching.",{},"/en/blog/de-69-a-97-no-lighthouse",{"title":2494,"description":3349},"en/blog/de-69-a-97-no-lighthouse",[1065,1066,1067,1068,1069],"2RAX4F4bAIb6qGUZWRa5vjSrUdko-GWif7jChVVBxRc",{"id":3357,"title":3358,"body":3359,"category":1392,"date":1393,"description":3628,"extension":1058,"featured":1059,"meta":3629,"navigation":1059,"path":3630,"seo":3631,"stem":3632,"tags":3633,"__hash__":3635},"blog_en/en/blog/my-bullet-journal.md","My Bullet Journal: From Physical Notebooks to My Own App",{"type":9,"value":3360,"toc":3619},[3361,3365,3369,3377,3383,3387,3398,3407,3411,3437,3441,3461,3487,3490,3492,3495,3498,3501,3505,3511,3525,3533,3539,3554,3558,3566,3573,3580,3583,3589,3597,3608,3617],[12,3362,3364],{"id":3363},"my-bullet-journal-organization-and-productivity-with-a-personal-touch","My Bullet Journal: Organization and Productivity with a Personal Touch",[221,3366,3368],{"id":3367},"introduction","Introduction",[16,3370,3371,3372],{},"A well-crafted introduction is essential to engage readers, but to be completely honest: I'm still sharpening that skill. 😅\n",[1088,3373,3374],{},[1091,3375,3376],{},"Just kidding - I don't like fluff either",[16,3378,3379,3380,402],{},"So let's get straight to the point: if you already know what a Bullet Journal is, feel free to skip directly to ",[28,3381,3382],{},"My Bullet Journal Method",[221,3384,3386],{"id":3385},"what-is-a-bullet-journal","What is a Bullet Journal?",[16,3388,3389,3390,3393,3394,3397],{},"The Bullet Journal is a personal organization system created by designer ",[20,3391,1111],{"href":1109,"rel":3392},[24],". It brilliantly combines (🚨 ",[1114,3395,3396],{},"OPINION ALERT!",") elements of a diary, a traditional planner, and a to-do list.",[16,3399,3400,3401,3404,3405,799],{},"The method's biggest advantage is that it was designed to be ",[28,3402,3403],{},"100% flexible and adaptable"," to the user's needs. Instead of forcing yourself into rigid boxes of pre-printed planners, you build a system that works for your brain. It uses specific symbols to categorize tasks, events, and notes, enabling quick visual reference and tracking (",[262,3406,1127],{},[558,3408,3410],{"id":3409},"why-does-the-bullet-journal-work","Why Does the Bullet Journal Work?",[563,3412,3413,3419,3425,3431],{},[532,3414,3415,3418],{},[28,3416,3417],{},"Total Flexibility:"," Highly customizable. You only build what you actually use.",[532,3420,3421,3424],{},[28,3422,3423],{},"Centralization:"," Keeps all your tasks, events, and thoughts in a single location, eliminating scattered sticky notes and notebooks.",[532,3426,3427,3430],{},[28,3428,3429],{},"Creative Expression:"," Allows you to express creativity through custom layouts, drawings, and visual trackers.",[532,3432,3433,3436],{},[28,3434,3435],{},"Intentional Mindfulness:"," The simple act of writing by hand (and reviewing tasks) heightens awareness of where your time goes, fostering focus and presence.",[221,3438,3440],{"id":3439},"my-method-of-bullet-journaling","\"My Method\" of Bullet Journaling",[1162,3442,1171,3444,1171,3448],{"className":3443},[1165,1166,1167,1168,1169,1170],[39,3445],{"src":1174,"alt":3446,"className":3447},"My Bullet Journal Rules",[1177,1178,1179,1180,1181,1182,1183,1184],[1162,3449,3451,3452,3454],{"className":3450},[1188],"The idea was to adapt the traditional Bullet Journal method to fit my daily routine and preferences better. I even created specific rules that made sense for me.",[1191,3453],{},[1088,3455,3456,3457,3460],{},"Tip:* The best part of Bullet Journaling is that there are no global fixed rules, but it is essential to create ",[1197,3458,3459],{},"your own"," rules and follow them consistently.",[563,3462,3463,3469,3475,3481],{},[532,3464,3465,3468],{},[28,3466,3467],{},"15-Day Cycles:"," Instead of traditional monthly planning, I split my time into 15-day sprints (just like agile development sprints).",[532,3470,3471,3474],{},[28,3472,3473],{},"Metrics and Dashboards:"," In each cycle, I create custom charts and trackers to visually monitor progress and track goals — such as my ongoing effort to consistently hit the gym! 💪",[532,3476,3477,3480],{},[28,3478,3479],{},"Daily Review:"," Never start a day without reviewing what was planned and what was accomplished the previous day. This habit keeps focus and discipline sharp while encouraging reflection on priorities.",[532,3482,3483,3486],{},[28,3484,3485],{},"Basic To-do List:"," Simple, yet with strict rules: strike out completed tasks, migrate uncompleted ones to the next period, and avoid endless lists of tasks that never get done.",[16,3488,3489],{},"Below are some photos of my physical Bullet Journal back when I did everything on paper. Notice how some pages are complete while others remain unfinished:",[1239,3491],{":slides":1241,":timer":1242},[16,3493,3494],{},"After laying out these rules, I realized something: was I building a system \"identical\" to what I use at work every day? 😂",[16,3496,3497],{},"Sprints, Scrum, retrospectives? Well, maybe yes, maybe no. 🤔",[16,3499,3500],{},"In the end, it was a tailored adaptation of the Bullet Journal with an agile, results-oriented focus. It kept me focused and disciplined, with a creative spark that motivated me to keep going.",[221,3502,3504],{"id":3503},"the-problem-spoiler-pure-laziness","The Problem: Spoiler... Pure Laziness!",[16,3506,3507,3508,402],{},"Everything seemed perfect, right? Pretty, organized, rewarding. But there was a major catch: ",[28,3509,3510],{},"maintenance",[563,3512,3513,3519],{},[532,3514,3515,3518],{},[28,3516,3517],{},"Setup Friction:"," Setup time was huge. Designing custom layouts, drawing tables, aligning lines with a ruler... Yes, actual rulers, colored pens — it's time-consuming. When you have a busy engineering workload, spending hours drawing the next week's planner becomes unsustainable.",[532,3520,3521,3524],{},[28,3522,3523],{},"Loss of Consistency:"," Because of this friction, I started each period enthusiastically but abandoned tracking after a few weeks simply for lack of time to \"draw the system\".",[623,3526,3527],{},[16,3528,3529,3532],{},[28,3530,3531],{},"The Reality:"," I really enjoyed the manual process, but the \"duty\" to redraw metrics every 15 days eventually killed my motivation. The satisfaction of a filled tracker no longer outweighed the effort to prepare it.",[16,3534,3535,3536],{},"This led to the inevitable software engineer reflection: ",[28,3537,3538],{},"\"Why not automate the tedious part and keep only the useful part?\"",[16,3540,3541],{},[1088,3542,3543,3546,3547,3550,3551,3553],{},[1091,3544,3545],{},"\"Wait, couldn't you just buy a ready-made planner or download any generic to-do app?\""," Sure, I could. But I wanted ",[1197,3548,3549],{},"my"," methodology, with ",[1197,3552,3549],{}," rules, without conforming to third-party workflows.",[221,3555,3557],{"id":3556},"the-solution-my-journal-app","The Solution: My Journal APP",[16,3559,3560,3561,1320],{},"Born out of that exact friction and laziness was ",[28,3562,3563],{},[20,3564,1319],{"href":1317,"rel":3565},[24],[16,3567,3568,3569,3572],{},"It is a full web application built by me — currently under active development ",[262,3570,3571],{},"(as of March 18, 2026)"," — with the sole purpose of organizing my personal and professional life efficiently without paper friction or weekly layout setup.",[16,3574,3575,3576,3579],{},"It is ",[28,3577,3578],{},"100% inspired"," by the paper journal method. It enforces the same rules (such as habit tracking and cycle reviews) in a digital format. All the table drawing, chart calculation, and next-week prep have been automated. I just open it, log what I need, and close it.",[16,3581,3582],{},"The system is live, hosted directly in my home server cluster. It's a PWA (Progressive Web App) running locally on the client using IndexedDB for local-first storage.",[16,3584,3585,3586],{},"The best part? I decided to open access. ",[28,3587,3588],{},"It's completely free to use if you like:",[16,3590,1349,3591,1353,3594],{},[28,3592,3593],{},"Access here:",[20,3595,1357],{"href":1317,"rel":3596},[24],[16,3598,3599,3600,3604,3605],{},"To help new users, I also wrote a ",[20,3601,3603],{"href":1363,"rel":3602},[24],"User Guide"," explaining the logic of the system and how I structure my routine inside it. Feel free to explore and send me feedback or suggestions. ",[262,3606,3607],{},"Constructive feedback is always welcome! ❤️",[16,3609,3610,3611,3613,3614,3616],{},"Remember: it is an \"opinionated\" app, originally built to solve ",[262,3612,3549],{}," own problem and follow ",[262,3615,3549],{}," mindset. It might not fit your workflow perfectly... or it might be the exact tool you've been searching for! Who knows?",[1239,3618],{":slides":1382,":timer":1242},{"title":806,"searchDepth":820,"depth":820,"links":3620},[3621,3622,3625,3626,3627],{"id":3367,"depth":820,"text":3368},{"id":3385,"depth":820,"text":3386,"children":3623},[3624],{"id":3409,"depth":838,"text":3410},{"id":3439,"depth":820,"text":3440},{"id":3503,"depth":820,"text":3504},{"id":3556,"depth":820,"text":3557},"Discover how I adapted the Bullet Journal method to organize my routine and why I decided to transform that experience into a free web application.",{},"/en/blog/my-bullet-journal",{"title":3358,"description":3628},"en/blog/my-bullet-journal",[1392,1401,3634,1403],"Software Engineering","mIyA54yTZ1L7yadxLlf_dtMBqaO-45EU40wJ3NXX7Gs",{"id":3637,"title":3638,"body":3639,"category":1580,"date":1573,"description":3778,"extension":1058,"featured":1059,"meta":3779,"navigation":1059,"path":3780,"seo":3781,"stem":3782,"tags":3783,"__hash__":3785},"blog_en/en/blog/volta-a-universidade-engenharia-ia.md","Back to University: Applied AI Engineering",{"type":9,"value":3640,"toc":3774},[3641,3644,3650,3667,3671,3674,3681,3684,3691,3695,3698,3753,3765,3771],[16,3642,3643],{},"For some of you this might be news, but for me it's been over a month already. Am I late to share it?",[16,3645,3646,3647,1419],{},"The big news is: ",[28,3648,3649],{},"I am back at university!",[1162,3651,1171,3653,3657],{"className":3652},[1165,1166,1423,1424,1425,1169,1170],[39,3654],{"src":1428,"alt":3655,"className":3656},"Rafa Pedrosa - Applied AI Postgraduate Ambassador at UNIPDS",[1431,1432,1433,1434,1435,1436,1437,1183,1438,1439,1440,1441,1442],[810,3658,3659,3660],{},"\nFor over a year, I have been asking myself deep career questions: How do I elevate my professional baseline? What is the best way to showcase my technical capabilities and engineering maturity to the market?\n",[16,3661,3662,3663,3666],{},"As I shared in ",[20,3664,3665],{"href":1451},"this post",", sharing more on networks like LinkedIn to showcase real-world projects and daily workflows is great for showing \"who I am\". But what about technical foundations? How do I validate and deepen the foundation built over the years?\n",[221,3668,3670],{"id":3669},"the-new-challenge-postgraduate-degree","The New Challenge: Postgraduate Degree",[16,3672,3673],{},"I decided to pursue a new academic degree to consolidate over 13 years of practical experience in technology.",[16,3675,3676,3677,3680],{},"Currently, I am pursuing a postgraduate degree in ",[28,3678,3679],{},"Applied AI Engineering"," at UNIPDS. I am dedicating myself fully, not just for a title, but with the genuine goal of contributing to an active learning community that goes far beyond a traditional course.",[16,3682,3683],{},"Over the coming year, my focus will be on building deep practical skills in Artificial Intelligence. And best of all: I am actively participating as a Student Ambassador, helping peers, answering technical questions, and fostering rich knowledge sharing among students.",[623,3685,3686],{},[16,3687,3688,3690],{},[28,3689,1480],{}," Returning to formal academic education doesn't replace real-world experience, but it provides a structured foundation for mastering complex domain areas like Artificial Intelligence.",[221,3692,3694],{"id":3693},"academic-degree-vs-online-courses","Academic Degree vs. Online Courses?",[16,3696,3697],{},"This is one of those topics that always sparks debate. What holds more value: a formal academic degree or five certificates from quick online courses?",[47,3699,3700,3713],{},[50,3701,3702],{},[53,3703,3704,3707,3710],{},[56,3705,3706],{"align":1497},"Criterion",[56,3708,3709],{"align":1497},"Academic Degree (Postgraduate)",[56,3711,3712],{"align":1497},"Short Online Courses",[66,3714,3715,3728,3740],{},[53,3716,3717,3722,3725],{},[71,3718,3719],{"align":1497},[28,3720,3721],{},"Depth",[71,3723,3724],{"align":1497},"Structured, deep theoretical and practical immersion",[71,3726,3727],{"align":1497},"Direct focus on fast skills and specific tools",[53,3729,3730,3734,3737],{},[71,3731,3732],{"align":1497},[28,3733,1526],{},[71,3735,3736],{"align":1497},"Strong (continuous interaction with professors, community, and peers)",[71,3738,3739],{"align":1497},"Generally limited or asynchronous",[53,3741,3742,3747,3750],{},[71,3743,3744],{"align":1497},[28,3745,3746],{},"Validation",[71,3748,3749],{"align":1497},"Formal recognition endorsed by educational institutions",[71,3751,3752],{"align":1497},"Fast validation, ideal for short-term trends",[623,3754,3755],{},[16,3756,3757,3760,3761,3764],{},[28,3758,3759],{},"⚠️ Reflection:"," In my view, ",[28,3762,3763],{},"both are valid and essential",". However, for those who believe software engineering no longer requires academic updating, or that degrees carry no weight... the question remains: is that really true?",[16,3766,3767,3768,3770],{},"Who are your technical role models today? Do they rely solely on quick tutorials, or do they also possess a solid formal grounding? I believe combining both — true ",[262,3769,1563],{}," — is the real differentiator for long-lasting engineering careers.",[16,3772,3773],{},"Food for thought 🤔",{"title":806,"searchDepth":820,"depth":820,"links":3775},[3776,3777],{"id":3669,"depth":820,"text":3670},{"id":3693,"depth":820,"text":3694},"Why I decided to return to university after 13 years of tech experience to study Applied Artificial Intelligence Engineering.",{},"/en/blog/volta-a-universidade-engenharia-ia",{"title":3638,"description":3778},"en/blog/volta-a-universidade-engenharia-ia",[1580,3784,1582,1563],"Career","GonZJ7HFyfx93S6couKXYOqvNmQHMxH4f3hUYn11vCE",{"id":3787,"title":3788,"body":3789,"category":1860,"date":1861,"description":4024,"extension":1058,"featured":1059,"meta":4025,"navigation":1059,"path":4026,"seo":4027,"stem":4028,"tags":4029,"__hash__":4030},"blog_en/en/blog/nuxt4-alicerce.md","Welcome to My First Blog Post! And My Site is Finally Live!",{"type":9,"value":3790,"toc":4016},[3791,3795,3798,3805,3809,3816,3822,3825,3829,3834,3844,3852,3856,3859,3887,3949,3953,3960,3963,3984,3992,3996,4002,4011,4014],[12,3792,3794],{"id":3793},"building-my-portfolio-with-nuxt-4-and-nuxt-content","Building My Portfolio with Nuxt 4 and Nuxt Content",[16,3796,3797],{},"It had been a very long time since I last maintained a personal website. If memory serves me right, the last time was years ago — perhaps hosted on good old GitHub Pages? It was something very simple, serving only to display my CV and nothing more.",[16,3799,3800,3801,3804],{},"Recently, I caught myself thinking: ",[262,3802,3803],{},"\"Why not build a new one? But this time, something serious?\""," I decided to return to formal studies (a postgraduate degree), expand my presence in the tech community, stay updated, and put my work out into the world. The first logical step was having my own space on the internet.",[221,3806,3808],{"id":3807},"the-challenge-simplicity-vs-functionality","The Challenge: Simplicity vs. Functionality",[16,3810,3811,3812,3815],{},"The premise was clear: the website needed to be ",[28,3813,3814],{},"simple to maintain",", but I wanted it to function like a full CMS (Content Management System).",[16,3817,3818,3819,402],{},"Building one from scratch with a custom database, API, and admin dashboard? Sure, AI all the way! But five minutes later, reality hit: ",[262,3820,3821],{},"\"Wait... what a chore. It has to stay simple!\"",[16,3823,3824],{},"The classic alternative would be WordPress. However, I wanted to stay within the modern web ecosystem and tech stack I use daily as a Software Engineer. I started brainstorming with AI tools (specifically Gemini) to explore architecture ideas, but no initial suggestion clicked. I wanted a live CV homepage, a project portfolio showcase, and a technical blog.",[221,3826,3828],{"id":3827},"discovering-nuxt-content","Discovering Nuxt Content",[16,3830,3831,3832],{},"Until I vaguely remembered a Nuxt extension I had seen in the past. I asked the AI about it, and the answer was immediate: ",[28,3833,1635],{},[16,3835,3836,3837,3840,3841,3843],{},"Reading through the documentation, I realized it did ",[28,3838,3839],{},"exactly"," what I wanted: it transformed Markdown (",[244,3842,1645],{},") files and YAML Frontmatter into highly optimized HTML. It was the perfect marriage between the Developer Experience (DX) I wanted and the publishing simplicity I needed.",[623,3845,3846],{},[16,3847,3848,3851],{},[28,3849,3850],{},"💡 The Secret Sauce:"," With Nuxt Content v3, the site behaves like a complete static CMS. Zero database, zero maintenance headache. Content is processed at build time (SSG), guaranteeing a 100 score on Lighthouse performance.",[558,3853,3855],{"id":3854},"the-technical-stack-i-chose","The Technical Stack I Chose",[16,3857,3858],{},"To bring this idea to life, the foundation was built with the best tools in the Vue ecosystem:",[563,3860,3861,3869,3877,3882],{},[532,3862,3863,3865,3866,3868],{},[28,3864,1668],{}," Taking advantage of the new folder structure (",[244,3867,1672],{},") that logically separates application code from configuration.",[532,3870,3871,3873,3874,3876],{},[28,3872,1678],{}," The engine that parses ",[244,3875,1645],{}," files and provides type-safe content directly to Vue components.",[532,3878,3879,3881],{},[28,3880,1687],{}," Ensuring markdown-rendered HTML looks clean, responsive, and dark-mode friendly effortlessly.",[532,3883,3884,3886],{},[28,3885,1693],{}," High-quality syntax highlighting for code blocks.",[801,3888,3890],{"className":803,"code":3889,"language":805,"meta":806,"style":806},"// Example of how clean code blocks render in posts!\nconst techStack: string[] = ['Nuxt 4', 'Nuxt Content', 'Tailwind', 'Shiki'];\nconst isHappy = techStack.every(tech => tech.isAwesome);\n",[244,3891,3892,3897,3929],{"__ignoreMap":806},[810,3893,3894],{"class":812,"line":813},[810,3895,3896],{"class":816},"// Example of how clean code blocks render in posts!\n",[810,3898,3899,3901,3903,3905,3907,3909,3911,3913,3915,3917,3919,3921,3923,3925,3927],{"class":812,"line":820},[810,3900,1709],{"class":823},[810,3902,1712],{"class":854},[810,3904,576],{"class":823},[810,3906,1717],{"class":854},[810,3908,1720],{"class":834},[810,3910,1723],{"class":823},[810,3912,1726],{"class":834},[810,3914,1729],{"class":847},[810,3916,265],{"class":834},[810,3918,1734],{"class":847},[810,3920,265],{"class":834},[810,3922,1739],{"class":847},[810,3924,265],{"class":834},[810,3926,1744],{"class":847},[810,3928,1747],{"class":834},[810,3930,3931,3933,3935,3937,3939,3941,3943,3945,3947],{"class":812,"line":838},[810,3932,1709],{"class":823},[810,3934,1754],{"class":854},[810,3936,1757],{"class":823},[810,3938,1760],{"class":834},[810,3940,1763],{"class":830},[810,3942,1766],{"class":834},[810,3944,1770],{"class":1769},[810,3946,1773],{"class":823},[810,3948,1776],{"class":834},[221,3950,3952],{"id":3951},"about-the-development-journey","About the Development Journey",[16,3954,3955,3956,3959],{},"The most interesting part of this journey was that it didn't take months of writing complex code or integrating obscure APIs. The real challenge was ",[28,3957,3958],{},"organizing information",", defining data structures (what fields the Markdown frontmatter required), and designing the site's Information Architecture.",[16,3961,3962],{},"Today, my workflow for publishing a new article or project is seamless:",[529,3964,3965,3971,3974,3981],{},[532,3966,3967,3968,3970],{},"Copy and paste a pre-formatted ",[244,3969,1645],{}," template.",[532,3972,3973],{},"Write content naturally in my favorite markdown editor.",[532,3975,3976,3977,3032,3979,402],{},"Run ",[244,3978,1807],{},[244,3980,1810],{},[532,3982,3983],{},"My local Home Lab CI/CD intercepts the push and builds/deploys automatically.",[623,3985,3986],{},[16,3987,3988,3991],{},[28,3989,3990],{},"⚠️ Spoiler Alert:"," Setting up my local Home Lab with GitLab CI and Node is a longer story that will definitely get a dedicated post in the future!",[221,3993,3995],{"id":3994},"conclusion","Conclusion",[16,3997,3998,3999,4001],{},"I am extremely happy with the result. I built exactly what I wanted using tools I love, and the ongoing publishing workflow is completely frictionless. This portfolio is not just a site; it's a ",[262,4000,1831],{}," where I can showcase my work and share insights.",[16,4003,4004,4005,4008,4009,799],{},"I hope you enjoy browsing the site! Feel free to share your feedback. Reach out on ",[20,4006,1840],{"href":1838,"rel":4007},[24]," or send me an email at (",[20,4010,1845],{"href":1844},[16,4012,4013],{},"Cheers!",[1036,4015,1851],{},{"title":806,"searchDepth":820,"depth":820,"links":4017},[4018,4019,4022,4023],{"id":3807,"depth":820,"text":3808},{"id":3827,"depth":820,"text":3828,"children":4020},[4021],{"id":3854,"depth":838,"text":3855},{"id":3951,"depth":820,"text":3952},{"id":3994,"depth":820,"text":3995},"Building my headless static CMS website using Markdown, Nuxt 4, and Tailwind CSS.",{"isOnReview":1864},"/en/blog/nuxt4-alicerce",{"title":3788,"description":4024},"en/blog/nuxt4-alicerce",[1065,1869,1870,1871,1872],"Jb-kl7R589250sy_Mb3z5HuphvgeIaSNaV1R23vs3ZQ",{"id":4032,"title":4033,"body":4034,"category":2204,"date":2205,"description":4306,"extension":1058,"featured":1059,"meta":4307,"navigation":1059,"path":4308,"seo":4309,"stem":4310,"tags":4311,"__hash__":4312},"blog_en/en/blog/assinaturas-clicksign-fastify-nuxt.md","Digital Signatures Architecture: Integrating Clicksign, Fastify, and Nuxt",{"type":9,"value":4035,"toc":4299},[4036,4039,4045,4049,4052,4069,4073,4135,4139,4142,4274,4278,4286,4294,4297],[12,4037,4033],{"id":4038},"digital-signatures-architecture-integrating-clicksign-fastify-and-nuxt",[16,4040,4041,4042,4044],{},"In the ",[28,4043,1891],{}," project, the biggest technical challenge was ensuring security and integrity in the purchase and sale contract signature workflow.",[221,4046,4048],{"id":4047},"the-challenge","The Challenge",[16,4050,4051],{},"We needed an asynchronous workflow where:",[529,4053,4054,4057,4063,4066],{},[532,4055,4056],{},"The contract is generated in the backend.",[532,4058,4059,4060,4062],{},"Sent to the ",[28,4061,1910],{}," API.",[532,4064,4065],{},"Notified via Webhook once signed.",[532,4067,4068],{},"Reflected instantly in the customer's Nuxt dashboard.",[221,4070,4072],{"id":4071},"logical-architecture-diagram","Logical Architecture Diagram",[47,4074,4075,4088],{},[50,4076,4077],{},[53,4078,4079,4082,4085],{},[56,4080,4081],{"align":1497},"Stage",[56,4083,4084],{"align":1497},"Component",[56,4086,4087],{"align":1497},"Action",[66,4089,4090,4101,4113,4124],{},[53,4091,4092,4096,4098],{},[71,4093,4094],{"align":1497},[28,4095,1944],{},[71,4097,1947],{"align":1497},[71,4099,4100],{"align":1497},"User confirms contract details.",[53,4102,4103,4108,4110],{},[71,4104,4105],{"align":1497},[28,4106,4107],{},"Process",[71,4109,1960],{"align":1497},[71,4111,4112],{"align":1497},"Generates PDF and invokes Clicksign API.",[53,4114,4115,4119,4121],{},[71,4116,4117],{"align":1497},[28,4118,4087],{},[71,4120,1972],{"align":1497},[71,4122,4123],{"align":1497},"Sends emails and manages signatures.",[53,4125,4126,4130,4132],{},[71,4127,4128],{"align":1497},[28,4129,1982],{},[71,4131,1985],{"align":1497},[71,4133,4134],{"align":1497},"Receives event and updates PostgreSQL.",[221,4136,4138],{"id":4137},"technical-implementation-code-snippet","Technical Implementation (Code Snippet)",[16,4140,4141],{},"Below is a simplified example of how we handle the Clicksign webhook inside Fastify:",[801,4143,4145],{"className":803,"code":4144,"language":805,"meta":806,"style":806},"// Webhook Handler\nfastify.post('/webhooks/clicksign', async (request, reply) => {\n  const { event, document } = request.body;\n\n  if (event.name === 'sign') {\n    // Update contract status in database\n    await db.contracts.update({\n      where: { externalId: document.key },\n      data: { status: 'SIGNED' }\n    });\n    \n    return reply.code(200).send();\n  }\n});\n",[244,4146,4147,4151,4179,4197,4201,4213,4218,4228,4232,4240,4244,4248,4266,4270],{"__ignoreMap":806},[810,4148,4149],{"class":812,"line":813},[810,4150,2005],{"class":816},[810,4152,4153,4155,4157,4159,4161,4163,4165,4167,4169,4171,4173,4175,4177],{"class":812,"line":820},[810,4154,2010],{"class":834},[810,4156,2013],{"class":830},[810,4158,1766],{"class":834},[810,4160,2018],{"class":847},[810,4162,265],{"class":834},[810,4164,2023],{"class":823},[810,4166,2026],{"class":834},[810,4168,2029],{"class":1769},[810,4170,265],{"class":834},[810,4172,2034],{"class":1769},[810,4174,2037],{"class":834},[810,4176,2040],{"class":823},[810,4178,2043],{"class":834},[810,4180,4181,4183,4185,4187,4189,4191,4193,4195],{"class":812,"line":838},[810,4182,2048],{"class":823},[810,4184,2051],{"class":834},[810,4186,2054],{"class":854},[810,4188,265],{"class":834},[810,4190,2059],{"class":854},[810,4192,2062],{"class":834},[810,4194,1723],{"class":823},[810,4196,2067],{"class":834},[810,4198,4199],{"class":812,"line":844},[810,4200,2072],{"emptyLinePlaceholder":1059},[810,4202,4203,4205,4207,4209,4211],{"class":812,"line":861},[810,4204,2077],{"class":823},[810,4206,2080],{"class":834},[810,4208,2083],{"class":823},[810,4210,2086],{"class":847},[810,4212,2089],{"class":834},[810,4214,4215],{"class":812,"line":873},[810,4216,4217],{"class":816},"    // Update contract status in database\n",[810,4219,4220,4222,4224,4226],{"class":812,"line":888},[810,4221,2099],{"class":823},[810,4223,2102],{"class":834},[810,4225,2105],{"class":830},[810,4227,835],{"class":834},[810,4229,4230],{"class":812,"line":894},[810,4231,2112],{"class":834},[810,4233,4234,4236,4238],{"class":812,"line":2115},[810,4235,2118],{"class":834},[810,4237,2121],{"class":847},[810,4239,885],{"class":834},[810,4241,4242],{"class":812,"line":2126},[810,4243,2129],{"class":834},[810,4245,4246],{"class":812,"line":2132},[810,4247,2135],{"class":834},[810,4249,4250,4252,4254,4256,4258,4260,4262,4264],{"class":812,"line":2138},[810,4251,2141],{"class":823},[810,4253,2144],{"class":834},[810,4255,244],{"class":830},[810,4257,1766],{"class":834},[810,4259,2151],{"class":854},[810,4261,799],{"class":834},[810,4263,2156],{"class":830},[810,4265,2159],{"class":834},[810,4267,4268],{"class":812,"line":2162},[810,4269,891],{"class":834},[810,4271,4272],{"class":812,"line":2167},[810,4273,2170],{"class":834},[558,4275,4277],{"id":4276},"callouts-best-practices","Callouts & Best Practices",[623,4279,4280],{},[16,4281,4282,4285],{},[28,4283,4284],{},"Warning:"," Always validate the signature (HMAC) of incoming webhooks to prevent data injection attacks.",[623,4287,4288],{},[16,4289,4290,4293],{},[28,4291,4292],{},"Tip:"," Use Redis to manage temporary document states during the upload flow to keep the UI fast and responsive.",[16,4295,4296],{},"This architecture reduced contract closing time by 40%, proving that efficient API integration is a key business differentiator.",[1036,4298,2196],{},{"title":806,"searchDepth":820,"depth":820,"links":4300},[4301,4302,4303],{"id":4047,"depth":820,"text":4048},{"id":4071,"depth":820,"text":4072},{"id":4137,"depth":820,"text":4138,"children":4304},[4305],{"id":4276,"depth":838,"text":4277},"A deep technical dive into implementing the digital signature workflow for the Reserva Tamandaré project.",{"isOnReview":1059},"/en/blog/assinaturas-clicksign-fastify-nuxt",{"title":4033,"description":4306},"en/blog/assinaturas-clicksign-fastify-nuxt",[2212,2213,2214,2215],"HSNIN-2E9Dy0sSztmUh_x_DTfIj_eMg1mbbhQpo8NEw",{"id":4314,"title":4315,"body":4316,"category":2281,"date":2205,"description":4374,"extension":1058,"featured":1864,"meta":4375,"navigation":1059,"path":4376,"seo":4377,"stem":4378,"tags":4379,"__hash__":4380},"blog_en/en/blog/rog-z13-workstation.md","Performance Setup: ASUS ROG Z13 Flow as a Workstation",{"type":9,"value":4317,"toc":4368},[4318,4321,4327,4331,4334,4338,4358,4362,4365],[12,4319,4315],{"id":4320},"performance-setup-asus-rog-z13-flow-as-a-workstation",[16,4322,4323,4324,4326],{},"For a performance-oriented Software Engineer, hardware is a direct extension of your mind. Choosing the ",[28,4325,2232],{}," reflects my search for raw power combined with extreme portability.",[221,4328,4330],{"id":4329},"why-a-gaming-tablet","Why a Gaming Tablet?",[16,4332,4333],{},"The Z13 Flow is essentially a \"Surface Pro on steroids\". Powered by an Intel Core i9 processor and a dedicated GPU, it handles heavy Nuxt builds and multiple Docker containers effortlessly.",[558,4335,4337],{"id":4336},"key-highlights","Key Highlights:",[529,4339,4340,4346,4352],{},[532,4341,4342,4345],{},[28,4343,4344],{},"Portability:"," Code from anywhere without sacrificing computational power.",[532,4347,4348,4351],{},[28,4349,4350],{},"Vapor Chamber Cooling:"," Crucial for maintaining high CPU clock rates during intense coding sessions.",[532,4353,4354,4357],{},[28,4355,4356],{},"High-Fidelity Display:"," Essential for UI refinement using Tailwind CSS.",[221,4359,4361],{"id":4360},"the-workflow","The Workflow",[16,4363,4364],{},"I run the Z13 connected to an ultrawide monitor setup, where it acts as the main machine. Switching from \"mobile development\" to a \"desktop workstation\" setup is instant, keeping creative flow uninterrupted.",[16,4366,4367],{},"This hardware setup serves as the ideal partner for experimenting with local Generative AI models and managing complex architectures.",{"title":806,"searchDepth":820,"depth":820,"links":4369},[4370,4373],{"id":4329,"depth":820,"text":4330,"children":4371},[4372],{"id":4336,"depth":838,"text":4337},{"id":4360,"depth":820,"text":4361},"How I turned a gaming tablet into a high-performance software engineering workstation.",{"isOnReview":1059},"/en/blog/rog-z13-workstation",{"title":4315,"description":4374},"en/blog/rog-z13-workstation",[1392,2288,2281,2289],"HbKIMgxxYxsAJpZNT3ai5Qog1Mqg_eBUekGalLH9w1s",{"id":4382,"title":4383,"body":4384,"category":75,"date":2371,"description":4453,"extension":1058,"featured":1864,"meta":4454,"navigation":1059,"path":4455,"seo":4456,"stem":4457,"tags":4458,"__hash__":4459},"blog_en/en/blog/otimizando-performance-nuxt.md","Optimizing Performance in Nuxt 3",{"type":9,"value":4385,"toc":4450},[4386,4389,4392,4396,4412,4448],[12,4387,4383],{"id":4388},"optimizing-performance-in-nuxt-3",[16,4390,4391],{},"Achieving maximum performance isn't just about raw speed — it's about delivering a superior user experience.",[221,4393,4395],{"id":4394},"quick-tips","Quick Tips:",[529,4397,4398,4403,4406],{},[532,4399,2312,4400,4402],{},[244,4401,2315],{}," for automatic image optimization.",[532,4404,4405],{},"Minimize reliance on heavy third-party client libraries.",[532,4407,4408,4409,4411],{},"Leverage ",[244,4410,778],{}," for SWR caching.",[801,4413,4414],{"className":803,"code":2329,"language":805,"meta":806,"style":806},[244,4415,4416,4426,4430,4440,4444],{"__ignoreMap":806},[810,4417,4418,4420,4422,4424],{"class":812,"line":813},[810,4419,824],{"class":823},[810,4421,827],{"class":823},[810,4423,831],{"class":830},[810,4425,835],{"class":834},[810,4427,4428],{"class":812,"line":820},[810,4429,841],{"class":834},[810,4431,4432,4434,4436,4438],{"class":812,"line":838},[810,4433,2350],{"class":847},[810,4435,879],{"class":834},[810,4437,882],{"class":854},[810,4439,885],{"class":834},[810,4441,4442],{"class":812,"line":844},[810,4443,891],{"class":834},[810,4445,4446],{"class":812,"line":861},[810,4447,897],{"class":834},[1036,4449,2367],{},{"title":806,"searchDepth":820,"depth":820,"links":4451},[4452],{"id":4394,"depth":820,"text":4395},"How to achieve a 100 Lighthouse score using Nuxt ecosystem best practices.",{"isOnReview":1059},"/en/blog/otimizando-performance-nuxt",{"title":4383,"description":4453},"en/blog/otimizando-performance-nuxt",[1065,1066,1067],"dnVuQrgqFSUCt60ayQO__BVdtDP2aia-XwgOAl9H8tA",{"id":4461,"title":4462,"body":4463,"category":2214,"date":2481,"description":4544,"extension":1058,"featured":1864,"meta":4545,"navigation":1059,"path":4546,"seo":4547,"stem":4548,"tags":4549,"__hash__":4550},"blog_en/en/blog/fastify-node-backend.md","Fastify: The Future of Node.js Backend",{"type":9,"value":4464,"toc":4541},[4465,4468,4471,4475,4539],[12,4466,4462],{"id":4467},"fastify-the-future-of-nodejs-backend",[16,4469,4470],{},"Fastify is a web framework focused on providing low overhead and a plugin-based architecture.",[221,4472,4474],{"id":4473},"server-example","Server Example:",[801,4476,4477],{"className":2398,"code":2399,"language":2400,"meta":806,"style":806},[244,4478,4479,4483,4487,4491,4495,4499,4503,4507,4511,4515,4519,4523,4527,4531,4535],{"__ignoreMap":806},[810,4480,4481],{"class":812,"line":813},[810,4482,2407],{},[810,4484,4485],{"class":812,"line":820},[810,4486,2072],{"emptyLinePlaceholder":1059},[810,4488,4489],{"class":812,"line":838},[810,4490,2416],{},[810,4492,4493],{"class":812,"line":844},[810,4494,2421],{},[810,4496,4497],{"class":812,"line":861},[810,4498,897],{},[810,4500,4501],{"class":812,"line":873},[810,4502,2072],{"emptyLinePlaceholder":1059},[810,4504,4505],{"class":812,"line":888},[810,4506,2434],{},[810,4508,4509],{"class":812,"line":894},[810,4510,2439],{},[810,4512,4513],{"class":812,"line":2115},[810,4514,2444],{},[810,4516,4517],{"class":812,"line":2126},[810,4518,2449],{},[810,4520,4521],{"class":812,"line":2132},[810,4522,2454],{},[810,4524,4525],{"class":812,"line":2138},[810,4526,2459],{},[810,4528,4529],{"class":812,"line":2162},[810,4530,891],{},[810,4532,4533],{"class":812,"line":2167},[810,4534,2468],{},[810,4536,4537],{"class":812,"line":2471},[810,4538,2474],{},[1036,4540,2477],{},{"title":806,"searchDepth":820,"depth":820,"links":4542},[4543],{"id":4473,"depth":820,"text":4474},"Why Fastify became my default choice for high-performance Node.js APIs.",{"isOnReview":1059},"/en/blog/fastify-node-backend",{"title":4462,"description":4544},"en/blog/fastify-node-backend",[2488,2489,75],"pN4iMoJivN-mS8AE1TV-5Lgp2u0rl7tBT-RkrpmmOeI",1789775732357]