[{"data":1,"prerenderedAt":588},["ShallowReactive",2],{"blog-arsitektur-nuxt-laravel":3},{"id":4,"title":5,"body":6,"description":573,"extension":574,"meta":575,"navigation":157,"path":584,"seo":585,"stem":586,"__hash__":587},"blog\u002Fblog\u002Farsitektur-nuxt-laravel.md","Arsitektur Nuxt 4 + Laravel API: Pola Fullstack Modern & Skalabel",{"type":7,"value":8,"toc":567},"minimark",[9,22,25,30,37,66,289,293,300,414,418,421,526,529,533,548,560,563],[10,11,12,13,17,18,21],"p",{},"Memadukan ",[14,15,16],"strong",{},"Nuxt 4"," sebagai lapisan presentasi interaktif dan ",[14,19,20],{},"Laravel"," sebagai pondasi API bisnis merupakan kombinasi yang sangat matang bagi developer modern. Di satu sisi, kita mendapatkan reaktivitas Vue 3 dengan kecepatan engine Nitro, dan di sisi lain kita menikmati kemudahan Eloquent ORM, ekosistem queue, serta keamanan Laravel.",[10,23,24],{},"Namun, mengintegrasikan keduanya secara elegan membutuhkan pemahaman arsitektur yang solid agar frontend tetap ringan dan backend tidak terbebani permintaan yang redundan.",[26,27,29],"h2",{"id":28},"_1-strategi-autentikasi-sanctum-vs-token-bearer","1. Strategi Autentikasi: Sanctum vs Token Bearer",[10,31,32,33],{},"Ketika membangun arsitektur SPA\u002FSSG bersama Laravel, pertanyaan pertama selalu berkisar pada: ",[34,35,36],"em",{},"apakah menggunakan session cookie berbasis Sanctum atau Token Bearer JWT?",[38,39,40,60],"ul",{},[41,42,43,46,47,51,52,55,56,59],"li",{},[14,44,45],{},"Stateful Cookie (Sanctum SPA)",": Sangat direkomendasikan jika frontend Nuxt dan Laravel berjalan pada root domain atau subdomain yang sama (misal ",[48,49,50],"code",{},"app.dika.dev"," dan ",[48,53,54],{},"api.dika.dev","). Cookie ",[48,57,58],{},"httpOnly"," memberikan proteksi bawaan terhadap serangan XSS.",[41,61,62,65],{},[14,63,64],{},"Bearer Token",": Lebih fleksibel jika frontend di-hosting pada platform terpisah (misal di Vercel SSG murni) tanpa domain sharing atau saat melayani multi-client (mobile app & web).",[67,68,73],"pre",{"className":69,"code":70,"language":71,"meta":72,"style":72},"language-typescript shiki shiki-themes github-light github-dark","\u002F\u002F Contoh implementasi composable useApi di Nuxt\nexport const useApi = () => {\n  const config = useRuntimeConfig()\n  const token = useCookie('auth_token')\n\n  return $fetch.create({\n    baseURL: config.public.apiBase || 'https:\u002F\u002Fapi.domain.com\u002Fapi',\n    headers: token.value ? { Authorization: `Bearer ${token.value}` } : {},\n    onResponseError({ response }) {\n      if (response.status === 401) {\n        \u002F\u002F Redirect ke login atau trigger refresh token\n      }\n    }\n  })\n}\n","typescript","",[48,74,75,84,111,129,152,159,174,189,225,241,259,265,271,277,283],{"__ignoreMap":72},[76,77,80],"span",{"class":78,"line":79},"line",1,[76,81,83],{"class":82},"sJ8bj","\u002F\u002F Contoh implementasi composable useApi di Nuxt\n",[76,85,87,91,94,98,101,105,108],{"class":78,"line":86},2,[76,88,90],{"class":89},"szBVR","export",[76,92,93],{"class":89}," const",[76,95,97],{"class":96},"sScJk"," useApi",[76,99,100],{"class":89}," =",[76,102,104],{"class":103},"sVt8B"," () ",[76,106,107],{"class":89},"=>",[76,109,110],{"class":103}," {\n",[76,112,114,117,121,123,126],{"class":78,"line":113},3,[76,115,116],{"class":89},"  const",[76,118,120],{"class":119},"sj4cs"," config",[76,122,100],{"class":89},[76,124,125],{"class":96}," useRuntimeConfig",[76,127,128],{"class":103},"()\n",[76,130,132,134,137,139,142,145,149],{"class":78,"line":131},4,[76,133,116],{"class":89},[76,135,136],{"class":119}," token",[76,138,100],{"class":89},[76,140,141],{"class":96}," useCookie",[76,143,144],{"class":103},"(",[76,146,148],{"class":147},"sZZnC","'auth_token'",[76,150,151],{"class":103},")\n",[76,153,155],{"class":78,"line":154},5,[76,156,158],{"emptyLinePlaceholder":157},true,"\n",[76,160,162,165,168,171],{"class":78,"line":161},6,[76,163,164],{"class":89},"  return",[76,166,167],{"class":103}," $fetch.",[76,169,170],{"class":96},"create",[76,172,173],{"class":103},"({\n",[76,175,177,180,183,186],{"class":78,"line":176},7,[76,178,179],{"class":103},"    baseURL: config.public.apiBase ",[76,181,182],{"class":89},"||",[76,184,185],{"class":147}," 'https:\u002F\u002Fapi.domain.com\u002Fapi'",[76,187,188],{"class":103},",\n",[76,190,192,195,198,201,204,207,210,213,216,219,222],{"class":78,"line":191},8,[76,193,194],{"class":103},"    headers: token.value ",[76,196,197],{"class":89},"?",[76,199,200],{"class":103}," { Authorization: ",[76,202,203],{"class":147},"`Bearer ${",[76,205,206],{"class":103},"token",[76,208,209],{"class":147},".",[76,211,212],{"class":103},"value",[76,214,215],{"class":147},"}`",[76,217,218],{"class":103}," } ",[76,220,221],{"class":89},":",[76,223,224],{"class":103}," {},\n",[76,226,228,231,234,238],{"class":78,"line":227},9,[76,229,230],{"class":96},"    onResponseError",[76,232,233],{"class":103},"({ ",[76,235,237],{"class":236},"s4XuR","response",[76,239,240],{"class":103}," }) {\n",[76,242,244,247,250,253,256],{"class":78,"line":243},10,[76,245,246],{"class":89},"      if",[76,248,249],{"class":103}," (response.status ",[76,251,252],{"class":89},"===",[76,254,255],{"class":119}," 401",[76,257,258],{"class":103},") {\n",[76,260,262],{"class":78,"line":261},11,[76,263,264],{"class":82},"        \u002F\u002F Redirect ke login atau trigger refresh token\n",[76,266,268],{"class":78,"line":267},12,[76,269,270],{"class":103},"      }\n",[76,272,274],{"class":78,"line":273},13,[76,275,276],{"class":103},"    }\n",[76,278,280],{"class":78,"line":279},14,[76,281,282],{"class":103},"  })\n",[76,284,286],{"class":78,"line":285},15,[76,287,288],{"class":103},"}\n",[26,290,292],{"id":291},"_2-standarisasi-response-format-dengan-api-resources","2. Standarisasi Response Format dengan API Resources",[10,294,295,296,299],{},"Hindari mengembalikan model Eloquent langsung ke frontend. Selalu gunakan ",[48,297,298],{},"JsonResource"," di Laravel untuk memastikan format data konsisten dan data sensitif (seperti password hash atau deleted_at) tidak bocor:",[67,301,305],{"className":302,"code":303,"language":304,"meta":72,"style":72},"language-php shiki shiki-themes github-light github-dark","namespace App\\Http\\Resources;\n\nuse Illuminate\\Http\\Request;\nuse Illuminate\\Http\\Resources\\Json\\JsonResource;\n\nclass ProjectResource extends JsonResource\n{\n    public function toArray(Request $request): array\n    {\n        return [\n            'id' => $this->id,\n            'title' => $this->title,\n            'slug' => $this->slug,\n            'description' => $this->description,\n            'technologies' => $this->technologies,\n            'demo_url' => $this->demo_url,\n            'github_url' => $this->github_url,\n            'created_at' => $this->created_at->toISOString(),\n        ];\n    }\n}\n","php",[48,306,307,312,316,321,326,330,335,340,345,350,355,360,365,370,375,380,386,392,398,404,409],{"__ignoreMap":72},[76,308,309],{"class":78,"line":79},[76,310,311],{},"namespace App\\Http\\Resources;\n",[76,313,314],{"class":78,"line":86},[76,315,158],{"emptyLinePlaceholder":157},[76,317,318],{"class":78,"line":113},[76,319,320],{},"use Illuminate\\Http\\Request;\n",[76,322,323],{"class":78,"line":131},[76,324,325],{},"use Illuminate\\Http\\Resources\\Json\\JsonResource;\n",[76,327,328],{"class":78,"line":154},[76,329,158],{"emptyLinePlaceholder":157},[76,331,332],{"class":78,"line":161},[76,333,334],{},"class ProjectResource extends JsonResource\n",[76,336,337],{"class":78,"line":176},[76,338,339],{},"{\n",[76,341,342],{"class":78,"line":191},[76,343,344],{},"    public function toArray(Request $request): array\n",[76,346,347],{"class":78,"line":227},[76,348,349],{},"    {\n",[76,351,352],{"class":78,"line":243},[76,353,354],{},"        return [\n",[76,356,357],{"class":78,"line":261},[76,358,359],{},"            'id' => $this->id,\n",[76,361,362],{"class":78,"line":267},[76,363,364],{},"            'title' => $this->title,\n",[76,366,367],{"class":78,"line":273},[76,368,369],{},"            'slug' => $this->slug,\n",[76,371,372],{"class":78,"line":279},[76,373,374],{},"            'description' => $this->description,\n",[76,376,377],{"class":78,"line":285},[76,378,379],{},"            'technologies' => $this->technologies,\n",[76,381,383],{"class":78,"line":382},16,[76,384,385],{},"            'demo_url' => $this->demo_url,\n",[76,387,389],{"class":78,"line":388},17,[76,390,391],{},"            'github_url' => $this->github_url,\n",[76,393,395],{"class":78,"line":394},18,[76,396,397],{},"            'created_at' => $this->created_at->toISOString(),\n",[76,399,401],{"class":78,"line":400},19,[76,402,403],{},"        ];\n",[76,405,407],{"class":78,"line":406},20,[76,408,276],{},[76,410,412],{"class":78,"line":411},21,[76,413,288],{},[26,415,417],{"id":416},"_3-menghindari-over-fetching-dengan-typescript-dto","3. Menghindari \"Over-fetching\" dengan TypeScript DTO",[10,419,420],{},"Di sisi Nuxt, kita mendefinisikan interface TypeScript yang mencerminkan kontrak API dari Laravel:",[67,422,424],{"className":69,"code":423,"language":71,"meta":72,"style":72},"\u002F\u002F types\u002Fproject.ts\nexport interface Project {\n  id: number\n  title: string\n  slug: string\n  description: string\n  technologies: string[]\n  demo_url?: string\n  github_url?: string\n  created_at: string\n}\n",[48,425,426,431,443,453,463,472,481,494,504,513,522],{"__ignoreMap":72},[76,427,428],{"class":78,"line":79},[76,429,430],{"class":82},"\u002F\u002F types\u002Fproject.ts\n",[76,432,433,435,438,441],{"class":78,"line":86},[76,434,90],{"class":89},[76,436,437],{"class":89}," interface",[76,439,440],{"class":96}," Project",[76,442,110],{"class":103},[76,444,445,448,450],{"class":78,"line":113},[76,446,447],{"class":236},"  id",[76,449,221],{"class":89},[76,451,452],{"class":119}," number\n",[76,454,455,458,460],{"class":78,"line":131},[76,456,457],{"class":236},"  title",[76,459,221],{"class":89},[76,461,462],{"class":119}," string\n",[76,464,465,468,470],{"class":78,"line":154},[76,466,467],{"class":236},"  slug",[76,469,221],{"class":89},[76,471,462],{"class":119},[76,473,474,477,479],{"class":78,"line":161},[76,475,476],{"class":236},"  description",[76,478,221],{"class":89},[76,480,462],{"class":119},[76,482,483,486,488,491],{"class":78,"line":176},[76,484,485],{"class":236},"  technologies",[76,487,221],{"class":89},[76,489,490],{"class":119}," string",[76,492,493],{"class":103},"[]\n",[76,495,496,499,502],{"class":78,"line":191},[76,497,498],{"class":236},"  demo_url",[76,500,501],{"class":89},"?:",[76,503,462],{"class":119},[76,505,506,509,511],{"class":78,"line":227},[76,507,508],{"class":236},"  github_url",[76,510,501],{"class":89},[76,512,462],{"class":119},[76,514,515,518,520],{"class":78,"line":243},[76,516,517],{"class":236},"  created_at",[76,519,221],{"class":89},[76,521,462],{"class":119},[76,523,524],{"class":78,"line":261},[76,525,288],{"class":103},[10,527,528],{},"Dengan TypeScript yang ketat, komponen Vue kita mendapatkan autocomplete dan type checking penuh saat melakukan kompilasi, mencegah kesalahan penamaan field sebelum aplikasi masuk ke produksi.",[26,530,532],{"id":531},"_4-nuxt-ssg-static-prerendering","4. Nuxt SSG & Static Prerendering",[10,534,535,536,539,540,543,544,547],{},"Untuk situs portofolio atau blog publik, arsitektur terbaik adalah ",[14,537,538],{},"Pure SSG",". Nuxt memanggil endpoint Laravel saat waktu ",[34,541,542],{},"build"," (",[48,545,546],{},"nuxt generate","), menyimpan hasilnya sebagai HTML dan JSON statis. Dengan begitu:",[549,550,551,554,557],"ol",{},[41,552,553],{},"Waktu respon pengunjung mendekati 0ms (dilayani langsung dari edge CDN Vercel).",[41,555,556],{},"Server Laravel tidak dihujani request untuk konten yang jarang berubah.",[41,558,559],{},"Keamanan meningkat karena server database tidak terekspos langsung ke traffic publik.",[10,561,562],{},"Kombinasi ini memberikan performa terbaik dari kedua dunia: kenyamanan pengembangan Laravel dan kecepatan absolut Nuxt statis.",[564,565,566],"style",{},"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 .sJ8bj, html code.shiki .sJ8bj{--shiki-default:#6A737D;--shiki-dark:#6A737D}html pre.shiki code .szBVR, html code.shiki .szBVR{--shiki-default:#D73A49;--shiki-dark:#F97583}html pre.shiki code .sScJk, html code.shiki .sScJk{--shiki-default:#6F42C1;--shiki-dark:#B392F0}html pre.shiki code .sVt8B, html code.shiki .sVt8B{--shiki-default:#24292E;--shiki-dark:#E1E4E8}html pre.shiki code .sj4cs, html code.shiki .sj4cs{--shiki-default:#005CC5;--shiki-dark:#79B8FF}html pre.shiki code .sZZnC, html code.shiki .sZZnC{--shiki-default:#032F62;--shiki-dark:#9ECBFF}html pre.shiki code .s4XuR, html code.shiki .s4XuR{--shiki-default:#E36209;--shiki-dark:#FFAB70}",{"title":72,"searchDepth":86,"depth":86,"links":568},[569,570,571,572],{"id":28,"depth":86,"text":29},{"id":291,"depth":86,"text":292},{"id":416,"depth":86,"text":417},{"id":531,"depth":86,"text":532},"Panduan mendalam merancang komunikasi antara frontend Nuxt 4 dan backend Laravel REST API dengan autentikasi Sanctum, state management, dan caching efisien.","md",{"date":576,"category":577,"tags":578,"readTime":583,"cover":72},"2026-08-15","Engineering",[579,20,580,581,582],"Nuxt","Fullstack","API","TypeScript","8 menit","\u002Fblog\u002Farsitektur-nuxt-laravel",{"title":5,"description":573},"blog\u002Farsitektur-nuxt-laravel","N6H6ym6GWoOguJNBl9MWxJqdegsZNmXv47XAyALRnQI",1789128905112]