[{"data":1,"prerenderedAt":966},["ShallowReactive",2],{"blog-post-\u002Fko\u002Fadsense-ads-txt-and-seo-indexing-bug":3,"blog-related-source":706},{"id":4,"title":5,"body":6,"description":691,"extension":692,"meta":693,"navigation":700,"path":701,"seo":702,"sitemap":703,"stem":704,"__hash__":705},"ko\u002Fko\u002Fadsense-ads-txt-and-seo-indexing-bug.md","애드센스 ads.txt 경고를 받고 파헤치다가 찾은 진짜 색인 버그",{"type":7,"value":8,"toc":679},"minimark",[9,14,18,24,27,31,39,71,74,82,85,166,169,180,188,192,195,209,212,218,221,225,231,420,434,437,489,492,495,499,504,539,550,554,559,583,586,589,615,630,633,672,675],[10,11,13],"h2",{"id":12},"시작-애드센스의-수익-손실-경고","시작: 애드센스의 수익 손실 경고",[15,16,17],"p",{},"블로그를 운영하다 보면 가끔 애드센스에서 이런 알림을 받는다.",[19,20,21],"blockquote",{},[15,22,23],{},"수익 손실 위험 - 수익에 심각한 영향을 미치지 않도록 사이트에서 발견된 ads.txt 파일 문제를 해결해야 합니다.",[15,25,26],{},"무섭게 생긴 문구지만, 실제로 뭐가 문제인지 하나씩 확인해보기로 했다.",[10,28,30],{"id":29},"adstxt-자체는-멀쩡했다","ads.txt 자체는 멀쩡했다",[15,32,33,34,38],{},"먼저 배포된 사이트에서 ",[35,36,37],"code",{},"ads.txt","가 정상적으로 서빙되는지 확인했다.",[40,41,46],"pre",{"className":42,"code":43,"language":44,"meta":45,"style":45},"language-bash shiki shiki-themes github-light github-dark","curl -sS -D - \"https:\u002F\u002Fiambilly-blog.vercel.app\u002Fads.txt\"\n","bash","",[35,47,48],{"__ignoreMap":45},[49,50,53,57,61,64,68],"span",{"class":51,"line":52},"line",1,[49,54,56],{"class":55},"sScJk","curl",[49,58,60],{"class":59},"sj4cs"," -sS",[49,62,63],{"class":59}," -D",[49,65,67],{"class":66},"sZZnC"," -",[49,69,70],{"class":66}," \"https:\u002F\u002Fiambilly-blog.vercel.app\u002Fads.txt\"\n",[15,72,73],{},"결과는 200 OK, 내용도 애드센스가 요구하는 값과 완전히 일치했다.",[40,75,80],{"className":76,"code":78,"language":79},[77],"language-text","google.com, pub-89xxxxxxxxx575, DIRECT, f08c47fec0942fa0\n","text",[35,81,78],{"__ignoreMap":45},[15,83,84],{},"ads.txt는 쉼표로 구분된 4개 컬럼으로 구성되는데, 각각의 의미는 다음과 같다.",[86,87,88,104],"table",{},[89,90,91],"thead",{},[92,93,94,98,101],"tr",{},[95,96,97],"th",{},"컬럼",[95,99,100],{},"값",[95,102,103],{},"의미",[105,106,107,121,134,153],"tbody",{},[92,108,109,113,118],{},[110,111,112],"td",{},"1",[110,114,115],{},[35,116,117],{},"google.com",[110,119,120],{},"이 광고 인벤토리를 판매할 권한을 가진 광고 시스템(SSP)의 도메인",[92,122,123,126,131],{},[110,124,125],{},"2",[110,127,128],{},[35,129,130],{},"pub-89xxxxxxxxx575",[110,132,133],{},"그 광고 시스템 안에서 나(퍼블리셔)를 식별하는 계정 ID",[92,135,136,139,144],{},[110,137,138],{},"3",[110,140,141],{},[35,142,143],{},"DIRECT",[110,145,146,147,149,150],{},"나와 광고 시스템의 관계. 내가 직접 계약한 관계면 ",[35,148,143],{},", 중개상을 거치면 ",[35,151,152],{},"RESELLER",[92,154,155,158,163],{},[110,156,157],{},"4",[110,159,160],{},[35,161,162],{},"f08c47fec0942fa0",[110,164,165],{},"광고 시스템(여기선 구글)의 인증 기관 ID. 계정마다 다른 값이 아니라 구글을 쓰는 모든 사이트가 공통으로 갖는 고정값",[15,167,168],{},"즉 이 한 줄은 \"구글이 내 사이트의 광고 인벤토리를 직접 판매할 정당한 권한을 갖고 있다\"는 걸 광고주\u002F거래소 쪽에 선언하는 용도다. ads.txt가 만들어진 이유 자체가 \"이 사이트 광고 인벤토리는 나만 팔 수 있다\"를 공개적으로 못박아서, 제3자가 내 사이트인 척 가짜 인벤토리를 파는 도메인 스푸핑 사기를 막기 위함이다.",[15,170,171,172,175,176,179],{},"혹시 구글 크롤러만 차단되는 건 아닌지 ",[35,173,174],{},"Mediapartners-Google",", ",[35,177,178],{},"AdsBot-Google"," User-Agent로도 다시 요청해봤지만 결과는 동일했다. 리다이렉트도 http → https 한 번뿐이라 문제가 될 범위가 아니었고, 파일을 hex 덤프로 열어 BOM이나 숨은 문자가 섞였는지도 확인했지만 깨끗했다.",[15,181,182,183,187],{},"즉, ",[184,185,186],"strong",{},"코드\u002F설정 쪽에는 고칠 게 없었다."," 이런 경우 남는 건 구글 쪽 크롤러의 재검증 주기 지연인데, 실제로 애드센스는 ads.txt를 실시간이 아니라 주기적으로만 재크롤링하기 때문에 파일이 정상이어도 예전 경고가 한동안 남아있는 경우가 흔하다고 한다.",[10,189,191],{"id":190},"그런데-색인된-페이지-수가-이상하게-적다","그런데 색인된 페이지 수가 이상하게 적다",[15,193,194],{},"ads.txt는 결론이 났지만, 서치콘솔을 열어보니 다른 게 눈에 띄었다. 블로그 글은 30개가 넘는데 색인된 페이지는 겨우 7개뿐이었다. 다만 이 숫자를 100% 자연 크롤링 결과로 보기는 애매한 게, 예전에 URL 검사 도구로 몇몇 페이지를 직접 \"색인 생성 요청\"했던 기억이 어렴풋이 있다. 정확히 몇 개를 수동으로 요청했는지는 기억이 가물가물해서, 7개 중 일부는 수동 요청 덕분에 색인된 걸 수도 있다는 점은 감안해야 한다. 그래도 30개 넘는 글 중 7개는 누가 봐도 낮은 비율이라, 이건 진짜 문제일 가능성이 높아서 사이트맵부터 열어봤다.",[40,196,198],{"className":42,"code":197,"language":44,"meta":45,"style":45},"curl -sS \"https:\u002F\u002Fiambilly-blog.vercel.app\u002Fsitemap.xml\"\n",[35,199,200],{"__ignoreMap":45},[49,201,202,204,206],{"class":51,"line":52},[49,203,56],{"class":55},[49,205,60],{"class":59},[49,207,208],{"class":66}," \"https:\u002F\u002Fiambilly-blog.vercel.app\u002Fsitemap.xml\"\n",[15,210,211],{},"그런데 이상한 URL들이 섞여 있었다.",[40,213,216],{"className":214,"code":215,"language":79},[77],"\u002Fblog\u002Fcss-layouts-copy\n\u002Fblog\u002Fcss-layouts%20copy       \u003C- 공백이 그대로 인코딩된 URL\n\u002Fblog\u002Fnuxt-useasyncdata-cache-error\n\u002Fblog\u002Fnuxt-useAsyncData-cache-error   \u003C- 대소문자만 다른 중복 URL\n",[35,217,215],{"__ignoreMap":45},[15,219,220],{},"같은 글인데 URL이 두 개씩 존재하고 있었다.",[10,222,224],{"id":223},"원인-파일명-그대로-라우트를-만드는-코드","원인: 파일명 그대로 라우트를 만드는 코드",[15,226,227,230],{},[35,228,229],{},"nuxt.config.ts","에 이런 코드가 있었다.",[40,232,236],{"className":233,"code":234,"language":235,"meta":45,"style":45},"language-js shiki shiki-themes github-light github-dark","nitro: {\n  prerender: {\n    crawlLinks: true,\n    routes: await (async () => {\n      const blogFiles = await fs.readdir(resolve('content\u002Fblog'));\n      const blogRoutes = blogFiles.map(file => `\u002Fblog\u002F${file.replace('.md', '')}`);\n      return [...blogRoutes];\n    })(),\n  },\n},\n","js",[35,237,238,247,255,270,297,332,387,402,408,414],{"__ignoreMap":45},[49,239,240,243],{"class":51,"line":52},[49,241,242],{"class":55},"nitro",[49,244,246],{"class":245},"sVt8B",": {\n",[49,248,250,253],{"class":51,"line":249},2,[49,251,252],{"class":55},"  prerender",[49,254,246],{"class":245},[49,256,258,261,264,267],{"class":51,"line":257},3,[49,259,260],{"class":55},"    crawlLinks",[49,262,263],{"class":245},": ",[49,265,266],{"class":59},"true",[49,268,269],{"class":245},",\n",[49,271,273,276,278,282,285,288,291,294],{"class":51,"line":272},4,[49,274,275],{"class":55},"    routes",[49,277,263],{"class":245},[49,279,281],{"class":280},"szBVR","await",[49,283,284],{"class":245}," (",[49,286,287],{"class":280},"async",[49,289,290],{"class":245}," () ",[49,292,293],{"class":280},"=>",[49,295,296],{"class":245}," {\n",[49,298,300,303,306,309,312,315,318,321,324,326,329],{"class":51,"line":299},5,[49,301,302],{"class":280},"      const",[49,304,305],{"class":59}," blogFiles",[49,307,308],{"class":280}," =",[49,310,311],{"class":280}," await",[49,313,314],{"class":245}," fs.",[49,316,317],{"class":55},"readdir",[49,319,320],{"class":245},"(",[49,322,323],{"class":55},"resolve",[49,325,320],{"class":245},[49,327,328],{"class":66},"'content\u002Fblog'",[49,330,331],{"class":245},"));\n",[49,333,335,337,340,342,345,348,350,354,357,360,362,365,368,370,373,375,378,381,384],{"class":51,"line":334},6,[49,336,302],{"class":280},[49,338,339],{"class":59}," blogRoutes",[49,341,308],{"class":280},[49,343,344],{"class":245}," blogFiles.",[49,346,347],{"class":55},"map",[49,349,320],{"class":245},[49,351,353],{"class":352},"s4XuR","file",[49,355,356],{"class":280}," =>",[49,358,359],{"class":66}," `\u002Fblog\u002F${",[49,361,353],{"class":245},[49,363,364],{"class":66},".",[49,366,367],{"class":55},"replace",[49,369,320],{"class":66},[49,371,372],{"class":66},"'.md'",[49,374,175],{"class":66},[49,376,377],{"class":66},"''",[49,379,380],{"class":66},")",[49,382,383],{"class":66},"}`",[49,385,386],{"class":245},");\n",[49,388,390,393,396,399],{"class":51,"line":389},7,[49,391,392],{"class":280},"      return",[49,394,395],{"class":245}," [",[49,397,398],{"class":280},"...",[49,400,401],{"class":245},"blogRoutes];\n",[49,403,405],{"class":51,"line":404},8,[49,406,407],{"class":245},"    })(),\n",[49,409,411],{"class":51,"line":410},9,[49,412,413],{"class":245},"  },\n",[49,415,417],{"class":51,"line":416},10,[49,418,419],{"class":245},"},\n",[15,421,422,425,426,429,430,433],{},[35,423,424],{},"content\u002Fblog"," 폴더의 ",[184,427,428],{},"파일명을 그대로"," URL로 바꿔서 프리렌더 라우트 목록을 만드는 코드였다. 문제는 실제 글 조회는 ",[35,431,432],{},"@nuxt\u002Fcontent","가 슬러그화(소문자 변환, 공백 → 하이픈)한 경로 기준으로 동작한다는 점이다.",[15,435,436],{},"그래서 파일명에 대문자나 공백이 섞인 글에서 URL이 갈라졌다.",[86,438,439,452],{},[89,440,441],{},[92,442,443,446,449],{},[95,444,445],{},"실제 파일",[95,447,448],{},"정상 동작 URL",[95,450,451],{},"이 코드가 만든 깨진 URL",[105,453,454,471],{},[92,455,456,461,466],{},[110,457,458],{},[35,459,460],{},"nuxt-useAsyncData-cache-error.md",[110,462,463],{},[35,464,465],{},"\u002Fblog\u002Fnuxt-useasyncdata-cache-error",[110,467,468],{},[35,469,470],{},"\u002Fblog\u002Fnuxt-useAsyncData-cache-error",[92,472,473,479,484],{},[110,474,475,478],{},[35,476,477],{},"css-layouts copy.md"," (파일명 자체도 잘못 지어짐)",[110,480,481],{},[35,482,483],{},"\u002Fblog\u002Fcss-layouts-copy",[110,485,486],{},[35,487,488],{},"\u002Fblog\u002Fcss-layouts%20copy",[15,490,491],{},"실제로 오른쪽 URL에 접속해보면 200 응답은 오는데 본문이 텅 비어 있었다. 빌드 시점에 저 파일명 그대로 정적 페이지가 만들어졌는데, 콘텐츠 조회 시 슬러그가 안 맞아서 데이터를 못 찾은 것이다. 그리고 이 빈 페이지들이 사이트맵에도 그대로 올라가서 구글에 노출되고 있었다.",[15,493,494],{},"구글 입장에서는 \"응답은 오는데 내용이 없는\" URL을 계속 크롤링하게 되는 셈이라, 크롤 예산이 낭비되고 정상 글의 색인도 늦어질 수 있는 구조였다.",[10,496,498],{"id":497},"고친-방법","고친 방법",[500,501,503],"h3",{"id":502},"_1-파일명-기반-라우트-생성-코드-제거","1. 파일명 기반 라우트 생성 코드 제거",[40,505,507],{"className":233,"code":506,"language":235,"meta":45,"style":45},"nitro: {\n  prerender: {\n    crawlLinks: true,\n  },\n},\n",[35,508,509,515,521,531,535],{"__ignoreMap":45},[49,510,511,513],{"class":51,"line":52},[49,512,242],{"class":55},[49,514,246],{"class":245},[49,516,517,519],{"class":51,"line":249},[49,518,252],{"class":55},[49,520,246],{"class":245},[49,522,523,525,527,529],{"class":51,"line":257},[49,524,260],{"class":55},[49,526,263],{"class":245},[49,528,266],{"class":59},[49,530,269],{"class":245},[49,532,533],{"class":51,"line":272},[49,534,413],{"class":245},[49,536,537],{"class":51,"line":299},[49,538,419],{"class":245},[15,540,541,542,545,546,549],{},"애초에 이 코드는 불필요했다. ",[35,543,544],{},"crawlLinks: true","와 ",[35,547,548],{},"@nuxtjs\u002Fsitemap"," 모듈이 콘텐츠 컬렉션의 정상 경로를 기준으로 알아서 라우트를 찾아주기 때문이다.",[500,551,553],{"id":552},"_2-이름이-잘못된-파일-정리","2. 이름이 잘못된 파일 정리",[15,555,556,558],{},[35,557,477],{},"는 예전에 다른 글을 복사해서 새 글을 쓴 흔적이 파일명에 그대로 남아있던 케이스였다. 실제 내용은 \"선언적 프로그래밍 vs 절차형 프로그래밍\"이라 파일명을 내용에 맞게 바꿨다.",[40,560,562],{"className":42,"code":561,"language":44,"meta":45,"style":45},"git mv \"content\u002Fblog\u002Fcss-layouts copy.md\" \\\n       \"content\u002Fblog\u002Fdeclarative-vs-procedural-programming.md\"\n",[35,563,564,578],{"__ignoreMap":45},[49,565,566,569,572,575],{"class":51,"line":52},[49,567,568],{"class":55},"git",[49,570,571],{"class":66}," mv",[49,573,574],{"class":66}," \"content\u002Fblog\u002Fcss-layouts copy.md\"",[49,576,577],{"class":59}," \\\n",[49,579,580],{"class":51,"line":249},[49,581,582],{"class":66},"       \"content\u002Fblog\u002Fdeclarative-vs-procedural-programming.md\"\n",[10,584,585],{"id":585},"검증",[15,587,588],{},"로컬에서 빌드 후 사이트맵을 다시 확인했다.",[40,590,592],{"className":42,"code":591,"language":44,"meta":45,"style":45},"curl -sS \"http:\u002F\u002Flocalhost:3000\u002Fsitemap.xml\" | grep -c \"\u003Cloc>\"\n",[35,593,594],{"__ignoreMap":45},[49,595,596,598,600,603,606,609,612],{"class":51,"line":52},[49,597,56],{"class":55},[49,599,60],{"class":59},[49,601,602],{"class":66}," \"http:\u002F\u002Flocalhost:3000\u002Fsitemap.xml\"",[49,604,605],{"class":280}," |",[49,607,608],{"class":55}," grep",[49,610,611],{"class":59}," -c",[49,613,614],{"class":66}," \"\u003Cloc>\"\n",[15,616,617,618,621,622,625,626,629],{},"수정 전에는 중복\u002F깨진 URL이 섞여 있었지만, 수정 후에는 실제 글 31개 + ",[35,619,620],{},"\u002Fblog"," + ",[35,623,624],{},"\u002Flog",", 정확히 ",[184,627,628],{},"33개","만 깔끔하게 남았다.",[10,631,632],{"id":632},"정리",[86,634,635,648],{},[89,636,637],{},[92,638,639,642,645],{},[95,640,641],{},"문제",[95,643,644],{},"원인",[95,646,647],{},"이번 수정으로 해결됐나",[105,649,650,661],{},[92,651,652,655,658],{},[110,653,654],{},"애드센스 ads.txt 경고",[110,656,657],{},"구글 쪽 재크롤링 지연 (파일 자체는 정상)",[110,659,660],{},"아니오 — 코드 문제가 아니라서 별도 조치 불가, 시간이 해결",[92,662,663,666,669],{},[110,664,665],{},"색인 페이지 수 부족",[110,667,668],{},"파일명 기반 라우트 생성 코드의 슬러그 불일치로 사이트맵에 빈 URL 노출",[110,670,671],{},"부분적으로 해결 — 버그는 제거했지만 구글이 기존에 알던 URL을 색인에서 완전히 빼는 데는 시간이 걸릴 수 있음",[15,673,674],{},"무서운 경고 문구를 받으면 일단 화면에 보이는 문구 그대로 믿기보다, 실제로 무엇이 어떻게 동작하는지 하나씩 curl로 찍어보는 게 제일 빠르다는 걸 다시 느꼈다. 그리고 예상과 다른 곳(ads.txt가 아니라 사이트맵)에서 진짜 원인을 찾은 것도 재밌는 경험이었다.",[676,677,678],"style",{},"html pre.shiki code .sScJk, html code.shiki .sScJk{--shiki-default:#6F42C1;--shiki-dark:#B392F0}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 .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 .sVt8B, html code.shiki .sVt8B{--shiki-default:#24292E;--shiki-dark:#E1E4E8}html pre.shiki code .szBVR, html code.shiki .szBVR{--shiki-default:#D73A49;--shiki-dark:#F97583}html pre.shiki code .s4XuR, html code.shiki .s4XuR{--shiki-default:#E36209;--shiki-dark:#FFAB70}",{"title":45,"searchDepth":249,"depth":249,"links":680},[681,682,683,684,685,689,690],{"id":12,"depth":249,"text":13},{"id":29,"depth":249,"text":30},{"id":190,"depth":249,"text":191},{"id":223,"depth":249,"text":224},{"id":497,"depth":249,"text":498,"children":686},[687,688],{"id":502,"depth":257,"text":503},{"id":552,"depth":257,"text":553},{"id":585,"depth":249,"text":585},{"id":632,"depth":249,"text":632},"구글 애드센스가 ads.txt 문제를 알려왔는데, 확인해보니 파일은 멀쩡했다. 대신 서치콘솔 색인 수가 적었던 진짜 원인을 Nuxt Content 슬러그 불일치 버그에서 찾은 과정을 정리한다.","md",{"date":694,"field":695,"tags":696},"2026.09.17","blog",[697,698,699],"seo","adsense","nuxt-content",true,"\u002Fko\u002Fadsense-ads-txt-and-seo-indexing-bug",{"title":5,"description":691},{"loc":701},"ko\u002Fadsense-ads-txt-and-seo-indexing-bug","gGiREhecYi7-31x9xc1tqRVEXWee068MjkTYoedB-W0",[707,710,720,728,736,743,754,762,770,782,790,799,805,812,819,826,832,841,851,858,864,871,878,886,893,900,909,916,923,930,938,945,953,959],{"path":701,"title":5,"description":691,"meta":708},{"date":694,"field":695,"tags":709},[697,698,699],{"path":711,"title":712,"description":713,"meta":714},"\u002Fko\u002Fcss-layouts","CSS Flexbox 완전 정복 가이드","Flexbox를 활용한 반응형 레이아웃 구현 방법과 실전 예제. 수직 정렬, 네비게이션 바, 카드 레이아웃까지 완벽 정리!",{"date":715,"image":716,"field":717,"tags":718},"2025.01.25","css.jpg","frontend",[719],"css",{"path":721,"title":722,"description":723,"meta":724},"\u002Fko\u002Fcss-login-form","로그인","로그인 UI를 그려보자",{"date":725,"field":717,"tags":726},"2025.03.29",[727,719],"html",{"path":729,"title":730,"description":731,"meta":732},"\u002Fko\u002Fcsv-parser-cleaning","복잡한 CSV 데이터를 안전하게 파싱하는 방법","JavaScript에서 큰따옴표, 줄바꿈, 쉼표 등 복잡한 CSV 데이터를 안전하게 객체로 변환하는 전체 과정 설명",{"date":733,"field":717,"tags":734},"2025.06.19",[735],"javascript",{"path":737,"title":738,"description":739,"meta":740},"\u002Fko\u002Fdebounce-guide","debounce와 throttle이란??","반복되는 함수 호출을 제어하는 기술인 디바운스(debounce)와 쓰로틀(throttle)을 알아보자.",{"date":741,"field":717,"tags":742},"2025.03.26",[735],{"path":744,"title":745,"description":746,"meta":747},"\u002Fko\u002Fdeclarative-vs-procedural-programming","선언적 프로그래밍 vs 절차형 프로그래밍","선언적 프로그래밍과 절차형 프로그래밍은 서로 대비되는 대표적인 패러다임이다. 두 패러다임의 차이를 알아보고, 각 패러다임에 해당하는 언어와 Vue\u002FReact의 특징을 살펴보자.",{"date":748,"field":749,"tags":750},"2025.05.24","concepts",[751,752,753],"paradigm","vue","react",{"path":755,"title":756,"description":757,"meta":758},"\u002Fko\u002Fdesign-guide","Design-Guide","A comprehensive guide on design principles and best practices for developers.",{"date":759,"field":717,"tags":760},"2025.01.19",[761],"design-system",{"path":763,"title":764,"description":765,"meta":766},"\u002Fko\u002Fdikw-pyramid","DIKW 피라미드 정리: 데이터가 지혜가 되기까지","Data, Information, Knowledge, Wisdom 4단계로 구성된 DIKW 피라미드 개념을 로그\u002F분석 데이터 같은 실제 개발 사례에 대입해서 정리한다.",{"date":767,"field":749,"tags":768},"2026.09.18",[769],"data",{"path":771,"title":772,"description":773,"meta":774},"\u002Fko\u002Fdisplaying-commits-with-github-actions","깃허브 액션으로 커밋 로그를 블로그에 띄우기: Nuxt와 Vercel 활용법","깃허브 액션을 통해 커밋 데이터를 자동으로 수집하고, Nuxt와 Vercel로 UI에 노출하는 방법을 소개합니다.",{"date":775,"image":776,"field":777,"tags":778},"2025.02.28","github-commits.jpg","infra",[779,780,781],"github-actions","vercel","nuxt",{"path":783,"title":784,"description":785,"meta":786},"\u002Fko\u002Fdocker-auto-build-run","로컬에서 커밋 시 도커 빌드 후 컨테이너 시작하기","로컬에서 커밋 시 도커 빌드 후 컨테이너 시작하기.",{"date":787,"field":777,"tags":788},"2025.03.10",[789],"docker",{"path":791,"title":792,"description":793,"meta":794},"\u002Fko\u002Fhow-to-mount-storage","라즈베리파이4에 저장장치 마운트 시키기","라즈베리파이에 저장장치를 연결하고 파티션 생성부터 포맷, 마운트까지의 전체 과정을 정리한 기록입니다.",{"date":795,"field":777,"tags":796},"2025.07.11",[797,798],"raspberry-pi","linux",{"path":800,"title":801,"description":801,"meta":802},"\u002Fko\u002Fhtml","HTML이란 무엇인가",{"date":715,"image":803,"field":717,"tags":804},"html.jpg",[727],{"path":806,"title":807,"description":808,"meta":809},"\u002Fko\u002Fhtml2","미치광이 카드 컴포넌트","미치광이 카드 컴포넌트는 어떤 느낌일까",{"date":810,"field":717,"tags":811},"2025.03.28",[727,719],{"path":813,"title":814,"description":814,"meta":815},"\u002Fko\u002Fhttps-guide","Let's Encrypt를 이용해서 HTTPS 적용하기",{"date":816,"field":777,"tags":817},"2025.03.11",[818],"https",{"path":820,"title":821,"description":822,"meta":823},"\u002Fko\u002Fjs-data-01","JavaScript 데이터01","JavaScript 데이터 다루는 문제를 풀어보자",{"date":824,"field":717,"tags":825},"2025.03.08",[735],{"path":827,"title":828,"description":829,"meta":830},"\u002Fko\u002Fjs-data-02","JavaScript 데이터02","복잡한 데이터 구조를 다루는 실전 문제를 해결해보자.",{"date":824,"field":717,"tags":831},[735],{"path":833,"title":834,"description":835,"meta":836},"\u002Fko\u002Fmarkdown-guid","마크다운 문법","마크다운 문법을 알아보자",{"date":837,"image":838,"field":695,"tags":839},"2025.03.02","markdown.webp",[840],"markdown",{"path":842,"title":843,"description":844,"meta":845},"\u002Fko\u002Fminio-guide","MinIO 완벽 가이드: 설치부터 Node.js 연동까지","MinIO는 오픈소스 기반의 고성능 객체 스토리지 서버입니다. AWS S3와 호환되는 API를 제공하여 클라우드 네이티브 애플리케이션을 위한 완벽한 스토리지 솔루션으로 자리잡고 있습니다.",{"date":846,"field":847,"tags":848},"2025.11.23","backend",[849,850],"minio","nodejs",{"path":852,"title":853,"description":854,"meta":855},"\u002Fko\u002Fnode-simple-server","Node.js 간단한 서버 만들기","Node.js로 간단한 HTTP 서버를 만드는 방법",{"date":856,"field":847,"tags":857},"2025.01.30",[850],{"path":859,"title":860,"description":861,"meta":862},"\u002Fko\u002Fnuxt-colormode","Nuxt3 다크모드 적용하기","Nuxt3에 다크모드 적용하여 관리하기",{"date":725,"field":717,"tags":863},[781],{"path":865,"title":866,"description":867,"meta":868},"\u002Fko\u002Fnuxt-content-vercel-empty-query-sqlite","배포만 하면 블로그 글이 하나도 안 보이던 버그: Nuxt Content와 Vercel의 \u002Ftmp 문제","로컬에서는 멀쩡한데 Vercel에 올리면 queryCollection()이 빈 배열만 반환하던 버그를 추적한 과정. 원인은 @nuxt\u002Fcontent가 Vercel의 읽기 전용 파일시스템을 지원하기 전 버전에 고정돼 있었던 것.",{"date":869,"field":695,"tags":870},"2026.10.07",[699,780],{"path":872,"title":873,"description":874,"meta":875},"\u002Fko\u002Fnuxt-runtimeconfig-appconfig","Nuxt 설정 runtimeConfig vs app.config 완전 정복","Nuxt3에서 전역 설정을 어떻게 관리하고, 어떤 기준으로 나누어야 할까? 다크모드 예시부터 실무 기준 설정 전략까지 총정리",{"date":876,"field":717,"tags":877},"2025.06.16",[781],{"path":879,"title":880,"description":881,"meta":882},"\u002Fko\u002Fnuxt-useasyncdata-cache-error","useAsyncData 캐싱 문제와 Key 동적 설정으로 해결하기","이전 포스트의 데이터가 계속 출력되는 캐싱 관련 문제를 해결하는 방법",{"date":883,"field":717,"tags":884,"image":885},"2025.01.24",[781],"cache.jpg",{"path":887,"title":888,"description":889,"meta":890},"\u002Fko\u002Fnuxt-vercel-sql-error","Nuxt 3에서 Vercel 배포 시 발생한 md 파일 로딩 문제 해결기","Nuxt 3 웹 어플리케이션에서 Vercel 배포 환경에서 발생한 md 파일 로딩 문제를 분석하고 해결한 방법에 대해 공유합니다.",{"date":883,"image":891,"field":717,"tags":892},"ssg.jpg",[781,699,780],{"path":894,"title":895,"description":896,"meta":897},"\u002Fko\u002Fnuxt3-pinia-guide","nuxt3 Pinia 적용하기","Nuxt3 프로젝트에서 Pinia를 적용해보자 @pinia\u002Fnuxt",{"date":725,"field":717,"tags":898},[781,899],"pinia",{"path":901,"title":902,"description":903,"meta":904},"\u002Fko\u002Fnuxt3-unit-test","Nuxt 프로젝트에 유닛 테스트 코드 추가하기","Nuxt 3 프로젝트에서 Vitest를 활용한 테스트 환경 설정과 유틸리티 함수 테스트 방법",{"date":905,"image":906,"field":717,"tags":907},"2025.03.07","unit-test.jpg",[781,908],"vitest",{"path":910,"title":911,"description":912,"meta":913},"\u002Fko\u002Fpm2-guide","프로세스 매니저 PM2","배포환경에서 유용하게 사용할 수 있는 프로세스 매니저를 알아보자.",{"date":787,"field":777,"tags":914},[915,850],"pm2",{"path":917,"title":918,"description":919,"meta":920},"\u002Fko\u002Freact-createportal","React createPortal 완벽 가이드","React의 `createPortal`을 활용한 UI 구현법, 활용 사례, 장단점 및 코드 예제를 소개합니다.",{"date":921,"field":717,"tags":922},"2025.01.27",[753],{"path":924,"title":925,"description":926,"meta":927},"\u002Fko\u002Frouting","Routing","Nuxt file-system routing creates a route for every file in the pages\u002F directory.",{"date":928,"field":717,"tags":929},"2025.01.22",[781],{"path":931,"title":932,"description":933,"meta":934},"\u002Fko\u002Ftanstack-query-guide","TanStack Query란 무엇인가?","TanStack Query가 웹 애플리케이션에서 데이터 가져오기, 캐싱, 상태 관리를 어떻게 개선하는지 알아본다.",{"date":935,"field":717,"tags":936},"2025.04.15",[753,937],"tanstack-query",{"path":939,"title":940,"description":941,"meta":942},"\u002Fko\u002Fuse-context","리엑트 상태관리 useContext()","React에서 `useContext`를 사용하면 컴포넌트 트리에 깊이 중첩된 컴포넌트에서도 prop drilling 없이 상태를 공유할 수 있습니다. 이 글에서는 `useContext`의 개념, 사용 방법, 장단점, 그리고 실전 예제를 함께 알아보겠습니다.",{"date":943,"field":717,"tags":944},"2025.02.03",[753],{"path":946,"title":947,"description":948,"meta":949},"\u002Fko\u002Fvite-plugin-style-inject","리엑티 컴포넌트를 패키지 빌드 시 CSS 같이 적용시키기","dist에 css에 파일이 생성되었으나, 적용 안 되는 이유를 알아보고 에러를 처리하자",{"date":950,"field":717,"tags":951},"2025.04.06",[952,753],"vite",{"path":954,"title":955,"description":956,"meta":957},"\u002Fko\u002Fvue-component-guide","Vue의 \u003Ccomponent :is \u002F> 완벽 정리: 동적 렌더링의 모든 것","Vue에서 \u003Ccomponent :is \u002F>를 활용한 동적 컴포넌트 렌더링 방법과 VNode, 컴포넌트 객체의 차이를 실전 예제로 이해하기",{"date":715,"field":717,"tags":958},[752],{"path":960,"title":961,"description":962,"meta":963},"\u002Fko\u002Fvue-query-ref-value-trap-nuxt3","Vue Query에서 데이터가 안 보이는 이유? Nuxt 3와 Ref 구조의 함정","Nuxt 3 + vue-query를 사용할 때 API 데이터가 `v-if` 조건문에서 제대로 표시되지 않는 현상을 해결한 경험을 바탕으로, Ref 구조와 template 디렉티브에서의 차이를 자세히 설명합니다.",{"date":964,"field":717,"tags":965},"2025.06.08",[781,752,937],1791391656116]