[{"data":1,"prerenderedAt":1737},["ShallowReactive",2],{"astro-vs-nuxt-pt":3},{"id":4,"title":5,"body":6,"description":1723,"extension":1724,"meta":1725,"navigation":265,"path":1728,"rawbody":1729,"seo":1730,"stem":1731,"__hash__":1732,"createdAt":1733,"updatedAt":1736},"content_pt/blog/astro-vs-nuxt/pt.md","Astro vs. Nuxt: Qual você deveria usar para o seu blog?",{"type":7,"value":8,"toc":1711},"minimark",[9,21,47,54,59,70,77,87,91,94,104,107,112,115,125,128,139,142,146,157,290,301,311,571,574,585,589,596,599,606,665,684,839,862,1090,1093,1110,1114,1117,1123,1293,1299,1568,1571,1577,1583,1590,1594,1617,1620,1633,1640,1643,1647,1650,1666,1679,1691,1694,1698,1701,1704,1707],[10,11,12,13,20],"p",{},"Olá! Nesses últimos dias, eu dei uma olhada no ",[14,15,19],"a",{"href":16,"rel":17},"https://astro.build/",[18],"nofollow","Astro",", uma framework para websites que tem como seu foco principal o conteúdo; ou seja, ela foi feita pensando diretamente em artigos, documentações e blogs.",[10,22,23,24,28,29,34,35,40,41,46],{},"O maior benefício dela é dado pelo Javascript ",[25,26,27],"em",{},"mínimo",". Isso me chamou muita atenção. Eu comentei ",[14,30,33],{"href":31,"rel":32},"https://blog.marciosobel.dev/pt/my-first-blog-post/",[18],"no meu primeiro post"," que havia pensado bastante entre usar o Astro ou ",[14,36,39],{"href":37,"rel":38},"https://gohugo.io/",[18],"Hugo"," para o meu blog, mas acabei escolhendo o ",[14,42,45],{"href":43,"rel":44},"https://nuxt.com/",[18],"Nuxt"," por ser uma framework feta para construir websites do zero, o que me daria mais flexibilidade para moldar o site para ficar no meu estilo.",[10,48,49,50,53],{},"Entretanto, decidi testar o Astro, e ver o que seria dele para mim. Quem sabe eu não o uso no lugar do ",[51,52,45],"code",{}," nesse blog?",[55,56,58],"h2",{"id":57},"por-que-astro","Por que Astro?",[10,60,61,62,65,66,69],{},"Logo de cara, o que mais me chamou atenção no Astro foi seu foco em conteúdo estático. Isso significa que ele não foi feito para ter muita interatividade: botões, requisições, formulários... Ele foi feito para coisas que mudam ",[25,63,64],{},"muito pouco",", como documentação, artigos, ",[25,67,68],{},"landing pages"," e blogs.",[10,71,72,73,76],{},"Ele possui suporte ",[25,74,75],{},"first-level"," para Markdown, linguagem que uso para escrever os meus posts. Também, ele evita ao máximo enviar código Javascript para o usuário; isso significa que os sites sempre são super leves.",[10,78,79,80,82,83,86],{},"Bom, eu decidi testar o Astro pois eu fiquei com uma pulga atrás da orelha em relação ao ",[51,81,45],{},": ele leva consigo o runtime de Javascript do ",[51,84,85],{},"Vue"," junto. Apesar de ser pouca coisa, eu queria que o meu blog fosse o mais simples e leve possível. E assim começou minha jornada!",[55,88,90],{"id":89},"recriando-o-meu-blog-com-astro","Recriando o meu blog com Astro",[10,92,93],{},"Vamos lá, primeiro vamos ver o que eu quero conseguir aqui.",[95,96,97,101],"ul",{},[98,99,100],"li",{},"Escrever meus posts em Markdown e não me preocupar com como eles vão parar no site; quero apenas escrever, subir na nuvem e o build do Astro deve atualizar a listagem e gerar os links de acordo;",[98,102,103],{},"Ter o site em dois ou mais idiomas.",[10,105,106],{},"É, o blog é bem simples; mas, acredito que, com essas exigências, vai dar para sujar bem as mãos na framework.",[108,109,111],"h3",{"id":110},"dando-início-ao-projeto","Dando início ao projeto",[10,113,114],{},"Essa é a parte mais fácil e a mais direta de toda essa jornada. Só precisei rodar:",[116,117,122],"pre",{"className":118,"code":120,"language":121},[119],"language-text","pnpm create astro@latest\n","text",[51,123,120],{"__ignoreMap":124},"",[10,126,127],{},"E seguir o passo a passo do robôzinho cuti cuti.",[10,129,130,131,134,135,138],{},"A arquitetura é bem simples, rotas baseadas por arquivos... Tudo super intuitivo. Eu não usei nenhum template, então para mim só tinha um ",[51,132,133],{},"index.astro",", com a tela toda em branco, apenas com um ",[51,136,137],{},"h1"," escrito \"Astro\".",[10,140,141],{},"Bom, vamos começar a riscar alguns itens da lista.",[108,143,145],{"id":144},"listando-todos-os-posts","Listando todos os posts",[10,147,148,149,152,153,156],{},"Comecei criando uma pasta ",[51,150,151],{},"posts/",", e coloquei os posts do blog nela. Como o Astro já foi feito pensado em lidar com posts, então foi bem fácil. Basta criar um arquivo ",[51,154,155],{},"content.config.ts"," com o seguinte código:",[116,158,162],{"className":159,"code":160,"language":161,"meta":124,"style":124},"language-typescript shiki shiki-themes gruvbox-light-soft gruvbox-dark-soft","const blog = defineCollection({\n  loader: glob({ base: \"posts\", pattern: \"**/*.{md,mdx}\" }),\n});\n\nexport const collections = { blog };\n","typescript",[51,163,164,193,249,260,267],{"__ignoreMap":124},[165,166,169,173,177,181,185,189],"span",{"class":167,"line":168},"line",1,[165,170,172],{"class":171},"s5BFC","const",[165,174,176],{"class":175},"selZM"," blog",[165,178,180],{"class":179},"sJcFL"," =",[165,182,184],{"class":183},"s9LfA"," defineCollection",[165,186,188],{"class":187},"s7kJP","(",[165,190,192],{"class":191},"sLjwx","{\n",[165,194,196,199,202,205,207,210,213,215,218,222,225,228,231,233,235,238,240,243,246],{"class":167,"line":195},2,[165,197,198],{"class":187},"  loader",[165,200,201],{"class":191},":",[165,203,204],{"class":183}," glob",[165,206,188],{"class":187},[165,208,209],{"class":191},"{",[165,211,212],{"class":187}," base",[165,214,201],{"class":191},[165,216,217],{"class":191}," \"",[165,219,221],{"class":220},"sg8ZQ","posts",[165,223,224],{"class":191},"\"",[165,226,227],{"class":191},",",[165,229,230],{"class":187}," pattern",[165,232,201],{"class":191},[165,234,217],{"class":191},[165,236,237],{"class":220},"**/*.{md,mdx}",[165,239,224],{"class":191},[165,241,242],{"class":191}," }",[165,244,245],{"class":187},")",[165,247,248],{"class":191},",\n",[165,250,252,255,257],{"class":167,"line":251},3,[165,253,254],{"class":191},"}",[165,256,245],{"class":187},[165,258,259],{"class":191},";\n",[165,261,263],{"class":167,"line":262},4,[165,264,266],{"emptyLinePlaceholder":265},true,"\n",[165,268,270,274,277,280,282,285,287],{"class":167,"line":269},5,[165,271,273],{"class":272},"sn-iv","export",[165,275,276],{"class":171}," const",[165,278,279],{"class":175}," collections",[165,281,180],{"class":179},[165,283,284],{"class":191}," {",[165,286,176],{"class":175},[165,288,289],{"class":191}," };\n",[10,291,292,293,296,297,300],{},"Isso foi bem fácil. Eu usei o ",[51,294,295],{},"zod"," para criar um schema para os ",[25,298,299],{},"frontmatters",", os metadados, dos posts.",[10,302,303,304,306,307,310],{},"Então, no arquivo ",[51,305,133],{},", o ",[25,308,309],{},"entrypoint"," do website, eu coloquei o seguinte:",[116,312,316],{"className":313,"code":314,"language":315,"meta":124,"style":124},"language-astro shiki shiki-themes gruvbox-light-soft gruvbox-dark-soft","---\nimport { getCollection } from \"astro:content\";\nconst posts = await getCollection(\"blog\");\nif (!posts) return Astro.redirect(\"/404\");\n---\n\n\u003Ch1>Blog\u003C/h1>\n\u003Cul>\n  {posts.map((post) => (\n    \u003Cli>\n      \u003Ca href={post.id}>{post.data.title}\u003C/a>\n    \u003C/li>\n  ))}\n\u003C/ul>\n","astro",[51,317,318,324,348,375,416,420,425,447,456,484,494,543,553,562],{"__ignoreMap":124},[165,319,320],{"class":167,"line":168},[165,321,323],{"class":322},"sFEbH","---\n",[165,325,326,329,331,334,336,339,341,344,346],{"class":167,"line":195},[165,327,328],{"class":272},"import",[165,330,284],{"class":191},[165,332,333],{"class":175}," getCollection",[165,335,242],{"class":191},[165,337,338],{"class":272}," from",[165,340,217],{"class":191},[165,342,343],{"class":220},"astro:content",[165,345,224],{"class":191},[165,347,259],{"class":191},[165,349,350,352,355,357,360,362,364,366,369,371,373],{"class":167,"line":251},[165,351,172],{"class":171},[165,353,354],{"class":175}," posts",[165,356,180],{"class":179},[165,358,359],{"class":272}," await",[165,361,333],{"class":183},[165,363,188],{"class":187},[165,365,224],{"class":191},[165,367,368],{"class":220},"blog",[165,370,224],{"class":191},[165,372,245],{"class":187},[165,374,259],{"class":191},[165,376,377,380,383,386,388,391,394,397,400,403,405,407,410,412,414],{"class":167,"line":262},[165,378,379],{"class":272},"if",[165,381,382],{"class":187}," (",[165,384,385],{"class":272},"!",[165,387,221],{"class":175},[165,389,390],{"class":187},") ",[165,392,393],{"class":272},"return",[165,395,396],{"class":175}," Astro",[165,398,399],{"class":191},".",[165,401,402],{"class":183},"redirect",[165,404,188],{"class":187},[165,406,224],{"class":191},[165,408,409],{"class":220},"/404",[165,411,224],{"class":191},[165,413,245],{"class":187},[165,415,259],{"class":191},[165,417,418],{"class":167,"line":269},[165,419,323],{"class":322},[165,421,423],{"class":167,"line":422},6,[165,424,266],{"emptyLinePlaceholder":265},[165,426,428,431,433,436,439,442,444],{"class":167,"line":427},7,[165,429,430],{"class":175},"\u003C",[165,432,137],{"class":179},[165,434,435],{"class":175},">",[165,437,438],{"class":187},"Blog",[165,440,441],{"class":175},"\u003C/",[165,443,137],{"class":179},[165,445,446],{"class":175},">\n",[165,448,450,452,454],{"class":167,"line":449},8,[165,451,430],{"class":175},[165,453,95],{"class":179},[165,455,446],{"class":175},[165,457,459,462,464,466,469,471,473,476,478,481],{"class":167,"line":458},9,[165,460,461],{"class":191},"  {",[165,463,221],{"class":175},[165,465,399],{"class":191},[165,467,468],{"class":183},"map",[165,470,188],{"class":187},[165,472,188],{"class":191},[165,474,475],{"class":175},"post",[165,477,245],{"class":191},[165,479,480],{"class":171}," =>",[165,482,483],{"class":187}," (\n",[165,485,487,490,492],{"class":167,"line":486},10,[165,488,489],{"class":175},"    \u003C",[165,491,98],{"class":179},[165,493,446],{"class":175},[165,495,497,500,502,505,508,510,512,514,517,519,521,523,525,527,530,532,535,537,539,541],{"class":167,"line":496},11,[165,498,499],{"class":175},"      \u003C",[165,501,14],{"class":179},[165,503,504],{"class":183}," href",[165,506,507],{"class":179},"=",[165,509,209],{"class":191},[165,511,475],{"class":175},[165,513,399],{"class":191},[165,515,516],{"class":175},"id",[165,518,254],{"class":191},[165,520,435],{"class":175},[165,522,209],{"class":191},[165,524,475],{"class":175},[165,526,399],{"class":191},[165,528,529],{"class":175},"data",[165,531,399],{"class":191},[165,533,534],{"class":175},"title",[165,536,254],{"class":191},[165,538,441],{"class":175},[165,540,14],{"class":179},[165,542,446],{"class":175},[165,544,546,549,551],{"class":167,"line":545},12,[165,547,548],{"class":175},"    \u003C/",[165,550,98],{"class":179},[165,552,446],{"class":175},[165,554,556,559],{"class":167,"line":555},13,[165,557,558],{"class":187},"  ))",[165,560,561],{"class":191},"}\n",[165,563,565,567,569],{"class":167,"line":564},14,[165,566,441],{"class":175},[165,568,95],{"class":179},[165,570,446],{"class":175},[10,572,573],{},"Eeeee isso funcionou? Foi muito fácil!",[10,575,576,577,580,581,584],{},"No Astro, qualquer código executado entre as \"fences\" (",[51,578,579],{},"---",") é executado apenas em ",[25,582,583],{},"build time",". Isso significa que nossa aplicação até o momento está com um total de zero  Javascript!",[108,586,588],{"id":587},"gerando-páginas-dinamicamente","Gerando páginas dinamicamente",[10,590,591,592,595],{},"Após a criação da página de listagem de posts, eu queria criar uma página para cada post. Para isso, eu precisava de uma ",[25,593,594],{},"rota dinâmica",", ou seja, precisaria que o Astro gerasse as páginas para mim.",[10,597,598],{},"Gostaria de aproveitar este momento e dizer que a documentação do Astro é muito boa! Definitivamente seria muito mais trabalhoso fazer isso sem ela.",[10,600,601,602,605],{},"Enfim, eu criei um arquivo ",[51,603,604],{},"[slug].astro",". Este arquivo aceita qualquer valor como entrada, e eu posso usar esse valor via código.",[116,607,609],{"className":313,"code":608,"language":315,"meta":124,"style":124},"---\nconst { slug } = Astro.params;\n---\n\u003Cp>Slug: {slug}\u003C/p>\n",[51,610,611,615,637,641],{"__ignoreMap":124},[165,612,613],{"class":167,"line":168},[165,614,323],{"class":322},[165,616,617,619,621,624,626,628,630,632,635],{"class":167,"line":195},[165,618,172],{"class":171},[165,620,284],{"class":191},[165,622,623],{"class":175}," slug",[165,625,242],{"class":191},[165,627,180],{"class":179},[165,629,396],{"class":175},[165,631,399],{"class":191},[165,633,634],{"class":175},"params",[165,636,259],{"class":191},[165,638,639],{"class":167,"line":251},[165,640,323],{"class":322},[165,642,643,645,647,649,652,654,657,659,661,663],{"class":167,"line":262},[165,644,430],{"class":175},[165,646,10],{"class":179},[165,648,435],{"class":175},[165,650,651],{"class":187},"Slug: ",[165,653,209],{"class":191},[165,655,656],{"class":175},"slug",[165,658,254],{"class":191},[165,660,441],{"class":175},[165,662,10],{"class":179},[165,664,446],{"class":175},[10,666,667,668,671,672,675,676,679,680,683],{},"Então usei isso para pegar o ID de um post. Felizmente, o Astro já possui uma função específica para pegar um item de uma coleção: ",[51,669,670],{},"getEntry",". Ela recebe a coleção e o ID para buscar. Após isso, precisava saber como mostrar o conteúdo ",[51,673,674],{},"markdown"," em ",[51,677,678],{},"HTML",". Então me deparo com a função ",[51,681,682],{},"render",", que útil!",[116,685,687],{"className":313,"code":686,"language":315,"meta":124,"style":124},"---\nimport { getEntry, render } from \"astro:content\";\nconst { slug } = Astro.params;\nconst post = await getEntry(\"blog\", slug);\nif (!post) return Astro.redirect(\"/404\");\nconst { Content } = await render(post);\n---\n\u003CContent />\n",[51,688,689,693,719,739,768,800,825,829],{"__ignoreMap":124},[165,690,691],{"class":167,"line":168},[165,692,323],{"class":322},[165,694,695,697,699,702,704,707,709,711,713,715,717],{"class":167,"line":195},[165,696,328],{"class":272},[165,698,284],{"class":191},[165,700,701],{"class":175}," getEntry",[165,703,227],{"class":191},[165,705,706],{"class":175}," render",[165,708,242],{"class":191},[165,710,338],{"class":272},[165,712,217],{"class":191},[165,714,343],{"class":220},[165,716,224],{"class":191},[165,718,259],{"class":191},[165,720,721,723,725,727,729,731,733,735,737],{"class":167,"line":251},[165,722,172],{"class":171},[165,724,284],{"class":191},[165,726,623],{"class":175},[165,728,242],{"class":191},[165,730,180],{"class":179},[165,732,396],{"class":175},[165,734,399],{"class":191},[165,736,634],{"class":175},[165,738,259],{"class":191},[165,740,741,743,746,748,750,752,754,756,758,760,762,764,766],{"class":167,"line":262},[165,742,172],{"class":171},[165,744,745],{"class":175}," post",[165,747,180],{"class":179},[165,749,359],{"class":272},[165,751,701],{"class":183},[165,753,188],{"class":187},[165,755,224],{"class":191},[165,757,368],{"class":220},[165,759,224],{"class":191},[165,761,227],{"class":191},[165,763,623],{"class":175},[165,765,245],{"class":187},[165,767,259],{"class":191},[165,769,770,772,774,776,778,780,782,784,786,788,790,792,794,796,798],{"class":167,"line":269},[165,771,379],{"class":272},[165,773,382],{"class":187},[165,775,385],{"class":272},[165,777,475],{"class":175},[165,779,390],{"class":187},[165,781,393],{"class":272},[165,783,396],{"class":175},[165,785,399],{"class":191},[165,787,402],{"class":183},[165,789,188],{"class":187},[165,791,224],{"class":191},[165,793,409],{"class":220},[165,795,224],{"class":191},[165,797,245],{"class":187},[165,799,259],{"class":191},[165,801,802,804,806,809,811,813,815,817,819,821,823],{"class":167,"line":422},[165,803,172],{"class":171},[165,805,284],{"class":191},[165,807,808],{"class":175}," Content",[165,810,242],{"class":191},[165,812,180],{"class":179},[165,814,359],{"class":272},[165,816,706],{"class":183},[165,818,188],{"class":187},[165,820,475],{"class":175},[165,822,245],{"class":187},[165,824,259],{"class":191},[165,826,827],{"class":167,"line":427},[165,828,323],{"class":322},[165,830,831,833,836],{"class":167,"line":449},[165,832,430],{"class":175},[165,834,835],{"class":187},"Content ",[165,837,838],{"class":175},"/>\n",[10,840,841,842,844,845,849,850,853,854,857,858,861],{},"E em teoria era para funcionar super bem... Mas o Astro não consegue saber todas as rotas possíveis para o ",[51,843,604],{},", o que o impedia de deixar o site limpo de javascript. Para contornar isso, temos que dizer ao Astro ",[846,847,848],"strong",{},"todas as rotas possíveis",". Fazemos isso exportando uma função ",[51,851,852],{},"getStaticPaths",", que ",[25,855,856],{},"felizmente"," pode ser ",[51,859,860],{},"async",", onde retornamos uma lista com todas as variáveis completadas. Também podemos passar props para cada página, o que significa que podemos passar todos os posts diretamente!",[116,863,865],{"className":313,"code":864,"language":315,"meta":124,"style":124},"---\nimport { getCollection, render } from \"astro:content\"; // agora, pegamos a coleção inteira!\n\nexport async function getStaticPaths() {\n  const posts = await getCollection(\"blog\");\n  return posts.map((post) => ({\n    params: { slug: post.id },\n    props: { post }, // podemos passar o post diretamente para a página!\n  }));\n}\n\nconst { post } = Astro.props; // E o post é garantido que existe!\nconst { Content } = await render(post);\n---\n\u003CContent />\n",[51,866,867,871,899,903,922,947,972,994,1011,1021,1025,1029,1053,1077,1081],{"__ignoreMap":124},[165,868,869],{"class":167,"line":168},[165,870,323],{"class":322},[165,872,873,875,877,879,881,883,885,887,889,891,893,896],{"class":167,"line":195},[165,874,328],{"class":272},[165,876,284],{"class":191},[165,878,333],{"class":175},[165,880,227],{"class":191},[165,882,706],{"class":175},[165,884,242],{"class":191},[165,886,338],{"class":272},[165,888,217],{"class":191},[165,890,343],{"class":220},[165,892,224],{"class":191},[165,894,895],{"class":191},";",[165,897,898],{"class":322}," // agora, pegamos a coleção inteira!\n",[165,900,901],{"class":167,"line":251},[165,902,266],{"emptyLinePlaceholder":265},[165,904,905,907,910,913,916,919],{"class":167,"line":262},[165,906,273],{"class":272},[165,908,909],{"class":171}," async",[165,911,912],{"class":171}," function",[165,914,915],{"class":183}," getStaticPaths",[165,917,918],{"class":191},"()",[165,920,921],{"class":191}," {\n",[165,923,924,927,929,931,933,935,937,939,941,943,945],{"class":167,"line":269},[165,925,926],{"class":171},"  const",[165,928,354],{"class":175},[165,930,180],{"class":179},[165,932,359],{"class":272},[165,934,333],{"class":183},[165,936,188],{"class":187},[165,938,224],{"class":191},[165,940,368],{"class":220},[165,942,224],{"class":191},[165,944,245],{"class":187},[165,946,259],{"class":191},[165,948,949,952,954,956,958,960,962,964,966,968,970],{"class":167,"line":422},[165,950,951],{"class":272},"  return",[165,953,354],{"class":175},[165,955,399],{"class":191},[165,957,468],{"class":183},[165,959,188],{"class":187},[165,961,188],{"class":191},[165,963,475],{"class":175},[165,965,245],{"class":191},[165,967,480],{"class":171},[165,969,382],{"class":187},[165,971,192],{"class":191},[165,973,974,977,979,981,983,985,987,989,991],{"class":167,"line":427},[165,975,976],{"class":187},"    params",[165,978,201],{"class":191},[165,980,284],{"class":191},[165,982,623],{"class":187},[165,984,201],{"class":191},[165,986,745],{"class":175},[165,988,399],{"class":191},[165,990,516],{"class":175},[165,992,993],{"class":191}," },\n",[165,995,996,999,1001,1003,1005,1008],{"class":167,"line":449},[165,997,998],{"class":187},"    props",[165,1000,201],{"class":191},[165,1002,284],{"class":191},[165,1004,745],{"class":175},[165,1006,1007],{"class":191}," },",[165,1009,1010],{"class":322}," // podemos passar o post diretamente para a página!\n",[165,1012,1013,1016,1019],{"class":167,"line":458},[165,1014,1015],{"class":191},"  }",[165,1017,1018],{"class":187},"))",[165,1020,259],{"class":191},[165,1022,1023],{"class":167,"line":486},[165,1024,561],{"class":191},[165,1026,1027],{"class":167,"line":496},[165,1028,266],{"emptyLinePlaceholder":265},[165,1030,1031,1033,1035,1037,1039,1041,1043,1045,1048,1050],{"class":167,"line":545},[165,1032,172],{"class":171},[165,1034,284],{"class":191},[165,1036,745],{"class":175},[165,1038,242],{"class":191},[165,1040,180],{"class":179},[165,1042,396],{"class":175},[165,1044,399],{"class":191},[165,1046,1047],{"class":175},"props",[165,1049,895],{"class":191},[165,1051,1052],{"class":322}," // E o post é garantido que existe!\n",[165,1054,1055,1057,1059,1061,1063,1065,1067,1069,1071,1073,1075],{"class":167,"line":555},[165,1056,172],{"class":171},[165,1058,284],{"class":191},[165,1060,808],{"class":175},[165,1062,242],{"class":191},[165,1064,180],{"class":179},[165,1066,359],{"class":272},[165,1068,706],{"class":183},[165,1070,188],{"class":187},[165,1072,475],{"class":175},[165,1074,245],{"class":187},[165,1076,259],{"class":191},[165,1078,1079],{"class":167,"line":564},[165,1080,323],{"class":322},[165,1082,1084,1086,1088],{"class":167,"line":1083},15,[165,1085,430],{"class":175},[165,1087,835],{"class":187},[165,1089,838],{"class":175},[10,1091,1092],{},"Okay! Isso foi bem fácil.",[10,1094,1095,1096,1099,1100,1103,1104,1109],{},"Testando os diferentes posts, vi que um deles estava renderizando os acentos errado. Por exemplo, ao invés de da palavra ",[51,1097,1098],{},"água",", o HTML renderizado retornava ",[51,1101,1102],{},"Ã¡gua",". Fiquei confuso do motivo, e aparentemente o Astro não define mais o charset do documento como UTF-8 por padrão. Fiquei bem confuso pois ",[14,1105,1108],{"href":1106,"rel":1107},"https://docs.astro.build/en/guides/markdown-content/#frontmatter-layout-property",[18],"só vi isso sendo citado em um lugar das docs","? Mas tudo bem, perdi um tempinho nessa parte.",[108,1111,1113],{"id":1112},"lidando-com-diferentes-idiomas","Lidando com diferentes idiomas",[10,1115,1116],{},"Caso você nunca tenha notado, este blog está disponível tanto em Português do Brasil, quanto em Inglês. Até o momento em que escrevo este post, todos os posts estão disponíveis nos dois idiomas. Não sei se em algum momento eles vão divergir e ter conteúdos diferentes.",[10,1118,1119,1120,1122],{},"Enfim, ter múltiplos idiomas é algo necessário. Como o bom programador que sou™, quis deixar dinâmico o suficiente para ter a menor dor de cabeça possível caso eu queira escrever em algum outro idioma. Primeiro, alterei o arquivo ",[51,1121,155],{}," para ter os posts separados:",[116,1124,1126],{"className":159,"code":1125,"language":161,"meta":124,"style":124},"function createCollection(suffix: string) {\n  return defineCollection({\n    loader: glob({ base: `posts/${suffix}`, pattern: \"**/*.{md,mdx}\" });\n  });\n}\n\nexport const collections = {\n  \"blog-en\": createCollection(\"en\"),\n  \"blog-pt\": createCollection(\"pt\"),\n};\n",[51,1127,1128,1150,1160,1207,1215,1219,1223,1235,1262,1288],{"__ignoreMap":124},[165,1129,1130,1133,1136,1138,1141,1143,1146,1148],{"class":167,"line":168},[165,1131,1132],{"class":171},"function",[165,1134,1135],{"class":183}," createCollection",[165,1137,188],{"class":191},[165,1139,1140],{"class":175},"suffix",[165,1142,201],{"class":179},[165,1144,1145],{"class":183}," string",[165,1147,245],{"class":191},[165,1149,921],{"class":191},[165,1151,1152,1154,1156,1158],{"class":167,"line":195},[165,1153,951],{"class":272},[165,1155,184],{"class":183},[165,1157,188],{"class":187},[165,1159,192],{"class":191},[165,1161,1162,1165,1167,1169,1171,1173,1175,1177,1180,1182,1185,1187,1190,1192,1194,1196,1198,1200,1202,1204],{"class":167,"line":251},[165,1163,1164],{"class":187},"    loader",[165,1166,201],{"class":191},[165,1168,204],{"class":183},[165,1170,188],{"class":187},[165,1172,209],{"class":191},[165,1174,212],{"class":187},[165,1176,201],{"class":191},[165,1178,1179],{"class":191}," `",[165,1181,151],{"class":220},[165,1183,1184],{"class":191},"${",[165,1186,1140],{"class":175},[165,1188,1189],{"class":191},"}`",[165,1191,227],{"class":191},[165,1193,230],{"class":187},[165,1195,201],{"class":191},[165,1197,217],{"class":191},[165,1199,237],{"class":220},[165,1201,224],{"class":191},[165,1203,242],{"class":191},[165,1205,1206],{"class":187},");\n",[165,1208,1209,1211,1213],{"class":167,"line":262},[165,1210,1015],{"class":191},[165,1212,245],{"class":187},[165,1214,259],{"class":191},[165,1216,1217],{"class":167,"line":269},[165,1218,561],{"class":191},[165,1220,1221],{"class":167,"line":422},[165,1222,266],{"emptyLinePlaceholder":265},[165,1224,1225,1227,1229,1231,1233],{"class":167,"line":427},[165,1226,273],{"class":272},[165,1228,276],{"class":171},[165,1230,279],{"class":175},[165,1232,180],{"class":179},[165,1234,921],{"class":191},[165,1236,1237,1240,1243,1245,1247,1249,1251,1253,1256,1258,1260],{"class":167,"line":449},[165,1238,1239],{"class":191},"  \"",[165,1241,1242],{"class":220},"blog-en",[165,1244,224],{"class":191},[165,1246,201],{"class":191},[165,1248,1135],{"class":183},[165,1250,188],{"class":187},[165,1252,224],{"class":191},[165,1254,1255],{"class":220},"en",[165,1257,224],{"class":191},[165,1259,245],{"class":187},[165,1261,248],{"class":191},[165,1263,1264,1266,1269,1271,1273,1275,1277,1279,1282,1284,1286],{"class":167,"line":458},[165,1265,1239],{"class":191},[165,1267,1268],{"class":220},"blog-pt",[165,1270,224],{"class":191},[165,1272,201],{"class":191},[165,1274,1135],{"class":183},[165,1276,188],{"class":187},[165,1278,224],{"class":191},[165,1280,1281],{"class":220},"pt",[165,1283,224],{"class":191},[165,1285,245],{"class":187},[165,1287,248],{"class":191},[165,1289,1290],{"class":167,"line":486},[165,1291,1292],{"class":191},"};\n",[10,1294,1295,1296,156],{},"E então criei um arquivo ",[51,1297,1298],{},"i18n.ts",[116,1300,1302],{"className":159,"code":1301,"language":161,"meta":124,"style":124},"const entries = {\n  en: {\n    \"all-posts\": \"All posts\",\n  },\n  pt: {\n    \"all-posts\": \"Todos os posts\",\n  },\n};\n\nexport type Locales = keyof typeof entries;\nexport const locales = Object.keys(entries) as Locale[];\nexport const defaultLocale: Locale = \"en\" as const;\n\nimport { getCollection as getContentCollection } from \"astro:content\";\n\nexport function getCollection(locale: Locale = defaultLocale) {\n    return getContentCollection(`blog-${locale}`);\n}\n",[51,1303,1304,1315,1324,1345,1350,1359,1378,1382,1386,1390,1412,1449,1478,1482,1507,1511,1537,1563],{"__ignoreMap":124},[165,1305,1306,1308,1311,1313],{"class":167,"line":168},[165,1307,172],{"class":171},[165,1309,1310],{"class":175}," entries",[165,1312,180],{"class":179},[165,1314,921],{"class":191},[165,1316,1317,1320,1322],{"class":167,"line":195},[165,1318,1319],{"class":187},"  en",[165,1321,201],{"class":191},[165,1323,921],{"class":191},[165,1325,1326,1329,1332,1334,1336,1338,1341,1343],{"class":167,"line":251},[165,1327,1328],{"class":191},"    \"",[165,1330,1331],{"class":220},"all-posts",[165,1333,224],{"class":191},[165,1335,201],{"class":191},[165,1337,217],{"class":191},[165,1339,1340],{"class":220},"All posts",[165,1342,224],{"class":191},[165,1344,248],{"class":191},[165,1346,1347],{"class":167,"line":262},[165,1348,1349],{"class":191},"  },\n",[165,1351,1352,1355,1357],{"class":167,"line":269},[165,1353,1354],{"class":187},"  pt",[165,1356,201],{"class":191},[165,1358,921],{"class":191},[165,1360,1361,1363,1365,1367,1369,1371,1374,1376],{"class":167,"line":422},[165,1362,1328],{"class":191},[165,1364,1331],{"class":220},[165,1366,224],{"class":191},[165,1368,201],{"class":191},[165,1370,217],{"class":191},[165,1372,1373],{"class":220},"Todos os posts",[165,1375,224],{"class":191},[165,1377,248],{"class":191},[165,1379,1380],{"class":167,"line":427},[165,1381,1349],{"class":191},[165,1383,1384],{"class":167,"line":449},[165,1385,1292],{"class":191},[165,1387,1388],{"class":167,"line":458},[165,1389,266],{"emptyLinePlaceholder":265},[165,1391,1392,1394,1397,1400,1402,1405,1408,1410],{"class":167,"line":486},[165,1393,273],{"class":272},[165,1395,1396],{"class":171}," type",[165,1398,1399],{"class":183}," Locales",[165,1401,180],{"class":179},[165,1403,1404],{"class":179}," keyof",[165,1406,1407],{"class":179}," typeof",[165,1409,1310],{"class":175},[165,1411,259],{"class":191},[165,1413,1414,1416,1418,1421,1423,1426,1428,1431,1433,1436,1438,1441,1444,1447],{"class":167,"line":496},[165,1415,273],{"class":272},[165,1417,276],{"class":171},[165,1419,1420],{"class":175}," locales",[165,1422,180],{"class":179},[165,1424,1425],{"class":175}," Object",[165,1427,399],{"class":191},[165,1429,1430],{"class":183},"keys",[165,1432,188],{"class":187},[165,1434,1435],{"class":175},"entries",[165,1437,390],{"class":187},[165,1439,1440],{"class":272},"as",[165,1442,1443],{"class":183}," Locale",[165,1445,1446],{"class":187},"[]",[165,1448,259],{"class":191},[165,1450,1451,1453,1455,1458,1460,1463,1465,1467,1469,1471,1474,1476],{"class":167,"line":545},[165,1452,273],{"class":272},[165,1454,276],{"class":171},[165,1456,1457],{"class":175}," defaultLocale",[165,1459,201],{"class":179},[165,1461,1462],{"class":183}," Locale ",[165,1464,507],{"class":179},[165,1466,217],{"class":191},[165,1468,1255],{"class":220},[165,1470,224],{"class":191},[165,1472,1473],{"class":272}," as",[165,1475,276],{"class":171},[165,1477,259],{"class":191},[165,1479,1480],{"class":167,"line":555},[165,1481,266],{"emptyLinePlaceholder":265},[165,1483,1484,1486,1488,1490,1492,1495,1497,1499,1501,1503,1505],{"class":167,"line":564},[165,1485,328],{"class":272},[165,1487,284],{"class":191},[165,1489,333],{"class":175},[165,1491,1473],{"class":272},[165,1493,1494],{"class":175}," getContentCollection",[165,1496,242],{"class":191},[165,1498,338],{"class":272},[165,1500,217],{"class":191},[165,1502,343],{"class":220},[165,1504,224],{"class":191},[165,1506,259],{"class":191},[165,1508,1509],{"class":167,"line":1083},[165,1510,266],{"emptyLinePlaceholder":265},[165,1512,1514,1516,1518,1520,1522,1525,1527,1529,1531,1533,1535],{"class":167,"line":1513},16,[165,1515,273],{"class":272},[165,1517,912],{"class":171},[165,1519,333],{"class":183},[165,1521,188],{"class":191},[165,1523,1524],{"class":175},"locale",[165,1526,201],{"class":179},[165,1528,1462],{"class":183},[165,1530,507],{"class":179},[165,1532,1457],{"class":175},[165,1534,245],{"class":191},[165,1536,921],{"class":191},[165,1538,1540,1543,1545,1547,1550,1553,1555,1557,1559,1561],{"class":167,"line":1539},17,[165,1541,1542],{"class":272},"    return",[165,1544,1494],{"class":183},[165,1546,188],{"class":187},[165,1548,1549],{"class":191},"`",[165,1551,1552],{"class":220},"blog-",[165,1554,1184],{"class":191},[165,1556,1524],{"class":175},[165,1558,1189],{"class":191},[165,1560,245],{"class":187},[165,1562,259],{"class":191},[165,1564,1566],{"class":167,"line":1565},18,[165,1567,561],{"class":191},[10,1569,1570],{},"A arquitetura das páginas ficou assim:",[116,1572,1575],{"className":1573,"code":1574,"language":121},[119],"src/\n├── content.config.ts\n├── i18n.ts\n├── pages/\n│   ├── [lang]/\n│   │   ├── [slug].astro\n│   │   └── index.astro\n│   ├── [slug].astro\n└── └── index.astro\n",[51,1576,1574],{"__ignoreMap":124},[10,1578,1579,1580,399],{},"E todo lugar que eu precisasse do idioma selecionado, foi só colocar ",[51,1581,1582],{},"const { lang = defaultLocale } = Astro.params",[10,1584,1585,1586,1589],{},"Daí em diante, tudo funcionou perfeitamente bem. Eu criei um componente que coloca o sufixo do idioma no URL, e assim eu podia trocar de idiomas. Tudo funcionou perfeitamente e, até o momento, ",[25,1587,1588],{},"zero"," Javascript!",[108,1591,1593],{"id":1592},"onde-as-coisas-começaram-a-desandar","Onde as coisas começaram a desandar...",[10,1595,1596,1597,1600,1601,1604,1605,1608,1609,1612,1613,1616],{},"No Astro, o markdown é renderizado para um HTML. A renderização é bem básica: links viram ",[51,1598,1599],{},"\u003Ca>",", headings viram ",[51,1602,1603],{},"\u003Ch1>",", ",[51,1606,1607],{},"\u003Ch2>","... Tudo normal. Acontece que eu queria uma pequena customização: se você quiser testar neste post, todo título é clicável. Eu deixei assim pois, caso alguém queira compartilhar apenas um tópico específico do post, é possível. Para isso, eu criei um componente de ",[51,1610,1611],{},"Prose"," no Nuxt, que me permite dar um ",[25,1614,1615],{},"override"," nas tags geradas com o markdown.",[10,1618,1619],{},"No Astro, entretanto, isso não é possível. Não de uma maneira simples. Eu tinha duas escolhas:",[1621,1622,1623,1630],"ol",{},[98,1624,1625,1626,1629],{},"Usar ",[51,1627,1628],{},"MDX"," para escrever os posts no lugar do markdown e chamar os componentes conforme necessários, ou;",[98,1631,1632],{},"Usar um plugin do rehype para alterar as tags no momento de renderização.",[10,1634,1635,1636,1639],{},"Como não gosto de ",[51,1637,1638],{},"mdx",", eu fui com a segunda opção. Acontece que isso gerou um código muito feio, pouco funcional (bugava muito) e que eu não ia entender poucos dias depois. Mas, tudo bem, eu o fiz ainda assim.",[10,1641,1642],{},"E pronto, após adicionar o  css, eu tinha uma réplica exata do meu blog, 100% funcional.",[55,1644,1646],{"id":1645},"veredito-e-comparação","Veredito e comparação",[10,1648,1649],{},"Bom, o blog inteiro não ficou imune de Javascript. É possível pesquisar por posts e tem como alternar entre tema claro e escuro. Eu buildei o site e quis ver o quão pesado ele estava.",[1651,1652,1653],"blockquote",{},[10,1654,1655,1656,1659,1660,1665],{},"As métricas estão na medida ",[51,1657,1658],{},"\u003Ctamanho_comprimido>/\u003Ctamanho_real>",". O site sempre será baixado comprimido e é descomprimido localmente. Você pode acessar o post de referência ",[14,1661,1664],{"href":1662,"rel":1663},"https://blog.marciosobel.dev/switching-to-nixos/",[18],"aqui",".  Todos os caches foram desabilitados nas medidas.",[10,1667,1668,1669,1671,1672,1675,1676,399],{},"Meu blog, com ",[51,1670,45],{},", a listagem de todos os posts utiliza ",[846,1673,1674],{},"228 kB / 569 kB",". Acessar um post utiliza ",[846,1677,1678],{},"351 kB / 657 kB",[10,1680,1681,1682,1684,1685,1675,1688],{},"A réplica, com ",[51,1683,19],{},", na tela de listagem, utiliza ",[846,1686,1687],{},"164 kB / 166 kB",[846,1689,1690],{},"257 kB / 260 kB",[10,1692,1693],{},"É possível notar que o Astro usa 28% menos rede que o Nuxt na listagem, e 27% menos ao visualizar um post, sendo a sua maior diferença nos dados descomprimidos, possuindo uma diferença de 71% na listagem e 60% ao visualizar um post!",[55,1695,1697],{"id":1696},"conclusão-com-qual-framework-vou-ficar","Conclusão: com qual framework vou ficar?",[10,1699,1700],{},"A diferença no bundle final é certamente notável. O Astro entrega um site muito mais leve, apenas com o conteúdo que você escolhe entregar. Mas, apesar disso, eu decidi me manter com o Nuxt. Eis o motivo: eu gosto de mexer nas coisas de lá pra cá. Achei o Astro para este meu caso de uso pouco maleável, especialmente na parte de modificar o output do markdown. E, apesar dele ser bem mais leve, é apenas no conteúdo não comprimido; na parte de rede, a diferença não é tão gritante assim,. Os celulares e computadores hoje nem se esforçam direito para compensar essa diferença. Então aceito fazer esse sacrifício em troca de uma DX melhor.",[10,1702,1703],{},"Todavia, o Astro com certeza me conquistou com seu charme e simplicidade. Com certeza vou utilizá-lo para gerar documentação de possíveis libs minhas no futuro. Eu não cheguei a explorar todo o potencial dele e, se eu estivesse começando do zero, eu provavelmente pegaria um template e construiria a partir dele.",[10,1705,1706],{},"E esta foi minha review do Astro, espero que tenham tido uma boa leitura. Até a próxima!",[1708,1709,1710],"style",{},"html pre.shiki code .s5BFC, html code.shiki .s5BFC{--shiki-default:#AF3A03;--shiki-dark:#FE8019}html pre.shiki code .selZM, html code.shiki .selZM{--shiki-default:#076678;--shiki-dark:#83A598}html pre.shiki code .sJcFL, html code.shiki .sJcFL{--shiki-default:#427B58;--shiki-dark:#8EC07C}html pre.shiki code .s9LfA, html code.shiki .s9LfA{--shiki-default:#B57614;--shiki-dark:#FABD2F}html pre.shiki code .s7kJP, html code.shiki .s7kJP{--shiki-default:#3C3836;--shiki-dark:#EBDBB2}html pre.shiki code .sLjwx, html code.shiki .sLjwx{--shiki-default:#7C6F64;--shiki-dark:#A89984}html pre.shiki code .sg8ZQ, html code.shiki .sg8ZQ{--shiki-default:#79740E;--shiki-dark:#B8BB26}html pre.shiki code .sn-iv, html code.shiki .sn-iv{--shiki-default:#9D0006;--shiki-dark:#FB4934}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);}html pre.shiki code .sFEbH, html code.shiki .sFEbH{--shiki-default:#928374;--shiki-default-font-style:italic;--shiki-dark:#928374;--shiki-dark-font-style:italic}",{"title":124,"searchDepth":195,"depth":195,"links":1712},[1713,1714,1721,1722],{"id":57,"depth":195,"text":58},{"id":89,"depth":195,"text":90,"children":1715},[1716,1717,1718,1719,1720],{"id":110,"depth":251,"text":111},{"id":144,"depth":251,"text":145},{"id":587,"depth":251,"text":588},{"id":1112,"depth":251,"text":1113},{"id":1592,"depth":251,"text":1593},{"id":1645,"depth":195,"text":1646},{"id":1696,"depth":195,"text":1697},"Se você está querendo iniciar um blog, provavelmente vai se deparar com essas duas opções tão populares. Neste post, vou dissertar sobre os pontos positivos e negativos de cada um.","md",{"date":1726,"updated":1727},"2026-06-17",null,"/blog/astro-vs-nuxt/pt","---\ntitle: \"Astro vs. Nuxt: Qual você deveria usar para o seu blog?\"\ndescription: Se você está querendo iniciar um blog, provavelmente vai se deparar com essas duas opções tão populares. Neste post, vou dissertar sobre os pontos positivos e negativos de cada um.\ndate: 2026-06-17\nupdated:\n---\nOlá! Nesses últimos dias, eu dei uma olhada no [Astro](https://astro.build/), uma framework para websites que tem como seu foco principal o conteúdo; ou seja, ela foi feita pensando diretamente em artigos, documentações e blogs.\n\nO maior benefício dela é dado pelo Javascript *mínimo*. Isso me chamou muita atenção. Eu comentei [no meu primeiro post](https://blog.marciosobel.dev/pt/my-first-blog-post/) que havia pensado bastante entre usar o Astro ou [Hugo](https://gohugo.io/) para o meu blog, mas acabei escolhendo o [Nuxt](https://nuxt.com/) por ser uma framework feta para construir websites do zero, o que me daria mais flexibilidade para moldar o site para ficar no meu estilo.\n\nEntretanto, decidi testar o Astro, e ver o que seria dele para mim. Quem sabe eu não o uso no lugar do `Nuxt` nesse blog?\n## Por que Astro?\nLogo de cara, o que mais me chamou atenção no Astro foi seu foco em conteúdo estático. Isso significa que ele não foi feito para ter muita interatividade: botões, requisições, formulários... Ele foi feito para coisas que mudam *muito pouco*, como documentação, artigos, *landing pages* e blogs.\n\nEle possui suporte *first-level* para Markdown, linguagem que uso para escrever os meus posts. Também, ele evita ao máximo enviar código Javascript para o usuário; isso significa que os sites sempre são super leves.\n\nBom, eu decidi testar o Astro pois eu fiquei com uma pulga atrás da orelha em relação ao `Nuxt`: ele leva consigo o runtime de Javascript do `Vue` junto. Apesar de ser pouca coisa, eu queria que o meu blog fosse o mais simples e leve possível. E assim começou minha jornada!\n## Recriando o meu blog com Astro\nVamos lá, primeiro vamos ver o que eu quero conseguir aqui.\n- Escrever meus posts em Markdown e não me preocupar com como eles vão parar no site; quero apenas escrever, subir na nuvem e o build do Astro deve atualizar a listagem e gerar os links de acordo;\n- Ter o site em dois ou mais idiomas.\n\nÉ, o blog é bem simples; mas, acredito que, com essas exigências, vai dar para sujar bem as mãos na framework.\n### Dando início ao projeto\nEssa é a parte mais fácil e a mais direta de toda essa jornada. Só precisei rodar:\n```\npnpm create astro@latest\n```\nE seguir o passo a passo do robôzinho cuti cuti.\n\nA arquitetura é bem simples, rotas baseadas por arquivos... Tudo super intuitivo. Eu não usei nenhum template, então para mim só tinha um `index.astro`, com a tela toda em branco, apenas com um `h1` escrito \"Astro\".\n\nBom, vamos começar a riscar alguns itens da lista.\n### Listando todos os posts\nComecei criando uma pasta `posts/`, e coloquei os posts do blog nela. Como o Astro já foi feito pensado em lidar com posts, então foi bem fácil. Basta criar um arquivo `content.config.ts` com o seguinte código:\n```typescript\nconst blog = defineCollection({\n  loader: glob({ base: \"posts\", pattern: \"**/*.{md,mdx}\" }),\n});\n\nexport const collections = { blog };\n```\nIsso foi bem fácil. Eu usei o `zod` para criar um schema para os *frontmatters*, os metadados, dos posts.\n\nEntão, no arquivo `index.astro`, o *entrypoint* do website, eu coloquei o seguinte:\n```astro\n---\nimport { getCollection } from \"astro:content\";\nconst posts = await getCollection(\"blog\");\nif (!posts) return Astro.redirect(\"/404\");\n---\n\n\u003Ch1>Blog\u003C/h1>\n\u003Cul>\n  {posts.map((post) => (\n    \u003Cli>\n      \u003Ca href={post.id}>{post.data.title}\u003C/a>\n    \u003C/li>\n  ))}\n\u003C/ul>\n```\nEeeee isso funcionou? Foi muito fácil!\n\nNo Astro, qualquer código executado entre as \"fences\" (`---`) é executado apenas em *build time*. Isso significa que nossa aplicação até o momento está com um total de zero  Javascript!\n### Gerando páginas dinamicamente\nApós a criação da página de listagem de posts, eu queria criar uma página para cada post. Para isso, eu precisava de uma *rota dinâmica*, ou seja, precisaria que o Astro gerasse as páginas para mim.\n\nGostaria de aproveitar este momento e dizer que a documentação do Astro é muito boa! Definitivamente seria muito mais trabalhoso fazer isso sem ela.\n\nEnfim, eu criei um arquivo `[slug].astro`. Este arquivo aceita qualquer valor como entrada, e eu posso usar esse valor via código.\n```astro\n---\nconst { slug } = Astro.params;\n---\n\u003Cp>Slug: {slug}\u003C/p>\n```\nEntão usei isso para pegar o ID de um post. Felizmente, o Astro já possui uma função específica para pegar um item de uma coleção: `getEntry`. Ela recebe a coleção e o ID para buscar. Após isso, precisava saber como mostrar o conteúdo `markdown` em `HTML`. Então me deparo com a função `render`, que útil!\n```astro\n---\nimport { getEntry, render } from \"astro:content\";\nconst { slug } = Astro.params;\nconst post = await getEntry(\"blog\", slug);\nif (!post) return Astro.redirect(\"/404\");\nconst { Content } = await render(post);\n---\n\u003CContent />\n```\nE em teoria era para funcionar super bem... Mas o Astro não consegue saber todas as rotas possíveis para o `[slug].astro`, o que o impedia de deixar o site limpo de javascript. Para contornar isso, temos que dizer ao Astro **todas as rotas possíveis**. Fazemos isso exportando uma função `getStaticPaths`, que *felizmente* pode ser `async`, onde retornamos uma lista com todas as variáveis completadas. Também podemos passar props para cada página, o que significa que podemos passar todos os posts diretamente!\n```astro\n---\nimport { getCollection, render } from \"astro:content\"; // agora, pegamos a coleção inteira!\n\nexport async function getStaticPaths() {\n  const posts = await getCollection(\"blog\");\n  return posts.map((post) => ({\n    params: { slug: post.id },\n    props: { post }, // podemos passar o post diretamente para a página!\n  }));\n}\n\nconst { post } = Astro.props; // E o post é garantido que existe!\nconst { Content } = await render(post);\n---\n\u003CContent />\n```\nOkay! Isso foi bem fácil.\n\nTestando os diferentes posts, vi que um deles estava renderizando os acentos errado. Por exemplo, ao invés de da palavra `água`, o HTML renderizado retornava `Ã¡gua`. Fiquei confuso do motivo, e aparentemente o Astro não define mais o charset do documento como UTF-8 por padrão. Fiquei bem confuso pois [só vi isso sendo citado em um lugar das docs](https://docs.astro.build/en/guides/markdown-content/#frontmatter-layout-property)? Mas tudo bem, perdi um tempinho nessa parte.\n### Lidando com diferentes idiomas\nCaso você nunca tenha notado, este blog está disponível tanto em Português do Brasil, quanto em Inglês. Até o momento em que escrevo este post, todos os posts estão disponíveis nos dois idiomas. Não sei se em algum momento eles vão divergir e ter conteúdos diferentes.\n\nEnfim, ter múltiplos idiomas é algo necessário. Como o bom programador que sou™, quis deixar dinâmico o suficiente para ter a menor dor de cabeça possível caso eu queira escrever em algum outro idioma. Primeiro, alterei o arquivo `content.config.ts` para ter os posts separados:\n```typescript\nfunction createCollection(suffix: string) {\n  return defineCollection({\n    loader: glob({ base: `posts/${suffix}`, pattern: \"**/*.{md,mdx}\" });\n  });\n}\n\nexport const collections = {\n  \"blog-en\": createCollection(\"en\"),\n  \"blog-pt\": createCollection(\"pt\"),\n};\n```\nE então criei um arquivo `i18n.ts` com o seguinte código:\n```typescript\nconst entries = {\n  en: {\n    \"all-posts\": \"All posts\",\n  },\n  pt: {\n    \"all-posts\": \"Todos os posts\",\n  },\n};\n\nexport type Locales = keyof typeof entries;\nexport const locales = Object.keys(entries) as Locale[];\nexport const defaultLocale: Locale = \"en\" as const;\n\nimport { getCollection as getContentCollection } from \"astro:content\";\n\nexport function getCollection(locale: Locale = defaultLocale) {\n\treturn getContentCollection(`blog-${locale}`);\n}\n```\nA arquitetura das páginas ficou assim:\n```\nsrc/\n├── content.config.ts\n├── i18n.ts\n├── pages/\n│   ├── [lang]/\n│   │   ├── [slug].astro\n│   │   └── index.astro\n│   ├── [slug].astro\n└── └── index.astro\n```\nE todo lugar que eu precisasse do idioma selecionado, foi só colocar `const { lang = defaultLocale } = Astro.params`.\n\nDaí em diante, tudo funcionou perfeitamente bem. Eu criei um componente que coloca o sufixo do idioma no URL, e assim eu podia trocar de idiomas. Tudo funcionou perfeitamente e, até o momento, *zero* Javascript!\n### Onde as coisas começaram a desandar...\nNo Astro, o markdown é renderizado para um HTML. A renderização é bem básica: links viram `\u003Ca>`, headings viram `\u003Ch1>`, `\u003Ch2>`... Tudo normal. Acontece que eu queria uma pequena customização: se você quiser testar neste post, todo título é clicável. Eu deixei assim pois, caso alguém queira compartilhar apenas um tópico específico do post, é possível. Para isso, eu criei um componente de `Prose` no Nuxt, que me permite dar um *override* nas tags geradas com o markdown.\n\nNo Astro, entretanto, isso não é possível. Não de uma maneira simples. Eu tinha duas escolhas:\n1. Usar `MDX` para escrever os posts no lugar do markdown e chamar os componentes conforme necessários, ou;\n2. Usar um plugin do rehype para alterar as tags no momento de renderização.\n\nComo não gosto de `mdx`, eu fui com a segunda opção. Acontece que isso gerou um código muito feio, pouco funcional (bugava muito) e que eu não ia entender poucos dias depois. Mas, tudo bem, eu o fiz ainda assim.\n\nE pronto, após adicionar o  css, eu tinha uma réplica exata do meu blog, 100% funcional.\n## Veredito e comparação\nBom, o blog inteiro não ficou imune de Javascript. É possível pesquisar por posts e tem como alternar entre tema claro e escuro. Eu buildei o site e quis ver o quão pesado ele estava.\n\n> As métricas estão na medida `\u003Ctamanho_comprimido>/\u003Ctamanho_real>`. O site sempre será baixado comprimido e é descomprimido localmente. Você pode acessar o post de referência [aqui](https://blog.marciosobel.dev/switching-to-nixos/).  Todos os caches foram desabilitados nas medidas.\n\nMeu blog, com `Nuxt`, a listagem de todos os posts utiliza **228 kB / 569 kB**. Acessar um post utiliza **351 kB / 657 kB**.\n\nA réplica, com `Astro`, na tela de listagem, utiliza **164 kB / 166 kB**. Acessar um post utiliza **257 kB / 260 kB**\n\nÉ possível notar que o Astro usa 28% menos rede que o Nuxt na listagem, e 27% menos ao visualizar um post, sendo a sua maior diferença nos dados descomprimidos, possuindo uma diferença de 71% na listagem e 60% ao visualizar um post!\n## Conclusão: com qual framework vou ficar?\nA diferença no bundle final é certamente notável. O Astro entrega um site muito mais leve, apenas com o conteúdo que você escolhe entregar. Mas, apesar disso, eu decidi me manter com o Nuxt. Eis o motivo: eu gosto de mexer nas coisas de lá pra cá. Achei o Astro para este meu caso de uso pouco maleável, especialmente na parte de modificar o output do markdown. E, apesar dele ser bem mais leve, é apenas no conteúdo não comprimido; na parte de rede, a diferença não é tão gritante assim,. Os celulares e computadores hoje nem se esforçam direito para compensar essa diferença. Então aceito fazer esse sacrifício em troca de uma DX melhor.\n\nTodavia, o Astro com certeza me conquistou com seu charme e simplicidade. Com certeza vou utilizá-lo para gerar documentação de possíveis libs minhas no futuro. Eu não cheguei a explorar todo o potencial dele e, se eu estivesse começando do zero, eu provavelmente pegaria um template e construiria a partir dele.\n\nE esta foi minha review do Astro, espero que tenham tido uma boa leitura. Até a próxima!",{"title":5,"description":1723},"blog/astro-vs-nuxt/pt","5CFnAOge6GEPtiohjB6oD8772NOUVqamN2UgLug5LM8",{"iso":1734,"formatted":1735},"2026-06-17T00:00:00.000Z","17 de junho de 2026",{"iso":-1,"formatted":-1},1781699324017]