[{"data":1,"prerenderedAt":1289},["ShallowReactive",2],{"blog-nuxt-wordpress-headless-nuxt":3},{"id":4,"title":5,"author":6,"body":7,"date":1258,"description":1259,"extension":1260,"faq":1261,"image":1274,"meta":1275,"navigation":649,"path":1276,"seo":1277,"stem":1278,"tags":1279,"updated":1287,"__hash__":1288},"blog\u002Fblog\u002Fnuxt-wordpress-headless-nuxt.md","WordPress Headless com Nuxt: quando faz sentido e como estruturar","Larissa Santos",{"type":8,"value":9,"toc":1244},"minimark",[10,14,17,20,23,28,31,47,50,57,59,63,66,69,72,74,78,81,89,159,166,168,172,175,178,185,268,271,277,279,283,286,292,300,303,583,598,600,604,607,864,871,906,909,916,918,922,928,969,980,983,1013,1016,1022,1025,1031,1033,1037,1123,1125,1129,1132,1135,1138,1140,1144,1147,1150,1153,1156,1171,1174,1177,1179,1183,1186,1189,1192,1195,1197,1201,1240],[11,12,13],"p",{},"No início do ano conheci uma amiga que tem um projeto de grande relevância social, mas que ainda estava estruturando seu negócio. Ela tinha um website feito em WordPress que virava bagunça toda vez que ela mexia num texto, pois as edições envolviam, além do conteúdo, posicionar os itens na tela. Cada edição quebrava o visual. Também reparei que a versão mobile do site estava muito prejudicada por esses motivos. E não faz sentido que ela precise lidar com essa complexidade: o trabalho dela é tocar o projeto, não brigar com a ferramenta a cada ajuste de texto.",[11,15,16],{},"A primeira ideia que me veio foi só trocar o tema. Seria mais rápido e até resolveria alguma coisa no começo, mas descartei logo: o tema não era o problema, era a arquitetura. Trocar o tema só empurraria a bagunça pra frente.",[11,18,19],{},"O que eu queria mesmo era facilitar a vida dela: um cadastro de conteúdo mais direto, e o layout deixando de ser responsabilidade dela para virar responsabilidade da aplicação.",[21,22],"hr",{},[24,25,27],"h2",{"id":26},"quando-wordpress-headless-faz-sentido","Quando WordPress Headless faz sentido?",[11,29,30],{},"As questões que analisei:",[32,33,34,38,41,44],"ul",{},[35,36,37],"li",{},"A cliente já usa WordPress, tem seu gerenciamento de arquivos por lá e já tem uma certa familiaridade com o painel",[35,39,40],{},"O layout precisa ser controlado completamente pelo front-end, sem risco de quebra por edição de conteúdo",[35,42,43],{},"SEO\u002FGEO é prioridade",[35,45,46],{},"Liberdade total de design sem depender de temas ou construtores",[11,48,49],{},"No meu caso, todos os pontos se aplicavam. A decisão foi manter o WordPress como back-end de gerenciamento e construir a camada de apresentação nova e performática com Nuxt.",[11,51,52,56],{},[53,54,55],"strong",{},"O WordPress já expõe uma REST API completa por padrão."," Sem plugin especial, sem configuração complexa. Isso foi uma surpresa positiva quando pesquisei pela primeira vez sobre Headless.",[21,58],{},[24,60,62],{"id":61},"por-que-nuxt-e-não-outra-solução","Por que Nuxt e não outra solução?",[11,64,65],{},"Tenho mais de 5 anos de experiência com Vue e seu ecossistema, domino Quasar em diversos projetos profissionais. Mas Quasar é voltado para SPAs, onde a página chega vazia e o conteúdo é montado no navegador via JavaScript. Para uma aplicação interna isso é ótimo; para um site institucional que vive de ser encontrado, é péssimo, porque o buscador recebe uma página em branco.",[11,67,68],{},"Poderia ter partido para React com Next.js, que resolve o mesmo problema, mas trocar de ecossistema custaria tempo de adaptação sem nenhum ganho real para o projeto. O Nuxt me dá o SSR que eu preciso sem sair do Vue, então todo o conhecimento acumulado continua valendo e a entrega acelera.",[11,70,71],{},"E o SSR faz diferença justamente pelo motivo que originou o projeto: SEO. Com a renderização no servidor, os dados que vêm do WordPress já chegam dentro do HTML. A página abre com o conteúdo presente, e tanto o Google quanto as IAs generativas conseguem ler e indexar sem depender de executar JavaScript. Para um site cujo objetivo é divulgar uma causa e alcançar mais pessoas, ser indexável não é detalhe: é o ponto.",[21,73],{},[24,75,77],{"id":76},"variáveis-de-ambiente","Variáveis de ambiente",[11,79,80],{},"Decidida a arquitetura, começa a parte de estruturar o projeto. E a primeira coisa que precisei resolver foi a mais básica de todas: o Nuxt precisa saber onde encontrar o WordPress.",[11,82,83,84,88],{},"Em vez de espalhar a URL pelo código, coloquei ela no ",[85,86,87],"code",{},"runtimeConfig",", acessível em toda a aplicação e fácil de trocar depois:",[90,91,96],"pre",{"className":92,"code":93,"language":94,"meta":95,"style":95},"language-ts shiki shiki-themes material-theme-lighter github-dark github-dark","runtimeConfig: {\n  public: {\n    wordpressUrl: process.env.NUXT_PUBLIC_WORDPRESS_URL\n  }\n}\n","ts","",[85,97,98,113,123,147,153],{"__ignoreMap":95},[99,100,103,106,110],"span",{"class":101,"line":102},"line",1,[99,104,87],{"class":105},"soiBB",[99,107,109],{"class":108},"sG-J9",":",[99,111,112],{"class":108}," {\n",[99,114,116,119,121],{"class":101,"line":115},2,[99,117,118],{"class":105},"  public",[99,120,109],{"class":108},[99,122,112],{"class":108},[99,124,126,129,131,135,138,141,143],{"class":101,"line":125},3,[99,127,128],{"class":105},"    wordpressUrl",[99,130,109],{"class":108},[99,132,134],{"class":133},"sMo7A"," process",[99,136,137],{"class":108},".",[99,139,140],{"class":133},"env",[99,142,137],{"class":108},[99,144,146],{"class":145},"sVPC0","NUXT_PUBLIC_WORDPRESS_URL\n",[99,148,150],{"class":101,"line":149},4,[99,151,152],{"class":108},"  }\n",[99,154,156],{"class":101,"line":155},5,[99,157,158],{"class":108},"}\n",[11,160,161,162,165],{},"Localmente a URL fica no ",[85,163,164],{},".env",", e em produção a mesma variável é configurada no painel da Vercel, fora do repositório. Assim a URL não fica cravada no código: se o WordPress mudar de endereço um dia, é só trocar o valor num lugar, sem mexer em nenhuma linha.",[21,167],{},[24,169,171],{"id":170},"campos-estruturados-com-acf","Campos estruturados com ACF",[11,173,174],{},"Por padrão, o WordPress devolve o conteúdo como HTML já formatado pelo editor. Isso engessa o front-end: em vez de dados, você recebe um bloco de markup com as tags e os estilos que o editor escolheu, e sobra pouca liberdade para apresentar do seu jeito.",[11,176,177],{},"O ACF (Advanced Custom Fields) resolve isso. Em vez de consumir HTML pronto, eu modelo os campos que quero e recebo dados limpos, campo a campo, com liberdade total de apresentação no Nuxt:",[11,179,180],{},[181,182],"img",{"alt":183,"src":184},"Grupo de campos no ACF, com os campos \"historia_titulo\" (tipo Texto) e \"historia_descricao\" (tipo Editor WYSIWYG)","\u002Fimages\u002Fblog\u002Facf\u002FACF-Campos.png",[90,186,190],{"className":187,"code":188,"language":189,"meta":95,"style":95},"language-json shiki shiki-themes material-theme-lighter github-dark github-dark","{\n  \"acf\": {\n    \"historia_titulo\": \"Do resíduo que ninguém via, nasceu um movimento que transforma\",\n    \"historia_descricao\": \"A Teciklar nasceu da observação de Isis Kátia...\"\n  }\n}\n","json",[85,191,192,197,214,240,259,263],{"__ignoreMap":95},[99,193,194],{"class":101,"line":102},[99,195,196],{"class":108},"{\n",[99,198,199,203,207,210,212],{"class":101,"line":115},[99,200,202],{"class":201},"swu5b","  \"",[99,204,206],{"class":205},"sod2m","acf",[99,208,209],{"class":201},"\"",[99,211,109],{"class":108},[99,213,112],{"class":108},[99,215,216,219,223,225,227,231,235,237],{"class":101,"line":125},[99,217,218],{"class":201},"    \"",[99,220,222],{"class":221},"s3afY","historia_titulo",[99,224,209],{"class":201},[99,226,109],{"class":108},[99,228,230],{"class":229},"sF_wb"," \"",[99,232,234],{"class":233},"s0vBq","Do resíduo que ninguém via, nasceu um movimento que transforma",[99,236,209],{"class":229},[99,238,239],{"class":108},",\n",[99,241,242,244,247,249,251,253,256],{"class":101,"line":149},[99,243,218],{"class":201},[99,245,246],{"class":221},"historia_descricao",[99,248,209],{"class":201},[99,250,109],{"class":108},[99,252,230],{"class":229},[99,254,255],{"class":233},"A Teciklar nasceu da observação de Isis Kátia...",[99,257,258],{"class":229},"\"\n",[99,260,261],{"class":101,"line":155},[99,262,152],{"class":108},[99,264,266],{"class":101,"line":265},6,[99,267,158],{"class":108},[11,269,270],{},"Um ponto de atenção na versão free: cada grupo de campos precisa ter a opção \"Show in REST API\" habilitada para que os dados apareçam no endpoint. É uma configuração por grupo, feita direto no painel do ACF.",[11,272,273],{},[181,274],{"alt":275,"src":276},"Configurações do grupo no ACF com a opção \"Mostrar na API REST\" ativada","\u002Fimages\u002Fblog\u002Facf\u002FACF-config.png",[21,278],{},[24,280,282],{"id":281},"convenção-de-prefixos-por-seção","Convenção de prefixos por seção",[11,284,285],{},"O ACF free não agrupa os campos hierarquicamente na REST API: com muitos campos numa mesma página, tudo volta junto num objeto plano. Para dar estrutura a isso sem depender da versão paga, adotei uma convenção de nomenclatura por prefixo de seção:",[11,287,288],{},[181,289],{"alt":290,"src":291},"Lista de grupos de campos no ACF, um por seção da home: Home – Historia e Home – Parceiros","\u002Fimages\u002Fblog\u002Facf\u002FACF-page.png",[90,293,298],{"className":294,"code":296,"language":297},[295],"language-text","historia_titulo\nhistoria_descricao\nparceiros_titulo\nparceiros_lista\n","text",[85,299,296],{"__ignoreMap":95},[11,301,302],{},"Com esse padrão de nomes, dá para criar um composable que extrai cada seção de forma isolada e tipada, filtrando os campos pelo prefixo:",[90,304,306],{"className":92,"code":305,"language":94,"meta":95,"style":95},"export function useAcfFields(fields: Record\u003Cstring, unknown>) {\n  function extractSection\u003CT = Record\u003Cstring, unknown>>(prefix: string): T {\n    return Object.entries(fields).reduce((acc, [key, value]) => {\n      if (key.startsWith(prefix)) {\n        acc[key.slice(prefix.length) as keyof typeof acc] = value as never\n      }\n      return acc\n    }, {} as T)\n  }\n  return { extractSection }\n}\n",[85,307,308,352,399,452,476,529,534,543,559,564,578],{"__ignoreMap":95},[99,309,310,314,318,322,325,329,332,335,338,341,344,347,350],{"class":101,"line":102},[99,311,313],{"class":312},"s3Er8","export",[99,315,317],{"class":316},"sFsEu"," function",[99,319,321],{"class":320},"sK_r7"," useAcfFields",[99,323,324],{"class":108},"(",[99,326,328],{"class":327},"sk1zL","fields",[99,330,109],{"class":331},"sFfmW",[99,333,334],{"class":105}," Record",[99,336,337],{"class":108},"\u003C",[99,339,340],{"class":221},"string",[99,342,343],{"class":108},",",[99,345,346],{"class":221}," unknown",[99,348,349],{"class":108},">)",[99,351,112],{"class":108},[99,353,354,357,360,362,365,368,370,372,374,376,378,381,384,386,389,392,394,397],{"class":101,"line":115},[99,355,356],{"class":316},"  function",[99,358,359],{"class":320}," extractSection",[99,361,337],{"class":108},[99,363,364],{"class":105},"T",[99,366,367],{"class":331}," =",[99,369,334],{"class":105},[99,371,337],{"class":108},[99,373,340],{"class":221},[99,375,343],{"class":108},[99,377,346],{"class":221},[99,379,380],{"class":108},">>(",[99,382,383],{"class":327},"prefix",[99,385,109],{"class":331},[99,387,388],{"class":221}," string",[99,390,391],{"class":108},")",[99,393,109],{"class":331},[99,395,396],{"class":105}," T",[99,398,112],{"class":108},[99,400,401,404,407,409,412,415,417,419,421,424,426,428,431,433,436,439,441,444,447,450],{"class":101,"line":125},[99,402,403],{"class":312},"    return",[99,405,406],{"class":133}," Object",[99,408,137],{"class":108},[99,410,411],{"class":320},"entries",[99,413,324],{"class":414},"sdv8B",[99,416,328],{"class":133},[99,418,391],{"class":414},[99,420,137],{"class":108},[99,422,423],{"class":320},"reduce",[99,425,324],{"class":414},[99,427,324],{"class":108},[99,429,430],{"class":327},"acc",[99,432,343],{"class":108},[99,434,435],{"class":108}," [",[99,437,438],{"class":327},"key",[99,440,343],{"class":108},[99,442,443],{"class":327}," value",[99,445,446],{"class":108},"])",[99,448,449],{"class":316}," =>",[99,451,112],{"class":108},[99,453,454,457,460,462,464,467,469,471,474],{"class":101,"line":149},[99,455,456],{"class":312},"      if",[99,458,459],{"class":414}," (",[99,461,438],{"class":133},[99,463,137],{"class":108},[99,465,466],{"class":320},"startsWith",[99,468,324],{"class":414},[99,470,383],{"class":133},[99,472,473],{"class":414},")) ",[99,475,196],{"class":108},[99,477,478,481,484,486,488,491,493,495,497,500,503,506,509,512,515,518,521,523,526],{"class":101,"line":155},[99,479,480],{"class":133},"        acc",[99,482,483],{"class":414},"[",[99,485,438],{"class":133},[99,487,137],{"class":108},[99,489,490],{"class":320},"slice",[99,492,324],{"class":414},[99,494,383],{"class":133},[99,496,137],{"class":108},[99,498,499],{"class":145},"length",[99,501,502],{"class":414},") ",[99,504,505],{"class":312},"as",[99,507,508],{"class":331}," keyof",[99,510,511],{"class":331}," typeof",[99,513,514],{"class":133}," acc",[99,516,517],{"class":414},"] ",[99,519,520],{"class":331},"=",[99,522,443],{"class":133},[99,524,525],{"class":312}," as",[99,527,528],{"class":221}," never\n",[99,530,531],{"class":101,"line":265},[99,532,533],{"class":108},"      }\n",[99,535,537,540],{"class":101,"line":536},7,[99,538,539],{"class":312},"      return",[99,541,542],{"class":133}," acc\n",[99,544,546,549,552,554,556],{"class":101,"line":545},8,[99,547,548],{"class":108},"    },",[99,550,551],{"class":108}," {}",[99,553,525],{"class":312},[99,555,396],{"class":105},[99,557,558],{"class":414},")\n",[99,560,562],{"class":101,"line":561},9,[99,563,152],{"class":108},[99,565,567,570,573,575],{"class":101,"line":566},10,[99,568,569],{"class":312},"  return",[99,571,572],{"class":108}," {",[99,574,359],{"class":133},[99,576,577],{"class":108}," }\n",[99,579,581],{"class":101,"line":580},11,[99,582,158],{"class":108},[11,584,585,586,589,590,593,594,589,596,137],{},"Cada seção chega tipada e com as chaves limpas, já sem o prefixo. No componente eu trabalho com ",[85,587,588],{},"titulo"," e ",[85,591,592],{},"descricao",", não com ",[85,595,222],{},[85,597,246],{},[21,599],{},[24,601,603],{"id":602},"composable-por-página","Composable por página",[11,605,606],{},"Com o projeto crescendo, deixei a lógica de busca fora da página. Ela vive num composable dedicado, um por página, que centraliza a chamada à API e a extração das seções:",[90,608,610],{"className":92,"code":609,"language":94,"meta":95,"style":95},"export async function useHome() {\n  const config = useRuntimeConfig()\n\n  const { data } = await useFetch\u003CWordPressPage>(\n    `${config.public.wordpressUrl}\u002Fwp-json\u002Fwp\u002Fv2\u002Fpages\u002F75`\n  )\n\n  const { extractSection } = useAcfFields(data.value?.acf ?? {})\n\n  return {\n    historia: extractSection\u003CHistoriaSection>('historia_'),\n    hero: extractSection\u003CHeroSection>('hero_'),\n    impacto: extractSection\u003CImpactoSection>('impacto_'),\n  }\n}\n",[85,611,612,629,645,651,682,709,714,718,754,758,764,794,824,854,859],{"__ignoreMap":95},[99,613,614,616,619,621,624,627],{"class":101,"line":102},[99,615,313],{"class":312},[99,617,618],{"class":316}," async",[99,620,317],{"class":316},[99,622,623],{"class":320}," useHome",[99,625,626],{"class":108},"()",[99,628,112],{"class":108},[99,630,631,634,637,639,642],{"class":101,"line":115},[99,632,633],{"class":316},"  const",[99,635,636],{"class":145}," config",[99,638,367],{"class":331},[99,640,641],{"class":320}," useRuntimeConfig",[99,643,644],{"class":414},"()\n",[99,646,647],{"class":101,"line":125},[99,648,650],{"emptyLinePlaceholder":649},true,"\n",[99,652,653,655,657,660,663,665,668,671,673,676,679],{"class":101,"line":149},[99,654,633],{"class":316},[99,656,572],{"class":108},[99,658,659],{"class":145}," data",[99,661,662],{"class":108}," }",[99,664,367],{"class":331},[99,666,667],{"class":312}," await",[99,669,670],{"class":320}," useFetch",[99,672,337],{"class":108},[99,674,675],{"class":105},"WordPressPage",[99,677,678],{"class":108},">",[99,680,681],{"class":414},"(\n",[99,683,684,687,690,692,695,697,700,703,706],{"class":101,"line":155},[99,685,686],{"class":229},"    `${",[99,688,689],{"class":133},"config",[99,691,137],{"class":229},[99,693,694],{"class":133},"public",[99,696,137],{"class":229},[99,698,699],{"class":133},"wordpressUrl",[99,701,702],{"class":229},"}",[99,704,705],{"class":233},"\u002Fwp-json\u002Fwp\u002Fv2\u002Fpages\u002F75",[99,707,708],{"class":229},"`\n",[99,710,711],{"class":101,"line":265},[99,712,713],{"class":414},"  )\n",[99,715,716],{"class":101,"line":536},[99,717,650],{"emptyLinePlaceholder":649},[99,719,720,722,724,726,728,730,732,734,737,739,742,745,747,750,752],{"class":101,"line":545},[99,721,633],{"class":316},[99,723,572],{"class":108},[99,725,359],{"class":145},[99,727,662],{"class":108},[99,729,367],{"class":331},[99,731,321],{"class":320},[99,733,324],{"class":414},[99,735,736],{"class":133},"data",[99,738,137],{"class":108},[99,740,741],{"class":133},"value",[99,743,744],{"class":108},"?.",[99,746,206],{"class":133},[99,748,749],{"class":331}," ??",[99,751,551],{"class":108},[99,753,558],{"class":414},[99,755,756],{"class":101,"line":561},[99,757,650],{"emptyLinePlaceholder":649},[99,759,760,762],{"class":101,"line":566},[99,761,569],{"class":312},[99,763,112],{"class":108},[99,765,766,769,771,773,775,778,780,782,785,788,790,792],{"class":101,"line":580},[99,767,768],{"class":414},"    historia",[99,770,109],{"class":108},[99,772,359],{"class":320},[99,774,337],{"class":108},[99,776,777],{"class":105},"HistoriaSection",[99,779,678],{"class":108},[99,781,324],{"class":414},[99,783,784],{"class":229},"'",[99,786,787],{"class":233},"historia_",[99,789,784],{"class":229},[99,791,391],{"class":414},[99,793,239],{"class":108},[99,795,797,800,802,804,806,809,811,813,815,818,820,822],{"class":101,"line":796},12,[99,798,799],{"class":414},"    hero",[99,801,109],{"class":108},[99,803,359],{"class":320},[99,805,337],{"class":108},[99,807,808],{"class":105},"HeroSection",[99,810,678],{"class":108},[99,812,324],{"class":414},[99,814,784],{"class":229},[99,816,817],{"class":233},"hero_",[99,819,784],{"class":229},[99,821,391],{"class":414},[99,823,239],{"class":108},[99,825,827,830,832,834,836,839,841,843,845,848,850,852],{"class":101,"line":826},13,[99,828,829],{"class":414},"    impacto",[99,831,109],{"class":108},[99,833,359],{"class":320},[99,835,337],{"class":108},[99,837,838],{"class":105},"ImpactoSection",[99,840,678],{"class":108},[99,842,324],{"class":414},[99,844,784],{"class":229},[99,846,847],{"class":233},"impacto_",[99,849,784],{"class":229},[99,851,391],{"class":414},[99,853,239],{"class":108},[99,855,857],{"class":101,"line":856},14,[99,858,152],{"class":108},[99,860,862],{"class":101,"line":861},15,[99,863,158],{"class":108},[11,865,866,867,870],{},"Com isso, o ",[85,868,869],{},"index.vue"," fica reduzido à lógica de dados que realmente importa:",[90,872,874],{"className":92,"code":873,"language":94,"meta":95,"style":95},"const { historia, hero, impacto } = await useHome()\n",[85,875,876],{"__ignoreMap":95},[99,877,878,881,883,886,888,891,893,896,898,900,902,904],{"class":101,"line":102},[99,879,880],{"class":316},"const",[99,882,572],{"class":108},[99,884,885],{"class":145}," historia",[99,887,343],{"class":108},[99,889,890],{"class":145}," hero",[99,892,343],{"class":108},[99,894,895],{"class":145}," impacto",[99,897,662],{"class":108},[99,899,367],{"class":331},[99,901,667],{"class":312},[99,903,623],{"class":320},[99,905,644],{"class":133},[11,907,908],{},"Todo o resto (a URL, o ID da página, a extração por prefixo, a tipagem) fica encapsulado no composable. Se eu precisar mudar o endpoint ou a forma de tratar os dados, mexo num arquivo só, e a página nem fica sabendo.",[11,910,911,912,915],{},"Um ponto que vale entender bem aqui é o ",[85,913,914],{},"useFetch",": na primeira requisição ele executa no servidor. Isso evita o waterfall de chamadas no cliente e é justamente o que faz o SSR valer a pena, já que os dados chegam prontos junto com o HTML.",[21,917],{},[24,919,921],{"id":920},"wordpress-como-headless-de-verdade","WordPress como Headless de verdade",[11,923,924,925,109],{},"Mesmo consumindo só a API, o WordPress continua carregando temas, o editor de blocos e uma série de features que não fazem sentido num setup headless. Para desligar isso de forma global, sem depender de nenhum tema ativo, usei um must-use plugin em ",[85,926,927],{},"wp-content\u002Fmu-plugins\u002Fheadless-config.php",[90,929,933],{"className":930,"code":931,"language":932,"meta":95,"style":95},"language-php shiki shiki-themes material-theme-lighter github-dark github-dark","\u003C?php\nadd_filter('use_block_editor_for_post_type', '__return_false', 999);\n\nadd_action('init', function () {\n  remove_post_type_support('post', 'editor');\n  remove_post_type_support('page', 'editor');\n}, 20);\n","php",[85,934,935,940,945,949,954,959,964],{"__ignoreMap":95},[99,936,937],{"class":101,"line":102},[99,938,939],{},"\u003C?php\n",[99,941,942],{"class":101,"line":115},[99,943,944],{},"add_filter('use_block_editor_for_post_type', '__return_false', 999);\n",[99,946,947],{"class":101,"line":125},[99,948,650],{"emptyLinePlaceholder":649},[99,950,951],{"class":101,"line":149},[99,952,953],{},"add_action('init', function () {\n",[99,955,956],{"class":101,"line":155},[99,957,958],{},"  remove_post_type_support('post', 'editor');\n",[99,960,961],{"class":101,"line":265},[99,962,963],{},"  remove_post_type_support('page', 'editor');\n",[99,965,966],{"class":101,"line":536},[99,967,968],{},"}, 20);\n",[11,970,971,972,975,976,979],{},"A pasta ",[85,973,974],{},"mu-plugins"," não existe por padrão, mas o WordPress a reconhece automaticamente quando ela é criada em ",[85,977,978],{},"wp-content\u002Fmu-plugins\u002F",". Os arquivos ali dentro são carregados sempre, sem precisar ativar nada pelo painel, o que a torna ideal para configuração de infraestrutura como essa.",[11,981,982],{},"Vale destacar três detalhes desse trecho:",[32,984,985,992,1006],{},[35,986,987,988,991],{},"A prioridade ",[85,989,990],{},"999"," no filtro garante que o Gutenberg fique desativado mesmo que outro plugin tente reativá-lo depois",[35,993,987,994,997,998,1001,1002,1005],{},[85,995,996],{},"20"," no ",[85,999,1000],{},"init"," garante que o ",[85,1003,1004],{},"remove_post_type_support"," rode depois de todos os post types já terem sido registrados",[35,1007,1008,1009,1012],{},"Omitir o ",[85,1010,1011],{},"?>"," no final é uma boa prática em arquivos PHP puro: evita o erro \"headers already sent\", causado por qualquer whitespace depois da tag de fechamento",[11,1014,1015],{},"Na prática, desligar o editor de blocos muda o fluxo de edição da cliente. Antes, ela montava a página no editor visual, arrastando e posicionando os elementos, e era justamente aí que o layout quebrava a cada mudança. Agora ela não edita mais a página no visual: só preenche os campos estruturados do ACF (título, texto, imagem). O posicionamento deixou de ser responsabilidade dela e passou a ser do front-end.",[11,1017,1018],{},[181,1019],{"alt":1020,"src":1021},"Painel do WordPress com os campos ACF da seção Home - Historia, um campo de título e um de descrição preenchidos pela cliente","\u002Fimages\u002Fblog\u002Facf\u002FACF-conteudo-pagina.png",[11,1023,1024],{},"O mesmo conteúdo, depois de passar pelo Nuxt, chega renderizado no layout definido no front-end:",[11,1026,1027],{},[181,1028],{"alt":1029,"src":1030},"Seção da home da Teciklar renderizada no Nuxt, com o mesmo conteúdo dos campos ACF apresentado no layout final","\u002Fimages\u002Fblog\u002Facf\u002FACF-resultado.png",[21,1032],{},[24,1034,1036],{"id":1035},"a-divisão-de-responsabilidades","A divisão de responsabilidades",[1038,1039,1040,1053],"table",{},[1041,1042,1043],"thead",{},[1044,1045,1046,1050],"tr",{},[1047,1048,1049],"th",{},"Camada",[1047,1051,1052],{},"Responsabilidade",[1054,1055,1056,1065,1073,1083,1097,1106,1115],"tbody",{},[1044,1057,1058,1062],{},[1059,1060,1061],"td",{},"WordPress",[1059,1063,1064],{},"Gerenciar conteúdo via painel familiar",[1044,1066,1067,1070],{},[1059,1068,1069],{},"ACF",[1059,1071,1072],{},"Modelar campos estruturados por seção",[1044,1074,1075,1080],{},[1059,1076,1077],{},[85,1078,1079],{},"useAcfFields",[1059,1081,1082],{},"Extrair e tipar cada seção por prefixo",[1044,1084,1085,1094],{},[1059,1086,1087,1090,1091],{},[85,1088,1089],{},"useHome"," \u002F ",[85,1092,1093],{},"useBlog",[1059,1095,1096],{},"Encapsular busca e transformação de dados por página",[1044,1098,1099,1103],{},[1059,1100,1101],{},[85,1102,87],{},[1059,1104,1105],{},"Isolar URL do WordPress do código",[1044,1107,1108,1112],{},[1059,1109,1110],{},[85,1111,974],{},[1059,1113,1114],{},"Configurar WordPress como backend puro",[1044,1116,1117,1120],{},[1059,1118,1119],{},"Nuxt (SSR)",[1059,1121,1122],{},"Renderizar páginas com dados prontos para SEO",[21,1124],{},[24,1126,1128],{"id":1127},"o-que-o-headless-teve-de-desvantagem","O que o Headless teve de desvantagem?",[11,1130,1131],{},"Até aqui pode parecer que o Headless só trouxe vantagem, mas toda decisão de arquitetura tem um preço.",[11,1133,1134],{},"Passei a ter duas coisas pra manter no lugar de uma: WordPress, Nuxt e o deploy, cada um com sua config. A REST API virou um ponto de falha que não existia antes: se o WordPress cai ou muda a resposta, o front sente na hora. E a cliente perdeu o \"ver antes de publicar\", já que agora preenche campos sem enxergar o layout se formando.",[11,1136,1137],{},"Então por que valeu? Porque esses custos são pagos uma vez, ou de vez em quando. O problema que originou tudo, o layout quebrando a cada edição, acontecia sempre que ela mexia no conteúdo. Trocar uma dor crônica por um custo pontual foi a conta certa pra este contexto. Noutro cenário, com outro orçamento ou outra escala, a resposta poderia ser diferente.",[21,1139],{},[24,1141,1143],{"id":1142},"e-depois-do-wordpress","E depois do WordPress?",[11,1145,1146],{},"Desde o começo eu montei essa arquitetura pensando em poder trocar de backend depois.",[11,1148,1149],{},"O WordPress cumpre bem o papel de backend inicial: painel familiar para a cliente, REST API pronta, sem complexidade desnecessária numa primeira entrega. Mas ele não precisa ser permanente.",[11,1151,1152],{},"Como toda a lógica de busca está encapsulada nos composables e o front-end consome dados via REST API, trocar o backend é mais uma questão de mudar o endpoint, não de reescrever o projeto.",[11,1154,1155],{},"Quando chegar a hora de desenvolver um backend próprio:",[32,1157,1158,1165,1168],{},[35,1159,1160,1161,1164],{},"A variável ",[85,1162,1163],{},"NUXT_PUBLIC_WORDPRESS_URL"," passa a apontar para a nova API",[35,1166,1167],{},"Os composables continuam iguais, desde que o novo backend respeite o mesmo contrato de resposta",[35,1169,1170],{},"O front-end não precisa saber que o WordPress saiu",[11,1172,1173],{},"É por isso que valeu definir bem a camada Headless: o front-end fica independente do que está por trás.",[11,1175,1176],{},"Nos próximos artigos vou continuar contando a evolução desse projeto, incluindo as decisões que vêm pela frente.",[21,1178],{},[24,1180,1182],{"id":1181},"conclusão","Conclusão",[11,1184,1185],{},"No fim, cada camada faz o que faz de melhor. A cliente edita o conteúdo no painel do WordPress que ela já conhece, o ACF garante que esse conteúdo chegue estruturado e limpo, e o Nuxt consome os dados e apresenta com a performance e o SEO que um site institucional precisa. O que ela mexe no painel não afeta mais o layout, que era exatamente o problema que motivou toda a arquitetura.",[11,1187,1188],{},"É essa separação de responsabilidades que faz o Headless valer o esforço de configuração inicial. Ela custa um pouco mais de trabalho no começo, mas devolve isso na forma de um site que não quebra a cada edição e de um código onde cada mudança tem um lugar claro para acontecer.",[11,1190,1191],{},"Para a Teciklar, o projeto está na reta final de ajustes, rodando num domínio provisório enquanto preparamos a virada para o oficial. Mas o essencial já é realidade: a cliente cuida do próprio conteúdo sem medo de estragar alguma coisa, porque o posicionamento deixou de ser responsabilidade dela. Que, no fim, era todo o objetivo.",[11,1193,1194],{},"Esse projeto rendeu muito aprendizado, e este artigo é só o primeiro de uma série. Nos próximos vou detalhar os composables, as soluções de SEO \u002F GEO \u002F AEO, como usei o Claude Cowork para transformar um Notion inteiro em contexto de código, como fechei o contrato de freelancer, e muito mais. Fica de olho.",[21,1196],{},[24,1198,1200],{"id":1199},"leituras-relacionadas","Leituras relacionadas",[32,1202,1203,1212,1219,1226,1233],{},[35,1204,1205],{},[1206,1207,1211],"a",{"href":1208,"rel":1209},"https:\u002F\u002Fwordpress.com\u002Fblog\u002F2025\u002F03\u002F20\u002Fheadless-wordpress\u002F",[1210],"nofollow","What Is Headless WordPress? — WordPress.com",[35,1213,1214],{},[1206,1215,1218],{"href":1216,"rel":1217},"https:\u002F\u002Fbr.wordpress.org\u002Fplugins\u002Fadvanced-custom-fields\u002F",[1210],"Advanced Custom Fields (ACF) — plugin oficial",[35,1220,1221],{},[1206,1222,1225],{"href":1223,"rel":1224},"https:\u002F\u002Fwww.larisantos.com.br\u002Fblog\u002Fvue-composition-vs-options-api",[1210],"Options API vs Composition API: qual usar e quando?",[35,1227,1228],{},[1206,1229,1232],{"href":1230,"rel":1231},"https:\u002F\u002Fwww.larisantos.com.br\u002Fblog\u002Fprincipio-solid",[1210],"SOLID: 5 Princípios para Escrever Código Limpo e Escalável",[35,1234,1235],{},[1206,1236,1239],{"href":1237,"rel":1238},"https:\u002F\u002Fwww.larisantos.com.br\u002Fblog\u002Fvuex-vs-pinia",[1210],"Vuex vs Pinia: Guia Completo de Gerenciamento de Estado",[1241,1242,1243],"style",{},"html .light .shiki span {color: var(--shiki-light);background: var(--shiki-light-bg);font-style: var(--shiki-light-font-style);font-weight: var(--shiki-light-font-weight);text-decoration: var(--shiki-light-text-decoration);}html.light .shiki span {color: var(--shiki-light);background: var(--shiki-light-bg);font-style: var(--shiki-light-font-style);font-weight: var(--shiki-light-font-weight);text-decoration: var(--shiki-light-text-decoration);}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 .soiBB, html code.shiki .soiBB{--shiki-light:#E2931D;--shiki-default:#B392F0;--shiki-dark:#B392F0}html pre.shiki code .sG-J9, html code.shiki .sG-J9{--shiki-light:#39ADB5;--shiki-default:#E1E4E8;--shiki-dark:#E1E4E8}html pre.shiki code .sMo7A, html code.shiki .sMo7A{--shiki-light:#90A4AE;--shiki-default:#E1E4E8;--shiki-dark:#E1E4E8}html pre.shiki code .sVPC0, html code.shiki .sVPC0{--shiki-light:#90A4AE;--shiki-default:#79B8FF;--shiki-dark:#79B8FF}html pre.shiki code .swu5b, html code.shiki .swu5b{--shiki-light:#39ADB5;--shiki-default:#79B8FF;--shiki-dark:#79B8FF}html pre.shiki code .sod2m, html code.shiki .sod2m{--shiki-light:#9C3EDA;--shiki-default:#79B8FF;--shiki-dark:#79B8FF}html pre.shiki code .s3afY, html code.shiki .s3afY{--shiki-light:#E2931D;--shiki-default:#79B8FF;--shiki-dark:#79B8FF}html pre.shiki code .sF_wb, html code.shiki .sF_wb{--shiki-light:#39ADB5;--shiki-default:#9ECBFF;--shiki-dark:#9ECBFF}html pre.shiki code .s0vBq, html code.shiki .s0vBq{--shiki-light:#91B859;--shiki-default:#9ECBFF;--shiki-dark:#9ECBFF}html pre.shiki code .s3Er8, html code.shiki .s3Er8{--shiki-light:#39ADB5;--shiki-light-font-style:italic;--shiki-default:#F97583;--shiki-default-font-style:inherit;--shiki-dark:#F97583;--shiki-dark-font-style:inherit}html pre.shiki code .sFsEu, html code.shiki .sFsEu{--shiki-light:#9C3EDA;--shiki-default:#F97583;--shiki-dark:#F97583}html pre.shiki code .sK_r7, html code.shiki .sK_r7{--shiki-light:#6182B8;--shiki-default:#B392F0;--shiki-dark:#B392F0}html pre.shiki code .sk1zL, html code.shiki .sk1zL{--shiki-light:#90A4AE;--shiki-light-font-style:italic;--shiki-default:#FFAB70;--shiki-default-font-style:inherit;--shiki-dark:#FFAB70;--shiki-dark-font-style:inherit}html pre.shiki code .sFfmW, html code.shiki .sFfmW{--shiki-light:#39ADB5;--shiki-default:#F97583;--shiki-dark:#F97583}html pre.shiki code .sdv8B, html code.shiki .sdv8B{--shiki-light:#E53935;--shiki-default:#E1E4E8;--shiki-dark:#E1E4E8}",{"title":95,"searchDepth":115,"depth":115,"links":1245},[1246,1247,1248,1249,1250,1251,1252,1253,1254,1255,1256,1257],{"id":26,"depth":115,"text":27},{"id":61,"depth":115,"text":62},{"id":76,"depth":115,"text":77},{"id":170,"depth":115,"text":171},{"id":281,"depth":115,"text":282},{"id":602,"depth":115,"text":603},{"id":920,"depth":115,"text":921},{"id":1035,"depth":115,"text":1036},{"id":1127,"depth":115,"text":1128},{"id":1142,"depth":115,"text":1143},{"id":1181,"depth":115,"text":1182},{"id":1199,"depth":115,"text":1200},"2026-07-02T17:35:12-03:00","Um artigo prático sobre como estruturar um projeto WordPress Headless com Nuxt: decisão pela arquitetura, organização de composables, convenção de campos ACF e configuração do WordPress como backend puro.","md",[1262,1265,1268,1271],{"question":1263,"answer":1264},"Quando usar WordPress Headless faz sentido?","Faz sentido quando o cliente já usa e conhece o painel do WordPress, quando o layout precisa ser controlado totalmente pelo front-end sem risco de quebra por edição de conteúdo, e quando SEO é prioridade. Se o site é simples e o cliente precisa montar as próprias páginas visualmente, um tema tradicional resolve melhor.",{"question":1266,"answer":1267},"Preciso de plugin pago para usar o WordPress como Headless?","Não. O WordPress já expõe uma REST API completa por padrão, sem plugin ou configuração especial. Para modelar campos estruturados e receber dados limpos em vez de HTML, o ACF na versão gratuita já resolve — basta habilitar \"Show in REST API\" em cada grupo de campos.",{"question":1269,"answer":1270},"Por que usar Nuxt em vez de uma SPA como o Quasar?","Uma SPA entrega a página vazia e monta o conteúdo no navegador via JavaScript, o que é péssimo para SEO em um site institucional. O Nuxt entrega SSR nativo: os dados do WordPress já chegam dentro do HTML, e tanto o Google quanto as IAs generativas indexam sem depender de executar JavaScript.",{"question":1272,"answer":1273},"Como organizar muitos campos ACF numa mesma página?","Adotando uma convenção de nomenclatura por prefixo de seção (historia_titulo, parceiros_titulo). Com esse padrão, um composable consegue extrair cada seção de forma isolada e tipada, filtrando os campos pelo prefixo e devolvendo as chaves já limpas.","\u002Fimages\u002Fblog\u002Fwordpress-headless-nuxt.png",{},"\u002Fblog\u002Fnuxt-wordpress-headless-nuxt",{"title":5,"description":1259},"blog\u002Fnuxt-wordpress-headless-nuxt",[1280,1281,1282,1283,206,1284,1285,1286],"nuxt","vue","wordpress","headless","typescript","composables","arquitetura","2026-07-02T21:02:52-03:00","a8l4wMoOu0qJqlcWLxn-zXGXGGitQw6MoMxXo1EdhLc",1783037051439]