[{"data":1,"prerenderedAt":1233},["ShallowReactive",2],{"blog-nuxt-wordpress-headless-nuxt":3},{"id":4,"title":5,"author":6,"body":7,"date":1202,"description":1203,"extension":1204,"faq":1205,"image":1218,"meta":1219,"navigation":649,"path":1220,"seo":1221,"stem":1222,"tags":1223,"updated":1231,"__hash__":1232},"blog\u002Fblog\u002Fnuxt-wordpress-headless-nuxt.md","WordPress Headless com Nuxt: quando faz sentido e como estruturar","Larissa Santos",{"type":8,"value":9,"toc":1190},"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,1141,1143,1147,1186],[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. Ela, por ser uma pessoa leiga em tecnologia, não deveria lidar com tamanha complexidade.",[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},"conclusão","Conclusão",[11,1130,1131],{},"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,1133,1134],{},"É 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,1136,1137],{},"Para a Teciklar, o resultado foi um site que finalmente reflete a seriedade do projeto, e uma cliente que consegue cuidar do próprio conteúdo sem medo de estragar alguma coisa. Que, no fim, era todo o objetivo.",[11,1139,1140],{},"Esse projeto rendeu muito aprendizado, e este artigo é só o primeiro de uma série. Em breve trago mais conteúdos sobre ele: os detalhes dos 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,1142],{},[24,1144,1146],{"id":1145},"leituras-relacionadas","Leituras relacionadas",[32,1148,1149,1158,1165,1172,1179],{},[35,1150,1151],{},[1152,1153,1157],"a",{"href":1154,"rel":1155},"https:\u002F\u002Fwordpress.com\u002Fblog\u002F2025\u002F03\u002F20\u002Fheadless-wordpress\u002F",[1156],"nofollow","What Is Headless WordPress? — WordPress.com",[35,1159,1160],{},[1152,1161,1164],{"href":1162,"rel":1163},"https:\u002F\u002Fbr.wordpress.org\u002Fplugins\u002Fadvanced-custom-fields\u002F",[1156],"Advanced Custom Fields (ACF) — plugin oficial",[35,1166,1167],{},[1152,1168,1171],{"href":1169,"rel":1170},"https:\u002F\u002Flarisantos.vercel.app\u002Fblog\u002Fvue-composition-vs-options-api",[1156],"Options API vs Composition API: qual usar e quando?",[35,1173,1174],{},[1152,1175,1178],{"href":1176,"rel":1177},"https:\u002F\u002Flarisantos.vercel.app\u002Fblog\u002Fprincipio-solid",[1156],"SOLID: 5 Princípios para Escrever Código Limpo e Escalável",[35,1180,1181],{},[1152,1182,1185],{"href":1183,"rel":1184},"https:\u002F\u002Flarisantos.vercel.app\u002Fblog\u002Fvuex-vs-pinia",[1156],"Vuex vs Pinia: Guia Completo de Gerenciamento de Estado",[1187,1188,1189],"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":1191},[1192,1193,1194,1195,1196,1197,1198,1199,1200,1201],{"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":1145,"depth":115,"text":1146},"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",[1206,1209,1212,1215],{"question":1207,"answer":1208},"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":1210,"answer":1211},"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":1213,"answer":1214},"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":1216,"answer":1217},"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":1203},"blog\u002Fnuxt-wordpress-headless-nuxt",[1224,1225,1226,1227,206,1228,1229,1230],"nuxt","vue","wordpress","headless","typescript","composables","arquitetura",null,"VjN-MU3qZiw9FGhEBIbog92ZpG9Zl5TvV4_daNzVKWQ",1783011476588]