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