[{"data":1,"prerenderedAt":2657},["ShallowReactive",2],{"navigation_docs_ko":3,"-ko-project-brobean-brobean-dev-log-12":267,"-ko-project-brobean-brobean-dev-log-12-surround":2652},[4,130,253],{"title":5,"icon":6,"path":7,"stem":8,"children":9,"page":33},"Note (개발 지식)","emojione-v1:note-pad","\u002Fko\u002Fnote","ko\u002F1.note",[10,34,48,74,84,94,110,120],{"title":11,"icon":12,"path":13,"stem":14,"children":15,"page":33},"Spring","logos:spring-icon","\u002Fko\u002Fnote\u002Fspring","ko\u002F1.note\u002F0.spring",[16,21,25,29],{"title":17,"path":18,"stem":19,"icon":20},"Spring AI 핵심 개념과 LLM 애플리케이션 구조 이해","\u002Fko\u002Fnote\u002Fspring\u002Fspring-ai","ko\u002F1.note\u002F0.spring\u002F1.spring-ai","lucide:file-text",{"title":22,"path":23,"stem":24,"icon":20},"[Java] Record와 @Version으로 이해하는 불변 데이터와 동시성 제어","\u002Fko\u002Fnote\u002Fspring\u002Frecord-and-version-concurrency","ko\u002F1.note\u002F0.spring\u002F2.record-and-version-concurrency",{"title":26,"path":27,"stem":28,"icon":20},"[JPA] @PersistenceContext와 EntityManager로 이해하는 영속성 컨텍스트","\u002Fko\u002Fnote\u002Fspring\u002Fspring-persistence-context","ko\u002F1.note\u002F0.spring\u002F3.spring-persistence-context",{"title":30,"path":31,"stem":32,"icon":20},"[Java] YearMonth로 월 단위 날짜를 다루는 방법","\u002Fko\u002Fnote\u002Fspring\u002Fjava-year-month","ko\u002F1.note\u002F0.spring\u002F4.java-year-month",false,{"title":35,"icon":36,"path":37,"stem":38,"children":39,"page":33},"Javascript","skill-icons:javascript","\u002Fko\u002Fnote\u002Fjavascript","ko\u002F1.note\u002F1.javascript",[40,44],{"title":41,"path":42,"stem":43,"icon":20},"Promise.all과 구조 분해 할당을 활용한 데이터 처리 패턴","\u002Fko\u002Fnote\u002Fjavascript\u002Fdestructuring","ko\u002F1.note\u002F1.javascript\u002F1.destructuring",{"title":45,"path":46,"stem":47,"icon":20},"JavaScript 배열 고차 함수와 데이터 처리 패턴 이해","\u002Fko\u002Fnote\u002Fjavascript\u002Ffilter","ko\u002F1.note\u002F1.javascript\u002F2.filter",{"title":49,"icon":50,"path":51,"stem":52,"children":53,"page":33},"Nuxt4","logos:nuxt-icon","\u002Fko\u002Fnote\u002Fnuxt4","ko\u002F1.note\u002F2.nuxt4",[54,58,62,66,70],{"title":55,"path":56,"stem":57,"icon":20},"Nuxt 4를 선택한 이유와 아키텍처 및 실무 가이드","\u002Fko\u002Fnote\u002Fnuxt4\u002Fnuxt-info","ko\u002F1.note\u002F2.nuxt4\u002F1.nuxt-info",{"title":59,"path":60,"stem":61,"icon":20},"Nuxt useAsyncData 동작 원리와 SSR 데이터 페칭 최적화","\u002Fko\u002Fnote\u002Fnuxt4\u002Fnuxt-use_async_data","ko\u002F1.note\u002F2.nuxt4\u002F2.nuxt-use_async_data",{"title":63,"path":64,"stem":65,"icon":20},"Nuxt Content v3 데이터 모델과 Query 시스템 이해","\u002Fko\u002Fnote\u002Fnuxt4\u002Fnuxt-content-v3","ko\u002F1.note\u002F2.nuxt4\u002F3.nuxt-content-v3",{"title":67,"path":68,"stem":69,"icon":20},"Nuxt Auto-imports 내부 동작과 컴파일 과정 분석","\u002Fko\u002Fnote\u002Fnuxt4\u002Fnuxt-auto-import","ko\u002F1.note\u002F2.nuxt4\u002F4.nuxt-auto-import",{"title":71,"path":72,"stem":73,"icon":20},"Nuxt 렌더링 방식 이해: SSR, SSG, ISR, CSR 구현 방법","\u002Fko\u002Fnote\u002Fnuxt4\u002Fnuxt-rendering-strategy","ko\u002F1.note\u002F2.nuxt4\u002F5.nuxt-rendering-strategy",{"title":75,"icon":76,"path":77,"stem":78,"children":79,"page":33},"Nitro","unjs:nitro","\u002Fko\u002Fnote\u002Fnitro","ko\u002F1.note\u002F3.nitro",[80],{"title":81,"path":82,"stem":83,"icon":20},"Nitro 아키텍처와 서버 엔진 구조","\u002Fko\u002Fnote\u002Fnitro\u002Fnitro","ko\u002F1.note\u002F3.nitro\u002Fnitro",{"title":85,"icon":86,"path":87,"stem":88,"children":89,"page":33},"Devops","vscode-icons:file-type-pipeline","\u002Fko\u002Fnote\u002Fdevops","ko\u002F1.note\u002F4.devops",[90],{"title":91,"path":92,"stem":93,"icon":20},"서버사이드 WebAssembly(Wasm)와 차세대 실행 환경","\u002Fko\u002Fnote\u002Fdevops\u002Fwebassembly","ko\u002F1.note\u002F4.devops\u002F1.webAssembly",{"title":95,"icon":96,"path":97,"stem":98,"children":99,"page":33},"Tooling","lucide:wrench","\u002Fko\u002Fnote\u002Ftooling","ko\u002F1.note\u002F4.tooling",[100],{"title":101,"icon":102,"path":103,"stem":104,"children":105,"page":33},"Package Manager","devicon:npm","\u002Fko\u002Fnote\u002Ftooling\u002Fpackage-manager","ko\u002F1.note\u002F4.tooling\u002F1.package-manager",[106],{"title":107,"path":108,"stem":109,"icon":20},"Package Lock 파일의 역할과 의존성 관리 원리","\u002Fko\u002Fnote\u002Ftooling\u002Fpackage-manager\u002Fpackage-lock","ko\u002F1.note\u002F4.tooling\u002F1.package-manager\u002F1.package-lock",{"title":111,"icon":112,"path":113,"stem":114,"children":115,"page":33},"Web","streamline-plump:web","\u002Fko\u002Fnote\u002Fweb","ko\u002F1.note\u002F4.web",[116],{"title":117,"path":118,"stem":119,"icon":20},"웹 애플리케이션 렌더링 아키텍처 이해","\u002Fko\u002Fnote\u002Fweb\u002Frendering-modes","ko\u002F1.note\u002F4.web\u002F2.rendering-modes",{"title":121,"icon":122,"path":123,"stem":124,"children":125,"page":33},"SEO","icon-park-solid:seo","\u002Fko\u002Fnote\u002Fseo","ko\u002F1.note\u002F6.seo",[126],{"title":127,"path":128,"stem":129,"icon":20},"[SEO] 개발 블로그 검색 노출을 위한 robots.txt와 SEO 설정 가이드","\u002Fko\u002Fnote\u002Fseo\u002Fseo","ko\u002F1.note\u002F6.seo\u002F1.seo",{"title":131,"icon":132,"path":133,"stem":134,"children":135,"page":33},"Project (개발일지)","icon-park-solid:branch-two","\u002Fko\u002Fproject","ko\u002F2.project",[136,187],{"title":137,"icon":138,"path":139,"stem":140,"children":141,"page":33},"Brolog","custom:symbol","\u002Fko\u002Fproject\u002Fbrolog","ko\u002F2.project\u002F1.brolog",[142,146,150,154,158,162,166,170,174,179,183],{"title":143,"path":144,"stem":145,"icon":20},"[Brolog] 프로젝트 개요","\u002Fko\u002Fproject\u002Fbrolog\u002Foutline","ko\u002F2.project\u002F1.brolog\u002F1.outline",{"title":147,"path":148,"stem":149,"icon":20},"[Brolog] Nuxt Multi Layer 프로젝트 Vercel 배포 (3) - Runtime 기능 문제 분석과 AI Assistant 전환","\u002Fko\u002Fproject\u002Fbrolog\u002Fvercel-deploy3-ai-integration","ko\u002F2.project\u002F1.brolog\u002F10.vercel-deploy3-ai-integration",{"title":151,"path":152,"stem":153,"icon":20},"[Brolog] AI Assistant 적용 과정 - Agent 구조와 API 사용량 관리 시 고려사항","\u002Fko\u002Fproject\u002Fbrolog\u002Fai-assistant-application-process","ko\u002F2.project\u002F1.brolog\u002F11.ai-assistant-application-process",{"title":155,"path":156,"stem":157,"icon":20},"[Brolog] 검색 엔진 노출을 위한 SEO 적용 과정","\u002Fko\u002Fproject\u002Fbrolog\u002Fbrolog-seo","ko\u002F2.project\u002F1.brolog\u002F12.brolog-seo",{"title":159,"path":160,"stem":161,"icon":20},"[Brolog] 프로젝트 구조","\u002Fko\u002Fproject\u002Fbrolog\u002Fstructure","ko\u002F2.project\u002F1.brolog\u002F2.structure",{"title":163,"path":164,"stem":165,"icon":20},"[Brolog] Nuxt Content 네비게이션 아이콘 커스텀 빌드","\u002Fko\u002Fproject\u002Fbrolog\u002Fcustom-icon","ko\u002F2.project\u002F1.brolog\u002F3.custom-icon",{"title":167,"path":168,"stem":169,"icon":20},"[Brolog] Nuxt4 Auto-imports 동작 원리와 자동 import 시스템 이해","\u002Fko\u002Fproject\u002Fbrolog\u002Fnuxt-auto-import","ko\u002F2.project\u002F1.brolog\u002F4.nuxt-auto-import",{"title":171,"path":172,"stem":173,"icon":20},"[Brolog] 개발 블로그 메인 페이지 구현","\u002Fko\u002Fproject\u002Fbrolog\u002Fdevelop-main","ko\u002F2.project\u002F1.brolog\u002F5.develop-main",{"title":175,"path":176,"stem":177,"icon":178},"[Brolog] 브랜드 아이덴티티 적용 - Custom 404 페이지와 Default No Image 구현","\u002Fko\u002Fproject\u002Fbrolog\u002Fcustom-error-and-default-thumbnail","ko\u002F2.project\u002F1.brolog\u002F6.custom-error-and-default-thumbnail","lucide:image",{"title":180,"path":181,"stem":182,"icon":20},"[Brolog] Nuxt Multi Layer 프로젝트 Vercel 배포 (1)","\u002Fko\u002Fproject\u002Fbrolog\u002Fvercel-deploy1","ko\u002F2.project\u002F1.brolog\u002F8.vercel-deploy1",{"title":184,"path":185,"stem":186,"icon":20},"[Brolog] Nuxt Multi Layer 프로젝트 Vercel 배포 (2) - Build Pipeline 오류 분석과 Nuxt 실행 구조 이해","\u002Fko\u002Fproject\u002Fbrolog\u002Fvercel-deploy2-error-analyze","ko\u002F2.project\u002F1.brolog\u002F9.vercel-deploy2-error-analyze",{"title":188,"icon":189,"path":190,"stem":191,"children":192,"page":33},"Brobean","custom:brobean","\u002Fko\u002Fproject\u002Fbrobean","ko\u002F2.project\u002F2.brobean",[193,197,201,205,209,213,217,221,225,229,233,237,241,245,249],{"title":194,"path":195,"stem":196,"icon":20},"[BroBean] 연인과 함께 사용하는 가계부: BroBean - 프로젝트 기획과 이름의 탄생","\u002Fko\u002Fproject\u002Fbrobean\u002Fbrobean-dev-log-01","ko\u002F2.project\u002F2.brobean\u002F1.brobean-dev-log-01",{"title":198,"path":199,"stem":200,"icon":20},"[BroBean] 연인과 함께 사용하는 가계부: BroBean 프로젝트 시작 - Nuxt 4 기반 프론트 개발 환경 구축","\u002Fko\u002Fproject\u002Fbrobean\u002Fbrobean-dev-log-02","ko\u002F2.project\u002F2.brobean\u002F2.brobean-dev-log-02",{"title":202,"path":203,"stem":204,"icon":20},"[BroBean] BroBean 프로젝트 #3 - 모바일 앱 전환을 고려한 공통 레이아웃 설계","\u002Fko\u002Fproject\u002Fbrobean\u002Fbrobean-dev-log-03","ko\u002F2.project\u002F2.brobean\u002F3.brobean-dev-log-03",{"title":206,"path":207,"stem":208,"icon":20},"[BroBean] BroBean 프로젝트 #4 - 홈 화면(Dashboard) 설계와 Mock 데이터 구성","\u002Fko\u002Fproject\u002Fbrobean\u002Fbrobean-dev-log-04","ko\u002F2.project\u002F2.brobean\u002F4.brobean-dev-log-04",{"title":210,"path":211,"stem":212,"icon":20},"[BroBean] BroBean 프로젝트 #5 돈을 모으면 캐릭터도 성장한다 - 브로빈 성장 시스템 설계","\u002Fko\u002Fproject\u002Fbrobean\u002Fbrobean-dev-log-05","ko\u002F2.project\u002F2.brobean\u002F5.brobean-dev-log-05",{"title":214,"path":215,"stem":216,"icon":20},"[BroBean] BroBean 프로젝트 #6 씨앗 심기와 밭 가꾸기 기능 설계","\u002Fko\u002Fproject\u002Fbrobean\u002Fbrobean-dev-log-06","ko\u002F2.project\u002F2.brobean\u002F6.brobean-dev-log-06",{"title":218,"path":219,"stem":220,"icon":20},"[BroBean] BroBean 프로젝트 #7 성장 일지와 나무 키우기 화면 구현","\u002Fko\u002Fproject\u002Fbrobean\u002Fbrobean-dev-log-07","ko\u002F2.project\u002F2.brobean\u002F7.brobean-dev-log-07",{"title":222,"path":223,"stem":224,"icon":20},"[BroBean] BroBean 프로젝트 #8 수확 정산으로 한눈에 보는 소비 흐름 - 소비 리포트 & 정산 화면 구현","\u002Fko\u002Fproject\u002Fbrobean\u002Fbrobean-dev-log-08","ko\u002F2.project\u002F2.brobean\u002F8.brobean-dev-log-08",{"title":226,"path":227,"stem":228,"icon":20},"[BroBean] BroBean 프로젝트 #9 알림부터 마이페이지까지 - 사용자 편의 기능과 개인 농장 설정 화면 구현","\u002Fko\u002Fproject\u002Fbrobean\u002Fbrobean-dev-log-09","ko\u002F2.project\u002F2.brobean\u002F9.brobean-dev-log-09",{"title":230,"path":231,"stem":232,"icon":20},"[BroBean] BroBean 프로젝트 #10 로그인부터 파트너 초대까지 - 계정 인증과 커플 연결 화면 구현","\u002Fko\u002Fproject\u002Fbrobean\u002Fbrobean-dev-log-10","ko\u002F2.project\u002F2.brobean\u002F910.brobean-dev-log-10",{"title":234,"path":235,"stem":236,"icon":20},"[BroBean] BroBean 프로젝트 #11 개인과 커플을 모두 위한 모드 분리 - Pinia 기반 개인\u002F커플 모드 전환 구현","\u002Fko\u002Fproject\u002Fbrobean\u002Fbrobean-dev-log-11","ko\u002F2.project\u002F2.brobean\u002F911.brobean-dev-log-11",{"title":238,"path":239,"stem":240,"icon":20},"[BroBean] BroBean 프로젝트 #12 인증 상태 복구와 Nuxt Plugin 실행 순서 제어","\u002Fko\u002Fproject\u002Fbrobean\u002Fbrobean-dev-log-12","ko\u002F2.project\u002F2.brobean\u002F912.brobean-dev-log-12",{"title":242,"path":243,"stem":244,"icon":20},"[BroBean] BroBean 프로젝트 #13 개인·커플 모드와 가계부 API 구현 - 쿼리 성능을 다시 돌아보다","\u002Fko\u002Fproject\u002Fbrobean\u002Fbrobean-dev-log-13","ko\u002F2.project\u002F2.brobean\u002F913.brobean-dev-log-13",{"title":246,"path":247,"stem":248,"icon":20},"[BroBean] BroBean 프로젝트 #14 순환 참조 오류를 ObjectProvider로 해결하기","\u002Fko\u002Fproject\u002Fbrobean\u002Fbrobean-dev-log-14","ko\u002F2.project\u002F2.brobean\u002F914.brobean-dev-log-14",{"title":250,"path":251,"stem":252,"icon":20},"[BroBean] BroBean 프로젝트 #15 JPA 페이지네이션 적용, Page와 Slice의 차이","\u002Fko\u002Fproject\u002Fbrobean\u002Fbrobean-dev-log-15","ko\u002F2.project\u002F2.brobean\u002F915.brobean-dev-log-15",{"title":254,"icon":255,"path":256,"stem":257,"children":258,"page":33},"Error (트러블슈팅)","emojione:stop-sign","\u002Fko\u002Ftrouble-shooting","ko\u002F3.trouble-shooting",[259],{"title":137,"icon":138,"path":260,"stem":261,"children":262,"page":33},"\u002Fko\u002Ftrouble-shooting\u002Fbrolog","ko\u002F3.trouble-shooting\u002F1.brolog",[263],{"title":264,"path":265,"stem":266,"icon":20},"[Nuxt Content] Nuxt Icon 오류 해결과 Custom Collection 기반 오프라인 아이콘 구성","\u002Fko\u002Ftrouble-shooting\u002Fbrolog\u002Fnuxt-icon-restricted-network-troubleshooting","ko\u002F3.trouble-shooting\u002F1.brolog\u002F1.nuxt-icon-restricted-network-troubleshooting",{"id":268,"title":238,"body":269,"date":2632,"description":2633,"extension":2634,"links":2635,"meta":2636,"navigation":2647,"path":239,"seo":2648,"stem":240,"__hash__":2651},"docs_ko\u002Fko\u002F2.project\u002F2.brobean\u002F912.brobean-dev-log-12.md",{"type":270,"value":271,"toc":2620},"minimark",[272,2616],[273,274,276,281,285,288,291,294,297,300,303,325,328,331,355,358,361,364,368,371,374,377,387,390,393,518,521,524,530,538,541,544,546,550,553,560,563,663,666,669,672,678,681,688,691,697,700,702,706,712,715,721,724,730,733,738,744,747,750,756,759,762,764,768,771,774,831,834,837,839,845,851,854,861,867,869,873,876,1388,1391,1397,1399,1403,1406,1409,1412,1415,1440,1443,1446,1452,1455,1457,1464,1467,1525,1531,1537,1540,1543,1549,1552,1558,1561,1564,1566,1570,1576,1579,1601,1604,1614,1627,1630,1636,1639,1645,1648,1651,1654,1656,1660,1667,1670,1673,1679,1685,1688,1694,1697,1700,1702,1706,1709,1712,1717,1720,1726,1729,1732,1738,1741,1744,1746,1750,1753,1759,1762,1769,1772,1778,1781,1784,1790,1793,1799,1802,1805,1807,1811,1814,1820,1823,1829,1832,1834,1838,1841,1847,1850,1852,1858,1864,1867,1873,1876,1878,1882,1885,1888,1890,1896,1898,1901,1904,1910,1913,1915,1919,1925,1928,1934,1937,1940,1963,1966,1969,1975,1978,1980,1984,1987,1990,1995,2001,2004,2010,2014,2020,2023,2029,2033,2039,2043,2049,2053,2059,2062,2064,2068,2071,2074,2080,2083,2086,2097,2100,2110,2132,2135,2138,2140,2144,2147,2150,2155,2158,2161,2167,2170,2176,2179,2181,2187,2190,2193,2195,2199,2202,2205,2211,2214,2217,2219,2225,2228,2235,2248,2250,2254,2257,2263,2266,2269,2275,2278,2283,2286,2289,2291,2295,2298,2301,2307,2310,2313,2316,2319,2325,2328,2334,2337,2339,2343,2346,2415,2418,2424,2426,2430,2433,2436,2440,2445,2460,2463,2466,2468,2472,2475,2481,2484,2486,2490,2493,2496,2502,2505,2507,2513,2516,2518,2522,2529,2532,2538,2545,2548,2554,2560,2571,2574,2580,2583,2586,2606,2609],"u-page-card",{":spotlight":275},"true",[277,278,280],"h1",{"id":279},"로그인-이후에도-사용자를-유지하기-인증-상태-복구와-nuxt-plugin-실행-순서","🔐 로그인 이후에도 사용자를 유지하기: 인증 상태 복구와 Nuxt Plugin 실행 순서",[282,283,284],"p",{},"앞선 글에서는 BroBean의 개인\u002F커플 모드를 분리하고, Pinia Store를 이용해 현재 사용자가 어떤 모드로 서비스를 이용하고 있는지 관리하는 구조를 구현했다.",[282,286,287],{},"이제 실제 회원가입과 로그인 백엔드 로직을 연결하면서 한 단계 더 중요한 문제가 드러났다.",[282,289,290],{},"로그인 자체는 정상적으로 동작했다.",[282,292,293],{},"회원가입도 정상적으로 처리되고, 로그인 API를 호출하면 서버에서도 사용자 정보를 정상적으로 반환했다.",[282,295,296],{},"문제는 그 다음이었다.",[282,298,299],{},"브라우저에서 페이지를 새로고침하면 프론트에서 가지고 있던 사용자 정보가 사라졌다.",[282,301,302],{},"결국 로그인 직후에는 정상적으로 보이던:",[304,305,306,310,313,316,319,322],"ul",{},[307,308,309],"li",{},"사용자 이메일",[307,311,312],{},"농장 정보",[307,314,315],{},"닉네임",[307,317,318],{},"파트너 정보",[307,320,321],{},"농장 경험치",[307,323,324],{},"커플 연결 상태",[282,326,327],{},"등의 정보가 새로고침 이후에는 초기화되었다.",[282,329,330],{},"이 문제를 해결하는 과정에서 단순히 Pinia Store를 보완하는 것뿐만 아니라,",[304,332,333,336,339,346,349,352],{},[307,334,335],{},"브라우저 새로고침과 메모리 상태",[307,337,338],{},"서버의 인증 상태와 클라이언트 상태",[307,340,341,345],{},[342,343,344],"code",{},"\u002Fapi\u002Fauth\u002Fme","를 통한 사용자 정보 복구",[307,347,348],{},"Nuxt Plugin의 실행 시점",[307,350,351],{},"Plugin 간 의존성",[307,353,354],{},"Axios Plugin과 Auth Plugin의 실행 순서",[282,356,357],{},"까지 연결해서 살펴보게 되었다.",[282,359,360],{},"이번 글에서는 이 과정을 정리해본다.",[362,363],"hr",{},[277,365,367],{"id":366},"_1-로그인은-정상인데-새로고침하면-사용자가-사라진다","🔎 1. 로그인은 정상인데 새로고침하면 사용자가 사라진다",[282,369,370],{},"로그인 API 자체에는 문제가 없었다.",[282,372,373],{},"사용자가 로그인하면 서버에서는 인증을 처리하고, 프론트에서는 반환된 사용자 정보를 Pinia Store에 저장한다.",[282,375,376],{},"대략적인 흐름은 다음과 같다.",[378,379,385],"pre",{"className":380,"code":382,"language":383,"meta":384},[381],"language-text","로그인 요청\n\n↓\n\nPOST \u002Fapi\u002Fauth\u002Flogin\n\n↓\n\n서버 인증 성공\n\n↓\n\n사용자 정보 반환\n\n↓\n\nPinia Auth Store 저장\n\n↓\n\n화면에서 사용자 정보 사용\n","text","",[342,386,382],{"__ignoreMap":384},[282,388,389],{},"로그인 직후에는 문제가 없다.",[282,391,392],{},"예를 들어 Store에는 다음과 같은 정보가 들어간다.",[378,394,398],{"className":395,"code":396,"language":397,"meta":384,"style":384},"language-ts shiki shiki-themes material-theme-lighter material-theme material-theme-palenight","{\n  email: 'user@example.com',\n  farmName: '알콩달콩 부자농장',\n  name: '콩이',\n  nickname: '콩이언니',\n  partnerNickname: '콩이오빠',\n  isCoupleConnected: true\n}\n","ts",[342,399,400,409,432,449,466,483,500,512],{"__ignoreMap":384},[401,402,405],"span",{"class":403,"line":404},"line",1,[401,406,408],{"class":407},"sMK4o","{\n",[401,410,412,416,419,422,426,429],{"class":403,"line":411},2,[401,413,415],{"class":414},"sBMFI","  email",[401,417,418],{"class":407},":",[401,420,421],{"class":407}," '",[401,423,425],{"class":424},"sfazB","user@example.com",[401,427,428],{"class":407},"'",[401,430,431],{"class":407},",\n",[401,433,435,438,440,442,445,447],{"class":403,"line":434},3,[401,436,437],{"class":414},"  farmName",[401,439,418],{"class":407},[401,441,421],{"class":407},[401,443,444],{"class":424},"알콩달콩 부자농장",[401,446,428],{"class":407},[401,448,431],{"class":407},[401,450,452,455,457,459,462,464],{"class":403,"line":451},4,[401,453,454],{"class":414},"  name",[401,456,418],{"class":407},[401,458,421],{"class":407},[401,460,461],{"class":424},"콩이",[401,463,428],{"class":407},[401,465,431],{"class":407},[401,467,469,472,474,476,479,481],{"class":403,"line":468},5,[401,470,471],{"class":414},"  nickname",[401,473,418],{"class":407},[401,475,421],{"class":407},[401,477,478],{"class":424},"콩이언니",[401,480,428],{"class":407},[401,482,431],{"class":407},[401,484,486,489,491,493,496,498],{"class":403,"line":485},6,[401,487,488],{"class":414},"  partnerNickname",[401,490,418],{"class":407},[401,492,421],{"class":407},[401,494,495],{"class":424},"콩이오빠",[401,497,428],{"class":407},[401,499,431],{"class":407},[401,501,503,506,508],{"class":403,"line":502},7,[401,504,505],{"class":414},"  isCoupleConnected",[401,507,418],{"class":407},[401,509,511],{"class":510},"sfNiH"," true\n",[401,513,515],{"class":403,"line":514},8,[401,516,517],{"class":407},"}\n",[282,519,520],{},"화면에서는 이 정보를 그대로 사용하면 된다.",[282,522,523],{},"문제는 브라우저에서 새로고침을 했을 때 발생한다.",[378,525,528],{"className":526,"code":527,"language":383,"meta":384},[381],"로그인\n\n↓\n\nPinia Store에 사용자 정보 저장\n\n↓\n\n페이지 정상 동작\n\n↓\n\nF5 \u002F 새로고침\n\n↓\n\nPinia Store 초기화\n\n↓\n\n사용자 정보 없음\n",[342,529,527],{"__ignoreMap":384},[282,531,532,533,537],{},"여기서 중요한 것은 ",[534,535,536],"strong",{},"인증 자체가 사라진 것과 프론트의 사용자 상태가 사라진 것은 다른 문제","라는 점이다.",[282,539,540],{},"서버 측 인증 상태가 정상적으로 유지되고 있더라도 클라이언트의 메모리에 올라가 있던 Store 데이터는 새로고침 과정에서 다시 만들어진다.",[282,542,543],{},"즉, 클라이언트 Store를 인증 정보의 영구 저장소처럼 사용하는 구조에는 한계가 있다.",[362,545],{},[277,547,549],{"id":548},"_2-pinia-store는-인증의-원본-데이터가-아니다","🧠 2. Pinia Store는 인증의 원본 데이터가 아니다",[282,551,552],{},"이번 문제를 정리하면서 가장 중요하게 본 부분이다.",[282,554,555,556,559],{},"Pinia Store는 애플리케이션 내부에서 사용자 상태를 공유하기 위한 ",[534,557,558],{},"상태 관리 계층","이다.",[282,561,562],{},"예를 들어:",[378,564,566],{"className":395,"code":565,"language":397,"meta":384,"style":384},"const authStore = useAuthStore()\n\nauthStore.updateProfile({\n  email: data.email,\n  farmName: data.farmName,\n  nickname: data.nickname\n})\n",[342,567,568,588,594,610,627,642,655],{"__ignoreMap":384},[401,569,570,574,578,581,585],{"class":403,"line":404},[401,571,573],{"class":572},"spNyl","const",[401,575,577],{"class":576},"sTEyZ"," authStore ",[401,579,580],{"class":407},"=",[401,582,584],{"class":583},"s2Zo4"," useAuthStore",[401,586,587],{"class":576},"()\n",[401,589,590],{"class":403,"line":411},[401,591,593],{"emptyLinePlaceholder":592},true,"\n",[401,595,596,599,602,605,608],{"class":403,"line":434},[401,597,598],{"class":576},"authStore",[401,600,601],{"class":407},".",[401,603,604],{"class":583},"updateProfile",[401,606,607],{"class":576},"(",[401,609,408],{"class":407},[401,611,612,615,617,620,622,625],{"class":403,"line":451},[401,613,415],{"class":614},"swJcz",[401,616,418],{"class":407},[401,618,619],{"class":576}," data",[401,621,601],{"class":407},[401,623,624],{"class":576},"email",[401,626,431],{"class":407},[401,628,629,631,633,635,637,640],{"class":403,"line":468},[401,630,437],{"class":614},[401,632,418],{"class":407},[401,634,619],{"class":576},[401,636,601],{"class":407},[401,638,639],{"class":576},"farmName",[401,641,431],{"class":407},[401,643,644,646,648,650,652],{"class":403,"line":485},[401,645,471],{"class":614},[401,647,418],{"class":407},[401,649,619],{"class":576},[401,651,601],{"class":407},[401,653,654],{"class":576},"nickname\n",[401,656,657,660],{"class":403,"line":502},[401,658,659],{"class":407},"}",[401,661,662],{"class":576},")\n",[282,664,665],{},"이렇게 저장한 데이터는 애플리케이션이 실행되는 동안 여러 컴포넌트에서 편리하게 사용할 수 있다.",[282,667,668],{},"하지만 브라우저가 새로고침되면 애플리케이션이 다시 초기화된다.",[282,670,671],{},"따라서:",[378,673,676],{"className":674,"code":675,"language":383,"meta":384},[381],"Pinia Store\n\n≠\n\n영구적인 인증 저장소\n",[342,677,675],{"__ignoreMap":384},[282,679,680],{},"라고 보는 것이 적절하다.",[282,682,683,684,687],{},"실제 인증의 기준은 서버에 있어야 하고, 클라이언트의 Store는 ",[534,685,686],{},"현재 로그인한 사용자의 상태를 빠르게 참조하기 위한 캐시 또는 상태 표현 계층","으로 사용하는 것이 자연스럽다.",[282,689,690],{},"결국 필요한 것은 새로고침 이후에도:",[378,692,695],{"className":693,"code":694,"language":383,"meta":384},[381],"\"현재 로그인한 사용자가 누구인지\"\n",[342,696,694],{"__ignoreMap":384},[282,698,699],{},"를 서버에게 다시 확인하고 Store를 복구하는 과정이다.",[362,701],{},[277,703,705],{"id":704},"_3-서버의-현재-사용자-정보를-다시-조회한다","🔐 3. 서버의 현재 사용자 정보를 다시 조회한다",[282,707,708,709,711],{},"이 역할을 담당하는 API가 ",[342,710,344],{},"다.",[282,713,714],{},"이 API의 목적은 단순하다.",[378,716,719],{"className":717,"code":718,"language":383,"meta":384},[381],"현재 요청을 보낸 사용자가 누구인지 확인한다.\n",[342,720,718],{"__ignoreMap":384},[282,722,723],{},"로그인 API가:",[378,725,728],{"className":726,"code":727,"language":383,"meta":384},[381],"\"로그인해 주세요.\"\n\n↓\n\n\"인증되었습니다.\"\n",[342,729,727],{"__ignoreMap":384},[282,731,732],{},"라는 흐름이라면,",[282,734,735,737],{},[342,736,344],{},"는:",[378,739,742],{"className":740,"code":741,"language":383,"meta":384},[381],"\"현재 인증된 사용자가 누구인가요?\"\n\n↓\n\n\"현재 사용자는 이 사용자입니다.\"\n",[342,743,741],{"__ignoreMap":384},[282,745,746],{},"라는 역할을 한다.",[282,748,749],{},"따라서 페이지가 새로 로드되었을 때:",[378,751,754],{"className":752,"code":753,"language":383,"meta":384},[381],"브라우저 실행\n\n↓\n\n현재 인증 상태 확인\n\n↓\n\n\u002Fapi\u002Fauth\u002Fme 요청\n\n↓\n\n사용자 정보 반환\n\n↓\n\nPinia Store 복구\n\n↓\n\n화면 렌더링\n",[342,755,753],{"__ignoreMap":384},[282,757,758],{},"이라는 구조를 만들 수 있다.",[282,760,761],{},"이렇게 하면 Store가 새로 생성되더라도 서버의 인증 상태를 기준으로 다시 사용자 정보를 구성할 수 있다.",[362,763],{},[277,765,767],{"id":766},"_4-이-작업을-어디에서-실행할-것인가","🧩 4. 이 작업을 어디에서 실행할 것인가?",[282,769,770],{},"여기서 Nuxt의 Plugin을 활용했다.",[282,772,773],{},"페이지마다 다음과 같은 코드를 작성하는 방식도 가능하다.",[378,775,777],{"className":395,"code":776,"language":397,"meta":384,"style":384},"const { data } = await $api.get('\u002Fapi\u002Fauth\u002Fme')\n\nauthStore.updateProfile(data)\n",[342,778,779,816,820],{"__ignoreMap":384},[401,780,781,783,786,789,791,794,798,801,803,806,808,810,812,814],{"class":403,"line":404},[401,782,573],{"class":572},[401,784,785],{"class":407}," {",[401,787,788],{"class":576}," data ",[401,790,659],{"class":407},[401,792,793],{"class":407}," =",[401,795,797],{"class":796},"s7zQu"," await",[401,799,800],{"class":576}," $api",[401,802,601],{"class":407},[401,804,805],{"class":583},"get",[401,807,607],{"class":576},[401,809,428],{"class":407},[401,811,344],{"class":424},[401,813,428],{"class":407},[401,815,662],{"class":576},[401,817,818],{"class":403,"line":411},[401,819,593],{"emptyLinePlaceholder":592},[401,821,822,824,826,828],{"class":403,"line":434},[401,823,598],{"class":576},[401,825,601],{"class":407},[401,827,604],{"class":583},[401,829,830],{"class":576},"(data)\n",[282,832,833],{},"하지만 이 방식은 문제가 있다.",[282,835,836],{},"사용자 정보가 필요한 페이지마다 인증 복구 로직이 반복될 수 있기 때문이다.",[282,838,562],{},[378,840,843],{"className":841,"code":842,"language":383,"meta":384},[381],"pages\u002Findex.vue\npages\u002Ffarm.vue\npages\u002Faccount-book.vue\npages\u002Fsavings.vue\npages\u002Fmypage.vue\n",[342,844,842],{"__ignoreMap":384},[282,846,847,848,850],{},"각각에서 ",[342,849,344],{},"를 호출하게 만들면 인증 상태 복구 로직이 특정 페이지에 종속된다.",[282,852,853],{},"이보다는 애플리케이션이 시작될 때 한 번 인증 상태를 확인하고, 공통 Store를 복구하는 편이 구조적으로 더 적합하다.",[282,855,856,857,860],{},"그래서 Nuxt의 ",[342,858,859],{},"plugins"," 디렉토리에 인증 초기화 Plugin을 추가했다.",[378,862,865],{"className":863,"code":864,"language":383,"meta":384},[381],"plugins\u002F\n\n├── axios.ts\n└── auth.ts\n",[342,866,864],{"__ignoreMap":384},[362,868],{},[277,870,872],{"id":871},"_5-authts에서-인증-상태-복구","🚀 5. auth.ts에서 인증 상태 복구",[282,874,875],{},"실제 구현은 다음과 같은 형태다.",[378,877,879],{"className":395,"code":878,"language":397,"meta":384,"style":384},"export default defineNuxtPlugin(async (nuxtApp) => {\n    const authStore = useAuthStore()\n    const { $api } = useNuxtApp()\n    const { formatDate, calculateDaysTogether } = useDateUtils()\n\n    \u002F\u002F 서버 사이드에서는 실행하지 않는다.\n    if (import.meta.server) return\n\n    const route = useRoute()\n\n    \u002F\u002F 인증 관련 페이지에서는 사용자 정보 조회를 생략한다.\n    if (route.path.startsWith('\u002Fauth\u002F')) {\n        return\n    }\n\n    try {\n        const { data } = await $api.get('\u002Fapi\u002Fauth\u002Fme')\n\n        if (data && data.email) {\n            authStore.updateProfile({\n                email: data.email,\n                farmName: data.farmName,\n                name: data.name,\n                nickname: data.nickname,\n                partnerNickname: data.partnerNickname,\n                isCoupleConnected: data.isPartnerVerified,\n                farmStartDate: formatDate(data.farmCreatedAt),\n                daysTogether: calculateDaysTogether(data.farmCreatedAt),\n                farmExp: data.farmExperience,\n            })\n        }\n    } catch (error) {\n        authStore.logout()\n    }\n})\n",[342,880,881,913,927,945,969,973,979,1005,1009,1024,1029,1035,1069,1075,1081,1086,1094,1126,1131,1155,1169,1185,1201,1218,1235,1252,1269,1292,1314,1331,1339,1345,1363,1376,1381],{"__ignoreMap":384},[401,882,883,886,889,892,894,897,900,904,907,910],{"class":403,"line":404},[401,884,885],{"class":796},"export",[401,887,888],{"class":796}," default",[401,890,891],{"class":583}," defineNuxtPlugin",[401,893,607],{"class":576},[401,895,896],{"class":572},"async",[401,898,899],{"class":407}," (",[401,901,903],{"class":902},"sHdIc","nuxtApp",[401,905,906],{"class":407},")",[401,908,909],{"class":572}," =>",[401,911,912],{"class":407}," {\n",[401,914,915,918,921,923,925],{"class":403,"line":411},[401,916,917],{"class":572},"    const",[401,919,920],{"class":576}," authStore",[401,922,793],{"class":407},[401,924,584],{"class":583},[401,926,587],{"class":614},[401,928,929,931,933,935,938,940,943],{"class":403,"line":434},[401,930,917],{"class":572},[401,932,785],{"class":407},[401,934,800],{"class":576},[401,936,937],{"class":407}," }",[401,939,793],{"class":407},[401,941,942],{"class":583}," useNuxtApp",[401,944,587],{"class":614},[401,946,947,949,951,954,957,960,962,964,967],{"class":403,"line":451},[401,948,917],{"class":572},[401,950,785],{"class":407},[401,952,953],{"class":576}," formatDate",[401,955,956],{"class":407},",",[401,958,959],{"class":576}," calculateDaysTogether",[401,961,937],{"class":407},[401,963,793],{"class":407},[401,965,966],{"class":583}," useDateUtils",[401,968,587],{"class":614},[401,970,971],{"class":403,"line":468},[401,972,593],{"emptyLinePlaceholder":592},[401,974,975],{"class":403,"line":485},[401,976,978],{"class":977},"sHwdD","    \u002F\u002F 서버 사이드에서는 실행하지 않는다.\n",[401,980,981,984,986,989,991,994,996,999,1002],{"class":403,"line":502},[401,982,983],{"class":796},"    if",[401,985,899],{"class":614},[401,987,988],{"class":796},"import",[401,990,601],{"class":407},[401,992,993],{"class":576},"meta",[401,995,601],{"class":407},[401,997,998],{"class":576},"server",[401,1000,1001],{"class":614},") ",[401,1003,1004],{"class":796},"return\n",[401,1006,1007],{"class":403,"line":514},[401,1008,593],{"emptyLinePlaceholder":592},[401,1010,1012,1014,1017,1019,1022],{"class":403,"line":1011},9,[401,1013,917],{"class":572},[401,1015,1016],{"class":576}," route",[401,1018,793],{"class":407},[401,1020,1021],{"class":583}," useRoute",[401,1023,587],{"class":614},[401,1025,1027],{"class":403,"line":1026},10,[401,1028,593],{"emptyLinePlaceholder":592},[401,1030,1032],{"class":403,"line":1031},11,[401,1033,1034],{"class":977},"    \u002F\u002F 인증 관련 페이지에서는 사용자 정보 조회를 생략한다.\n",[401,1036,1038,1040,1042,1045,1047,1050,1052,1055,1057,1059,1062,1064,1067],{"class":403,"line":1037},12,[401,1039,983],{"class":796},[401,1041,899],{"class":614},[401,1043,1044],{"class":576},"route",[401,1046,601],{"class":407},[401,1048,1049],{"class":576},"path",[401,1051,601],{"class":407},[401,1053,1054],{"class":583},"startsWith",[401,1056,607],{"class":614},[401,1058,428],{"class":407},[401,1060,1061],{"class":424},"\u002Fauth\u002F",[401,1063,428],{"class":407},[401,1065,1066],{"class":614},")) ",[401,1068,408],{"class":407},[401,1070,1072],{"class":403,"line":1071},13,[401,1073,1074],{"class":796},"        return\n",[401,1076,1078],{"class":403,"line":1077},14,[401,1079,1080],{"class":407},"    }\n",[401,1082,1084],{"class":403,"line":1083},15,[401,1085,593],{"emptyLinePlaceholder":592},[401,1087,1089,1092],{"class":403,"line":1088},16,[401,1090,1091],{"class":796},"    try",[401,1093,912],{"class":407},[401,1095,1097,1100,1102,1104,1106,1108,1110,1112,1114,1116,1118,1120,1122,1124],{"class":403,"line":1096},17,[401,1098,1099],{"class":572},"        const",[401,1101,785],{"class":407},[401,1103,619],{"class":576},[401,1105,937],{"class":407},[401,1107,793],{"class":407},[401,1109,797],{"class":796},[401,1111,800],{"class":576},[401,1113,601],{"class":407},[401,1115,805],{"class":583},[401,1117,607],{"class":614},[401,1119,428],{"class":407},[401,1121,344],{"class":424},[401,1123,428],{"class":407},[401,1125,662],{"class":614},[401,1127,1129],{"class":403,"line":1128},18,[401,1130,593],{"emptyLinePlaceholder":592},[401,1132,1134,1137,1139,1142,1145,1147,1149,1151,1153],{"class":403,"line":1133},19,[401,1135,1136],{"class":796},"        if",[401,1138,899],{"class":614},[401,1140,1141],{"class":576},"data",[401,1143,1144],{"class":407}," &&",[401,1146,619],{"class":576},[401,1148,601],{"class":407},[401,1150,624],{"class":576},[401,1152,1001],{"class":614},[401,1154,408],{"class":407},[401,1156,1158,1161,1163,1165,1167],{"class":403,"line":1157},20,[401,1159,1160],{"class":576},"            authStore",[401,1162,601],{"class":407},[401,1164,604],{"class":583},[401,1166,607],{"class":614},[401,1168,408],{"class":407},[401,1170,1172,1175,1177,1179,1181,1183],{"class":403,"line":1171},21,[401,1173,1174],{"class":614},"                email",[401,1176,418],{"class":407},[401,1178,619],{"class":576},[401,1180,601],{"class":407},[401,1182,624],{"class":576},[401,1184,431],{"class":407},[401,1186,1188,1191,1193,1195,1197,1199],{"class":403,"line":1187},22,[401,1189,1190],{"class":614},"                farmName",[401,1192,418],{"class":407},[401,1194,619],{"class":576},[401,1196,601],{"class":407},[401,1198,639],{"class":576},[401,1200,431],{"class":407},[401,1202,1204,1207,1209,1211,1213,1216],{"class":403,"line":1203},23,[401,1205,1206],{"class":614},"                name",[401,1208,418],{"class":407},[401,1210,619],{"class":576},[401,1212,601],{"class":407},[401,1214,1215],{"class":576},"name",[401,1217,431],{"class":407},[401,1219,1221,1224,1226,1228,1230,1233],{"class":403,"line":1220},24,[401,1222,1223],{"class":614},"                nickname",[401,1225,418],{"class":407},[401,1227,619],{"class":576},[401,1229,601],{"class":407},[401,1231,1232],{"class":576},"nickname",[401,1234,431],{"class":407},[401,1236,1238,1241,1243,1245,1247,1250],{"class":403,"line":1237},25,[401,1239,1240],{"class":614},"                partnerNickname",[401,1242,418],{"class":407},[401,1244,619],{"class":576},[401,1246,601],{"class":407},[401,1248,1249],{"class":576},"partnerNickname",[401,1251,431],{"class":407},[401,1253,1255,1258,1260,1262,1264,1267],{"class":403,"line":1254},26,[401,1256,1257],{"class":614},"                isCoupleConnected",[401,1259,418],{"class":407},[401,1261,619],{"class":576},[401,1263,601],{"class":407},[401,1265,1266],{"class":576},"isPartnerVerified",[401,1268,431],{"class":407},[401,1270,1272,1275,1277,1279,1281,1283,1285,1288,1290],{"class":403,"line":1271},27,[401,1273,1274],{"class":614},"                farmStartDate",[401,1276,418],{"class":407},[401,1278,953],{"class":583},[401,1280,607],{"class":614},[401,1282,1141],{"class":576},[401,1284,601],{"class":407},[401,1286,1287],{"class":576},"farmCreatedAt",[401,1289,906],{"class":614},[401,1291,431],{"class":407},[401,1293,1295,1298,1300,1302,1304,1306,1308,1310,1312],{"class":403,"line":1294},28,[401,1296,1297],{"class":614},"                daysTogether",[401,1299,418],{"class":407},[401,1301,959],{"class":583},[401,1303,607],{"class":614},[401,1305,1141],{"class":576},[401,1307,601],{"class":407},[401,1309,1287],{"class":576},[401,1311,906],{"class":614},[401,1313,431],{"class":407},[401,1315,1317,1320,1322,1324,1326,1329],{"class":403,"line":1316},29,[401,1318,1319],{"class":614},"                farmExp",[401,1321,418],{"class":407},[401,1323,619],{"class":576},[401,1325,601],{"class":407},[401,1327,1328],{"class":576},"farmExperience",[401,1330,431],{"class":407},[401,1332,1334,1337],{"class":403,"line":1333},30,[401,1335,1336],{"class":407},"            }",[401,1338,662],{"class":614},[401,1340,1342],{"class":403,"line":1341},31,[401,1343,1344],{"class":407},"        }\n",[401,1346,1348,1351,1354,1356,1359,1361],{"class":403,"line":1347},32,[401,1349,1350],{"class":407},"    }",[401,1352,1353],{"class":796}," catch",[401,1355,899],{"class":614},[401,1357,1358],{"class":576},"error",[401,1360,1001],{"class":614},[401,1362,408],{"class":407},[401,1364,1366,1369,1371,1374],{"class":403,"line":1365},33,[401,1367,1368],{"class":576},"        authStore",[401,1370,601],{"class":407},[401,1372,1373],{"class":583},"logout",[401,1375,587],{"class":614},[401,1377,1379],{"class":403,"line":1378},34,[401,1380,1080],{"class":407},[401,1382,1384,1386],{"class":403,"line":1383},35,[401,1385,659],{"class":407},[401,1387,662],{"class":576},[282,1389,1390],{},"이 Plugin의 핵심 역할은 크게 세 가지다.",[378,1392,1395],{"className":1393,"code":1394,"language":383,"meta":384},[381],"1. 클라이언트에서만 실행\n\n2. 현재 인증 상태 조회\n\n3. 사용자 정보를 Auth Store에 복구\n",[342,1396,1394],{"__ignoreMap":384},[362,1398],{},[277,1400,1402],{"id":1401},"️-6-ssr에서는-실행하지-않는다","🖥️ 6. SSR에서는 실행하지 않는다",[282,1404,1405],{},"첫 번째로 고려한 부분은 SSR이다.",[282,1407,1408],{},"Nuxt는 기본적으로 서버와 클라이언트 양쪽에서 코드를 실행할 수 있다.",[282,1410,1411],{},"하지만 이번 인증 상태 복구 로직은 브라우저에서 이미 유지되고 있는 인증 상태를 확인하고 클라이언트 Store를 복구하기 위한 목적이다.",[282,1413,1414],{},"따라서 다음 조건을 사용했다.",[378,1416,1418],{"className":395,"code":1417,"language":397,"meta":384,"style":384},"if (import.meta.server) return\n",[342,1419,1420],{"__ignoreMap":384},[401,1421,1422,1425,1427,1429,1431,1433,1435,1438],{"class":403,"line":404},[401,1423,1424],{"class":796},"if",[401,1426,899],{"class":576},[401,1428,988],{"class":796},[401,1430,601],{"class":407},[401,1432,993],{"class":576},[401,1434,601],{"class":407},[401,1436,1437],{"class":576},"server) ",[401,1439,1004],{"class":796},[282,1441,1442],{},"이렇게 하면 서버에서 Plugin이 실행될 때는 바로 종료되고, 클라이언트 환경에서만 인증 복구 로직이 수행된다.",[282,1444,1445],{},"즉:",[378,1447,1450],{"className":1448,"code":1449,"language":383,"meta":384},[381],"Nuxt Plugin 실행\n\n        ↓\n\n┌──────────────────┐\n│ Server 환경인가? │\n└────────┬─────────┘\n         │\n     Yes ↓\n       return\n\n         │ No\n         ↓\n\u002Fapi\u002Fauth\u002Fme 요청\n\n         ↓\nAuth Store 복구\n",[342,1451,1449],{"__ignoreMap":384},[282,1453,1454],{},"이런 실행 흐름을 의도한 것이다.",[362,1456],{},[277,1458,1460,1461,1463],{"id":1459},"_7-auth에서는-apiauthme-요청을-막았다","🔑 7. ",[342,1462,1061],{},"에서는 \u002Fapi\u002Fauth\u002Fme 요청을 막았다",[282,1465,1466],{},"또 하나 추가한 부분은 인증 페이지 예외 처리다.",[378,1468,1470],{"className":395,"code":1469,"language":397,"meta":384,"style":384},"const route = useRoute()\n\nif (route.path.startsWith('\u002Fauth\u002F')) {\n    return\n}\n",[342,1471,1472,1485,1489,1516,1521],{"__ignoreMap":384},[401,1473,1474,1476,1479,1481,1483],{"class":403,"line":404},[401,1475,573],{"class":572},[401,1477,1478],{"class":576}," route ",[401,1480,580],{"class":407},[401,1482,1021],{"class":583},[401,1484,587],{"class":576},[401,1486,1487],{"class":403,"line":411},[401,1488,593],{"emptyLinePlaceholder":592},[401,1490,1491,1493,1496,1498,1500,1502,1504,1506,1508,1510,1512,1514],{"class":403,"line":434},[401,1492,1424],{"class":796},[401,1494,1495],{"class":576}," (route",[401,1497,601],{"class":407},[401,1499,1049],{"class":576},[401,1501,601],{"class":407},[401,1503,1054],{"class":583},[401,1505,607],{"class":576},[401,1507,428],{"class":407},[401,1509,1061],{"class":424},[401,1511,428],{"class":407},[401,1513,1066],{"class":576},[401,1515,408],{"class":407},[401,1517,1518],{"class":403,"line":451},[401,1519,1520],{"class":796},"    return\n",[401,1522,1523],{"class":403,"line":468},[401,1524,517],{"class":407},[282,1526,1527,1528,1530],{},"현재 ",[342,1529,1061],{}," 아래에는:",[378,1532,1535],{"className":1533,"code":1534,"language":383,"meta":384},[381],"\u002Fauth\u002Flogin\n\u002Fauth\u002Fsignup\n\u002Fauth\u002Fforgot-password\n",[342,1536,1534],{"__ignoreMap":384},[282,1538,1539],{},"등의 페이지가 있다.",[282,1541,1542],{},"이 페이지들은 아직 인증 과정 자체를 수행하는 화면이다.",[282,1544,1545,1546,1548],{},"특히 로그인이나 회원가입 페이지에서 애플리케이션 초기화 과정으로 ",[342,1547,344],{},"를 호출하는 것은 불필요한 요청이 될 수 있다.",[282,1550,1551],{},"예를 들어 로그인하지 않은 사용자가 로그인 페이지에 접근했을 때:",[378,1553,1556],{"className":1554,"code":1555,"language":383,"meta":384},[381],"\u002Flogin 페이지 진입\n\n↓\n\nauth.ts 실행\n\n↓\n\n\u002Fapi\u002Fauth\u002Fme 요청\n\n↓\n\n인증 정보 없음\n\n↓\n\nlogout 처리\n",[342,1557,1555],{"__ignoreMap":384},[282,1559,1560],{},"같은 불필요한 흐름이 발생할 수 있다.",[282,1562,1563],{},"그래서 인증 페이지에서는 아예 사용자 복구 요청을 실행하지 않도록 했다.",[362,1565],{},[277,1567,1569],{"id":1568},"️-8-그런데-여기서-예상하지-못한-문제가-발생했다","⚠️ 8. 그런데 여기서 예상하지 못한 문제가 발생했다",[282,1571,1572,1575],{},[342,1573,1574],{},"auth.ts","를 추가하고 실행했더니 이번에는 다른 오류가 발생했다.",[282,1577,1578],{},"문제는:",[378,1580,1582],{"className":395,"code":1581,"language":397,"meta":384,"style":384},"const { $api } = useNuxtApp()\n",[342,1583,1584],{"__ignoreMap":384},[401,1585,1586,1588,1590,1593,1595,1597,1599],{"class":403,"line":404},[401,1587,573],{"class":572},[401,1589,785],{"class":407},[401,1591,1592],{"class":576}," $api ",[401,1594,659],{"class":407},[401,1596,793],{"class":407},[401,1598,942],{"class":583},[401,1600,587],{"class":576},[282,1602,1603],{},"부분이었다.",[282,1605,1606,1609,1610,1613],{},[342,1607,1608],{},"$api","가 ",[342,1611,1612],{},"undefined","인 상태로 접근되는 문제가 발생했다.",[282,1615,1616,1617,1619,1620,1622,1623,1626],{},"원인을 추적해보니 ",[342,1618,1574],{},"가 사용하는 ",[342,1621,1608],{},"는 다른 Plugin인 ",[342,1624,1625],{},"axios.ts","에서 등록하고 있었다.",[282,1628,1629],{},"구조는 대략 다음과 같다.",[378,1631,1634],{"className":1632,"code":1633,"language":383,"meta":384},[381],"plugins\u002F\n\n├── axios.ts\n│    └── $api 등록\n│\n└── auth.ts\n     └── $api 사용\n",[342,1635,1633],{"__ignoreMap":384},[282,1637,1638],{},"논리적으로 보면 당연히:",[378,1640,1643],{"className":1641,"code":1642,"language":383,"meta":384},[381],"axios.ts\n\n↓\n\n$api 등록\n\n↓\n\nauth.ts\n\n↓\n\n$api 사용\n",[342,1644,1642],{"__ignoreMap":384},[282,1646,1647],{},"순서가 되어야 한다.",[282,1649,1650],{},"그런데 Plugin이 여러 개 존재한다고 해서 내가 의도한 의존성 순서가 자동으로 보장되는 것은 아니었다.",[282,1652,1653],{},"이 지점에서 Nuxt Plugin의 실행 순서에 대해 확인할 필요가 생겼다.",[362,1655],{},[277,1657,1659],{"id":1658},"_9-nuxt-plugin은-파일-순서의-영향을-받는다","🧩 9. Nuxt Plugin은 파일 순서의 영향을 받는다",[282,1661,1662,1663,1666],{},"Nuxt의 Plugin 시스템에서는 Plugin 파일을 단순히 독립적인 코드 조각으로 보는 것보다 ",[534,1664,1665],{},"애플리케이션 초기화 단계에서 실행되는 Plugin 집합","으로 이해하는 것이 좋다.",[282,1668,1669],{},"특히 Plugin 간 의존성이 존재한다면 실행 순서를 명시적으로 관리해야 한다.",[282,1671,1672],{},"이번 구조에서는:",[378,1674,1677],{"className":1675,"code":1676,"language":383,"meta":384},[381],"auth.ts\n\n↓\n\n$api 필요\n",[342,1678,1676],{"__ignoreMap":384},[282,1680,1681,1682,1684],{},"이기 때문에 ",[342,1683,1625],{},"가 먼저 실행되어야 한다.",[282,1686,1687],{},"그런데 파일 이름이:",[378,1689,1692],{"className":1690,"code":1691,"language":383,"meta":384},[381],"axios.ts\nauth.ts\n",[342,1693,1691],{"__ignoreMap":384},[282,1695,1696],{},"라고 되어 있다고 해서 모든 상황에서 내가 생각한 의존 관계를 코드만 보고 명확하게 표현할 수 있는 것은 아니다.",[282,1698,1699],{},"실제 프로젝트에서는 Plugin이 많아질수록 실행 순서를 의식적으로 관리할 필요가 있다.",[362,1701],{},[277,1703,1705],{"id":1704},"️-10-plugin-파일-이름으로-실행-순서를-명확하게-했다","🛠️ 10. Plugin 파일 이름으로 실행 순서를 명확하게 했다",[282,1707,1708],{},"이번 프로젝트에서는 가장 간단한 방식으로 파일 이름에 숫자 Prefix를 붙였다.",[282,1710,1711],{},"기존:",[378,1713,1715],{"className":1714,"code":864,"language":383,"meta":384},[381],[342,1716,864],{"__ignoreMap":384},[282,1718,1719],{},"변경:",[378,1721,1724],{"className":1722,"code":1723,"language":383,"meta":384},[381],"plugins\u002F\n\n├── 01.axios.ts\n└── auth.ts\n",[342,1725,1723],{"__ignoreMap":384},[282,1727,1728],{},"이렇게 하면 파일 정렬 순서를 기준으로 Axios Plugin이 먼저 실행되도록 만들 수 있다.",[282,1730,1731],{},"결과적으로 실행 흐름은:",[378,1733,1736],{"className":1734,"code":1735,"language":383,"meta":384},[381],"01.axios.ts\n\n↓\n\n$api 생성\n\n↓\n\nauth.ts\n\n↓\n\n$api.get('\u002Fapi\u002Fauth\u002Fme')\n\n↓\n\nAuth Store 복구\n",[342,1737,1735],{"__ignoreMap":384},[282,1739,1740],{},"가 된다.",[282,1742,1743],{},"이번 문제에서는 별도의 복잡한 의존성 시스템을 추가하기보다는 파일 이름만으로 초기화 순서를 명확하게 표현하는 방식이 충분했다.",[362,1745],{},[277,1747,1749],{"id":1748},"_11-여기서-중요한-것은-순서보다-의존성이었다","🧠 11. 여기서 중요한 것은 \"순서\"보다 \"의존성\"이었다",[282,1751,1752],{},"이번 작업에서 더 중요하게 느껴졌던 부분은 단순히:",[1754,1755,1756],"blockquote",{},[282,1757,1758],{},"Plugin은 파일 이름 순서대로 실행된다.",[282,1760,1761],{},"라는 사실 자체가 아니었다.",[282,1763,1764,1765,1768],{},"실제 개발에서 중요한 것은 ",[534,1766,1767],{},"어떤 Plugin이 어떤 Plugin에 의존하고 있는가","를 명확하게 만드는 것이다.",[282,1770,1771],{},"현재 구조를 보면:",[378,1773,1776],{"className":1774,"code":1775,"language":383,"meta":384},[381],"axios Plugin\n\n└── $api 제공\n\n        ↓\n\nauth Plugin\n\n└── $api 사용\n",[342,1777,1775],{"__ignoreMap":384},[282,1779,1780],{},"이라는 의존성이 존재한다.",[282,1782,1783],{},"따라서 이것을 코드 구조에서도 명확하게 표현해야 한다.",[378,1785,1788],{"className":1786,"code":1787,"language":383,"meta":384},[381],"01.axios.ts\n\nauth.ts\n",[342,1789,1787],{"__ignoreMap":384},[282,1791,1792],{},"라는 이름은 단순히 실행 순서를 바꾸는 것뿐만 아니라:",[378,1794,1797],{"className":1795,"code":1796,"language":383,"meta":384},[381],"\"auth Plugin은 axios 초기화 이후에 실행되어야 한다.\"\n",[342,1798,1796],{"__ignoreMap":384},[282,1800,1801],{},"라는 프로젝트의 초기화 규칙을 파일 구조에 남기는 역할도 한다.",[282,1803,1804],{},"Plugin이 많아지면 이런 초기화 순서가 애플리케이션 부팅 과정의 일부가 된다.",[362,1806],{},[277,1808,1810],{"id":1809},"_12-최종적인-인증-상태-복구-흐름","🔄 12. 최종적인 인증 상태 복구 흐름",[282,1812,1813],{},"이번 작업을 통해 로그인 이후의 상태 흐름은 다음처럼 정리했다.",[378,1815,1818],{"className":1816,"code":1817,"language":383,"meta":384},[381],"[로그인]\n\n사용자\n  ↓\nPOST \u002Fapi\u002Fauth\u002Flogin\n  ↓\n서버 인증\n  ↓\n인증 상태 유지\n  ↓\n사용자 정보 반환\n  ↓\nPinia Auth Store 저장\n\n\n[새로고침]\n\n브라우저 Reload\n  ↓\nNuxt Application 초기화\n  ↓\nPinia Store 초기화\n  ↓\n01.axios.ts\n  ↓\n$api 초기화\n  ↓\nauth.ts\n  ↓\n\u002Fapi\u002Fauth\u002Fme\n  ↓\n서버에서 현재 사용자 확인\n  ↓\nAuth Store 복구\n  ↓\n애플리케이션에서 사용자 정보 사용\n",[342,1819,1817],{"__ignoreMap":384},[282,1821,1822],{},"이렇게 보면 Pinia가 인증을 담당하는 것이 아니라 각 계층이 서로 다른 역할을 담당하고 있다는 것이 명확해진다.",[378,1824,1827],{"className":1825,"code":1826,"language":383,"meta":384},[381],"Server\n└── 인증 상태의 기준\n\nAPI\n└── 현재 사용자 정보 제공\n\nAxios Plugin\n└── API Client 초기화\n\nAuth Plugin\n└── 초기 인증 상태 복구\n\nPinia\n└── 클라이언트 전역 상태 관리\n",[342,1828,1826],{"__ignoreMap":384},[282,1830,1831],{},"각각의 역할을 분리해두면 인증 흐름을 이해하기도 쉬워진다.",[362,1833],{},[277,1835,1837],{"id":1836},"_13-인증-plugin에서-store를-복구하는-이유","🧱 13. 인증 Plugin에서 Store를 복구하는 이유",[282,1839,1840],{},"여기서 한 가지 더 생각해볼 수 있는 부분이 있다.",[282,1842,1843,1844,1846],{},"왜 ",[342,1845,344],{},"의 응답을 바로 각 페이지에서 사용하는 것이 아니라 Auth Store에 다시 넣을까?",[282,1848,1849],{},"BroBean에서는 사용자 정보가 여러 영역에서 사용되기 때문이다.",[282,1851,562],{},[378,1853,1856],{"className":1854,"code":1855,"language":383,"meta":384},[381],"헤더\n └── 사용자 닉네임\n\n사이드바\n └── 농장 이름\n\n마이페이지\n └── 프로필\n\n농장 화면\n └── 농장 경험치\n\n커플 화면\n └── 파트너 정보\n\n가계부\n └── 현재 사용자 정보\n",[342,1857,1855],{"__ignoreMap":384},[282,1859,1860,1861,1863],{},"모든 화면에서 각각 ",[342,1862,344],{},"를 호출한다면 API 요청과 상태 관리가 분산된다.",[282,1865,1866],{},"반대로:",[378,1868,1871],{"className":1869,"code":1870,"language":383,"meta":384},[381],"\u002Fapi\u002Fauth\u002Fme\n\n↓\n\nAuth Store\n\n↓\n\n여러 컴포넌트에서 공유\n",[342,1872,1870],{"__ignoreMap":384},[282,1874,1875],{},"구조를 사용하면 인증된 사용자에 대한 클라이언트 상태를 하나의 지점에서 관리할 수 있다.",[362,1877],{},[277,1879,1881],{"id":1880},"_14-매번-사용자-정보를-api로-가져오는-것과의-차이","⚡ 14. 매번 사용자 정보를 API로 가져오는 것과의 차이",[282,1883,1884],{},"물론 모든 상황에서 Store만 사용하는 것도 적절하지 않다.",[282,1886,1887],{},"예를 들어 사용자의 정보가 서버에서 변경되었다면 기존 Store는 이전 값을 가지고 있을 수 있다.",[282,1889,671],{},[378,1891,1894],{"className":1892,"code":1893,"language":383,"meta":384},[381],"Auth Store\n\n= 현재 클라이언트에서 사용하는 사용자 상태\n",[342,1895,1893],{"__ignoreMap":384},[282,1897,680],{},[282,1899,1900],{},"필요한 경우 서버에서 최신 데이터를 다시 가져와 Store를 갱신할 수 있다.",[282,1902,1903],{},"이번 구조에서는 애플리케이션 초기화 시점에:",[378,1905,1908],{"className":1906,"code":1907,"language":383,"meta":384},[381],"\u002Fapi\u002Fauth\u002Fme\n\n↓\n\nAuth Store 초기화\n",[342,1909,1907],{"__ignoreMap":384},[282,1911,1912],{},"를 수행하기 때문에 새로고침 이후의 초기 상태를 서버 기준으로 다시 맞춰줄 수 있다.",[362,1914],{},[277,1916,1918],{"id":1917},"_15-인증-페이지에서-api-호출을-제외한-이유","🧩 15. 인증 페이지에서 API 호출을 제외한 이유",[282,1920,1921,1922,1924],{},"이번 구현에서 ",[342,1923,1061],{}," 경로를 예외 처리한 것도 단순한 최적화 이상의 의미가 있다.",[282,1926,1927],{},"인증 페이지는 일반적인 서비스 페이지와 lifecycle이 다르다.",[378,1929,1932],{"className":1930,"code":1931,"language":383,"meta":384},[381],"로그인\n\n↓\n\n인증 성공\n\n↓\n\n서비스 영역 진입\n",[342,1933,1931],{"__ignoreMap":384},[282,1935,1936],{},"이라는 흐름이기 때문에 인증 이전 단계에서는 사용자 정보를 조회하는 것 자체가 의미가 없을 수 있다.",[282,1938,1939],{},"특히 로그인 페이지에서:",[378,1941,1943],{"className":395,"code":1942,"language":397,"meta":384,"style":384},"\u002Fapi\u002Fauth\u002Fme\n",[342,1944,1945],{"__ignoreMap":384},[401,1946,1947,1950,1953,1955,1958,1960],{"class":403,"line":404},[401,1948,1949],{"class":407},"\u002F",[401,1951,1952],{"class":576},"api",[401,1954,1949],{"class":407},[401,1956,1957],{"class":576},"auth",[401,1959,1949],{"class":407},[401,1961,1962],{"class":576},"me\n",[282,1964,1965],{},"를 먼저 호출하는 것보다 로그인 액션 자체에 인증 확인 책임을 두는 것이 더 명확하다.",[282,1967,1968],{},"따라서 현재 구조에서는:",[378,1970,1973],{"className":1971,"code":1972,"language":383,"meta":384},[381],"\u002Fauth\u002F*\n\n→ Auth Plugin 인증 복구 생략\n\n그 외 서비스 페이지\n\n→ Auth Plugin 인증 상태 확인\n",[342,1974,1972],{"__ignoreMap":384},[282,1976,1977],{},"으로 분리했다.",[362,1979],{},[277,1981,1983],{"id":1982},"_16-실제로-확인해야-했던-테스트-케이스","🧪 16. 실제로 확인해야 했던 테스트 케이스",[282,1985,1986],{},"인증 상태 복구를 구현하고 나서는 단순히 로그인 성공만 확인해서는 충분하지 않았다.",[282,1988,1989],{},"최소한 다음 케이스를 확인해야 했다.",[1991,1992,1994],"h3",{"id":1993},"로그인-직후","로그인 직후",[378,1996,1999],{"className":1997,"code":1998,"language":383,"meta":384},[381],"로그인 성공\n→ 사용자 정보 정상 표시\n",[342,2000,1998],{"__ignoreMap":384},[1991,2002,2003],{"id":2003},"새로고침",[378,2005,2008],{"className":2006,"code":2007,"language":383,"meta":384},[381],"새로고침\n→ 사용자 정보 유지\n",[342,2009,2007],{"__ignoreMap":384},[1991,2011,2013],{"id":2012},"다른-페이지-이동-후-새로고침","다른 페이지 이동 후 새로고침",[378,2015,2018],{"className":2016,"code":2017,"language":383,"meta":384},[381],"농장 페이지 진입\n→ 새로고침\n→ 사용자 정보 복구\n",[342,2019,2017],{"__ignoreMap":384},[1991,2021,2022],{"id":2022},"로그아웃",[378,2024,2027],{"className":2025,"code":2026,"language":383,"meta":384},[381],"로그아웃\n→ Auth Store 초기화\n→ 인증 페이지 이동\n",[342,2028,2026],{"__ignoreMap":384},[1991,2030,2032],{"id":2031},"비로그인-상태에서-서비스-접근","비로그인 상태에서 서비스 접근",[378,2034,2037],{"className":2035,"code":2036,"language":383,"meta":384},[381],"인증 정보 없음\n→ \u002Fapi\u002Fauth\u002Fme 실패\n→ logout 처리\n",[342,2038,2036],{"__ignoreMap":384},[1991,2040,2042],{"id":2041},"로그인-페이지-진입","로그인 페이지 진입",[378,2044,2047],{"className":2045,"code":2046,"language":383,"meta":384},[381],"\u002Fauth\u002Flogin\n→ \u002Fapi\u002Fauth\u002Fme 요청 생략\n",[342,2048,2046],{"__ignoreMap":384},[1991,2050,2052],{"id":2051},"plugin-초기화-순서","Plugin 초기화 순서",[378,2054,2057],{"className":2055,"code":2056,"language":383,"meta":384},[381],"01.axios.ts\n→ $api 초기화\n\nauth.ts\n→ $api 사용\n",[342,2058,2056],{"__ignoreMap":384},[282,2060,2061],{},"이런 테스트를 통해 단순히 \"동작한다\"가 아니라 애플리케이션 초기화 흐름까지 정상적으로 구성되었는지 확인했다.",[362,2063],{},[277,2065,2067],{"id":2066},"_17-ssr과-client-초기화-시점을-함께-고려해야-한다","🌙 17. SSR과 Client 초기화 시점을 함께 고려해야 한다",[282,2069,2070],{},"Nuxt를 사용하면서 인증 관련 로직을 작성할 때 특히 신경 써야 하는 부분이 SSR이다.",[282,2072,2073],{},"Nuxt에서는 페이지가 단순히 브라우저에서 처음부터 실행되는 구조가 아니다.",[378,2075,2078],{"className":2076,"code":2077,"language":383,"meta":384},[381],"Server Rendering\n\n↓\n\nHTML 생성\n\n↓\n\nClient Hydration\n\n↓\n\nVue Application 실행\n",[342,2079,2077],{"__ignoreMap":384},[282,2081,2082],{},"과 같은 흐름이 존재한다.",[282,2084,2085],{},"따라서 인증 정보를 어디에서 확인할 것인지에 따라:",[304,2087,2088,2091,2094],{},[307,2089,2090],{},"서버에서 인증 상태를 처리할 것인지",[307,2092,2093],{},"클라이언트에서 처리할 것인지",[307,2095,2096],{},"SSR과 CSR 양쪽에서 처리할 것인지",[282,2098,2099],{},"를 구분해야 한다.",[282,2101,2102,2103,2105,2106,2109],{},"이번 구현의 ",[342,2104,1574],{},"는 ",[534,2107,2108],{},"클라이언트에서 Pinia Store를 복구하는 목적","이므로:",[378,2111,2112],{"className":395,"code":1417,"language":397,"meta":384,"style":384},[342,2113,2114],{"__ignoreMap":384},[401,2115,2116,2118,2120,2122,2124,2126,2128,2130],{"class":403,"line":404},[401,2117,1424],{"class":796},[401,2119,899],{"class":576},[401,2121,988],{"class":796},[401,2123,601],{"class":407},[401,2125,993],{"class":576},[401,2127,601],{"class":407},[401,2129,1437],{"class":576},[401,2131,1004],{"class":796},[282,2133,2134],{},"을 사용해 서버 실행을 제외했다.",[282,2136,2137],{},"프로젝트의 인증 구조가 확장되면서 SSR 단계에서 인증 사용자 정보를 필요로 하게 된다면 이후에는 서버 측 인증 처리까지 별도로 고려할 필요가 있다.",[362,2139],{},[277,2141,2143],{"id":2142},"_18-인증-상태를-저장하는-것보다-복구하는-관점","🔐 18. 인증 상태를 \"저장\"하는 것보다 \"복구\"하는 관점",[282,2145,2146],{},"이번 작업에서 개인적으로 중요하게 정리한 부분이다.",[282,2148,2149],{},"새로고침 이후 사용자 정보가 사라지는 문제를 단순히:",[1754,2151,2152],{},[282,2153,2154],{},"Pinia 데이터를 어떻게 유지하지?",[282,2156,2157],{},"라는 문제로 접근하면 결국 Store를 LocalStorage에 저장하는 방향만 생각하기 쉽다.",[282,2159,2160],{},"하지만 실제 인증 시스템에서는:",[378,2162,2165],{"className":2163,"code":2164,"language":383,"meta":384},[381],"클라이언트 상태를 영구 저장\n\n",[342,2166,2164],{"__ignoreMap":384},[282,2168,2169],{},"보다:",[378,2171,2174],{"className":2172,"code":2173,"language":383,"meta":384},[381],"서버의 인증 상태를 기준으로\n\n↓\n\n클라이언트 상태를 복구\n\n",[342,2175,2173],{"__ignoreMap":384},[282,2177,2178],{},"하는 방식으로 바라보는 것이 더 자연스럽다.",[282,2180,1445],{},[378,2182,2185],{"className":2183,"code":2184,"language":383,"meta":384},[381],"Auth Source of Truth\n        │\n        ▼\n     Server\n        │\n        ▼\n \u002Fapi\u002Fauth\u002Fme\n        │\n        ▼\n   Auth Store\n        │\n        ▼\n      UI\n",[342,2186,2184],{"__ignoreMap":384},[282,2188,2189],{},"구조가 된다.",[282,2191,2192],{},"Pinia는 인증의 원본이 아니라 서버 상태를 애플리케이션에서 사용하기 편한 형태로 유지하는 역할을 담당한다.",[362,2194],{},[277,2196,2198],{"id":2197},"_19-plugin이-많아질수록-초기화-순서를-의식해야-한다","🧩 19. Plugin이 많아질수록 초기화 순서를 의식해야 한다",[282,2200,2201],{},"처음에는 Plugin이 몇 개 없기 때문에 각각의 파일이 독립적으로 동작하는 것처럼 보인다.",[282,2203,2204],{},"하지만 프로젝트가 커지면서:",[378,2206,2209],{"className":2207,"code":2208,"language":383,"meta":384},[381],"plugins\u002F\n\n├── 01.axios.ts\n├── auth.ts\n├── api-error.ts\n├── notification.ts\n├── websocket.ts\n└── analytics.ts\n",[342,2210,2208],{"__ignoreMap":384},[282,2212,2213],{},"처럼 Plugin이 증가할 수 있다.",[282,2215,2216],{},"이때 Plugin 간에 의존성이 생기기 시작한다.",[282,2218,562],{},[378,2220,2223],{"className":2221,"code":2222,"language":383,"meta":384},[381],"axios\n ↓\nauth\n ↓\nnotification\n ↓\nwebsocket\n",[342,2224,2222],{"__ignoreMap":384},[282,2226,2227],{},"같은 초기화 흐름이 만들어질 수 있다.",[282,2229,2230,2231,2234],{},"이런 상황에서는 Plugin을 단순한 유틸리티 파일로 생각하기보다 ",[534,2232,2233],{},"애플리케이션 부팅 과정의 일부","로 관리해야 한다.",[282,2236,2237,2238,2240,2241,2240,2244,2247],{},"특히 어떤 Plugin이 제공하는 ",[342,2239,1608],{},", ",[342,2242,2243],{},"$socket",[342,2245,2246],{},"$auth"," 같은 runtime dependency를 다른 Plugin이 사용한다면 실행 순서 또는 명시적인 dependency 설정을 함께 고려해야 한다.",[362,2249],{},[277,2251,2253],{"id":2252},"️-20-파일명-prefix-방식의-장점","🛠️ 20. 파일명 Prefix 방식의 장점",[282,2255,2256],{},"이번에는 간단하게:",[378,2258,2261],{"className":2259,"code":2260,"language":383,"meta":384},[381],"01.axios.ts\nauth.ts\n",[342,2262,2260],{"__ignoreMap":384},[282,2264,2265],{},"방식으로 해결했다.",[282,2267,2268],{},"이 방식의 장점은 프로젝트 구조만 봐도 초기화 순서를 어느 정도 파악할 수 있다는 것이다.",[378,2270,2273],{"className":2271,"code":2272,"language":383,"meta":384},[381],"01.axios.ts\n",[342,2274,2272],{"__ignoreMap":384},[282,2276,2277],{},"라는 이름만 봐도:",[1754,2279,2280],{},[282,2281,2282],{},"\"초기화 단계에서 먼저 실행되어야 하는 Plugin\"",[282,2284,2285],{},"이라는 의도를 읽을 수 있다.",[282,2287,2288],{},"특히 Plugin이 많지 않은 프로젝트에서는 별도의 복잡한 추상화 없이도 의존성을 명확하게 표현할 수 있다.",[362,2290],{},[277,2292,2294],{"id":2293},"️-21-하지만-파일명-순서에-모든-의존성을-맡기면-안-된다","⚠️ 21. 하지만 파일명 순서에 모든 의존성을 맡기면 안 된다",[282,2296,2297],{},"그렇다고 Plugin 파일 이름에 모든 의존성을 몰아넣는 방식이 항상 좋은 것은 아니다.",[282,2299,2300],{},"예를 들어 Plugin이 수십 개가 되고:",[378,2302,2305],{"className":2303,"code":2304,"language":383,"meta":384},[381],"01\n02\n03\n04\n05\n...\n",[342,2306,2304],{"__ignoreMap":384},[282,2308,2309],{},"처럼 번호가 계속 증가한다면 파일 구조 자체가 실행 순서 관리에 지나치게 의존하게 된다.",[282,2311,2312],{},"또한 특정 Plugin이 다른 여러 Plugin에 의존하기 시작하면 단순한 숫자 순서만으로 관계를 표현하기 어려워질 수 있다.",[282,2314,2315],{},"따라서 현재 BroBean처럼 구조가 단순한 단계에서는 Prefix 방식으로 충분하지만, 프로젝트 규모가 커진다면 Nuxt가 제공하는 Plugin dependency 관리 방식이나 초기화 구조 자체를 재검토할 필요가 있다.",[282,2317,2318],{},"중요한 것은:",[378,2320,2323],{"className":2321,"code":2322,"language":383,"meta":384},[381],"\"무조건 숫자를 붙인다.\"\n\n",[342,2324,2322],{"__ignoreMap":384},[282,2326,2327],{},"가 아니라,",[378,2329,2332],{"className":2330,"code":2331,"language":383,"meta":384},[381],"\"Plugin 사이의 초기화 의존성을 명확하게 관리한다.\"\n\n",[342,2333,2331],{"__ignoreMap":384},[282,2335,2336],{},"는 것이다.",[362,2338],{},[277,2340,2342],{"id":2341},"_22-이번-작업에서-정리한-각-계층의-책임","📌 22. 이번 작업에서 정리한 각 계층의 책임",[282,2344,2345],{},"이번 인증 상태 복구를 구현하면서 각 기술의 역할을 다시 정리할 수 있었다.",[2347,2348,2349,2362],"table",{},[2350,2351,2352],"thead",{},[2353,2354,2355,2359],"tr",{},[2356,2357,2358],"th",{},"계층",[2356,2360,2361],{},"역할",[2363,2364,2365,2374,2383,2391,2399,2407],"tbody",{},[2353,2366,2367,2371],{},[2368,2369,2370],"td",{},"Backend",[2368,2372,2373],{},"인증 상태와 사용자 정보의 기준",[2353,2375,2376,2380],{},[2368,2377,2378],{},[342,2379,344],{},[2368,2381,2382],{},"현재 인증 사용자 조회",[2353,2384,2385,2388],{},[2368,2386,2387],{},"Axios Plugin",[2368,2389,2390],{},"공통 API Client 제공",[2353,2392,2393,2396],{},[2368,2394,2395],{},"Auth Plugin",[2368,2397,2398],{},"애플리케이션 초기 인증 상태 복구",[2353,2400,2401,2404],{},[2368,2402,2403],{},"Pinia",[2368,2405,2406],{},"클라이언트 전역 사용자 상태 관리",[2353,2408,2409,2412],{},[2368,2410,2411],{},"Component",[2368,2413,2414],{},"Store 상태를 화면에 표현",[282,2416,2417],{},"이렇게 책임을 나누면 인증 상태가 변경되는 흐름도 명확해진다.",[378,2419,2422],{"className":2420,"code":2421,"language":383,"meta":384},[381],"Backend\n   ↓\nAPI\n   ↓\nAuth Plugin\n   ↓\nPinia\n   ↓\nComponent\n",[342,2423,2421],{"__ignoreMap":384},[362,2425],{},[277,2427,2429],{"id":2428},"_23-앞으로-인증-구조를-확장한다면","🚀 23. 앞으로 인증 구조를 확장한다면",[282,2431,2432],{},"현재 구현은 BroBean의 로그인\u002F회원가입 기능을 실제 서비스 흐름에 연결하기 위한 초기 구조다.",[282,2434,2435],{},"앞으로 인증 기능이 확장된다면 다음과 같은 부분도 고려할 수 있다.",[1991,2437,2439],{"id":2438},"인증-만료-처리","🔐 인증 만료 처리",[282,2441,1527,2442,2444],{},[342,2443,344],{},"가 실패하면:",[378,2446,2448],{"className":395,"code":2447,"language":397,"meta":384,"style":384},"authStore.logout()\n",[342,2449,2450],{"__ignoreMap":384},[401,2451,2452,2454,2456,2458],{"class":403,"line":404},[401,2453,598],{"class":576},[401,2455,601],{"class":407},[401,2457,1373],{"class":583},[401,2459,587],{"class":576},[282,2461,2462],{},"을 호출한다.",[282,2464,2465],{},"향후에는 access token이나 session이 만료된 경우를 구분해 자동 갱신 구조를 추가할 수 있다.",[362,2467],{},[1991,2469,2471],{"id":2470},"사용자-정보-자동-동기화","🔄 사용자 정보 자동 동기화",[282,2473,2474],{},"사용자가 마이페이지에서 프로필을 수정하면:",[378,2476,2479],{"className":2477,"code":2478,"language":383,"meta":384},[381],"프로필 수정 API\n\n↓\n\n서버 업데이트\n\n↓\n\nAuth Store 갱신\n",[342,2480,2478],{"__ignoreMap":384},[282,2482,2483],{},"흐름으로 최신 상태를 즉시 반영할 수 있다.",[362,2485],{},[1991,2487,2489],{"id":2488},"인증이-필요한-페이지-middleware","🚫 인증이 필요한 페이지 Middleware",[282,2491,2492],{},"현재 Plugin은 애플리케이션 전체에서 인증 상태를 복구하는 역할에 가깝다.",[282,2494,2495],{},"반면 특정 페이지에 인증이 반드시 필요하다면:",[378,2497,2500],{"className":2498,"code":2499,"language":383,"meta":384},[381],"Middleware\n\n↓\n\n인증 여부 확인\n\n↓\n\n미인증 → 로그인 페이지 이동\n",[342,2501,2499],{"__ignoreMap":384},[282,2503,2504],{},"같은 접근 제어를 별도로 구성하는 것이 적절하다.",[282,2506,1445],{},[378,2508,2511],{"className":2509,"code":2510,"language":383,"meta":384},[381],"Plugin\n→ 인증 상태 초기화 \u002F 복구\n\nMiddleware\n→ 페이지 접근 제어\n",[342,2512,2510],{"__ignoreMap":384},[282,2514,2515],{},"처럼 책임을 나눌 수 있다.",[362,2517],{},[277,2519,2521],{"id":2520},"마무리","🎯 마무리",[282,2523,2524,2525,2528],{},"이번 작업은 단순히 \"새로고침하면 Pinia 데이터가 사라진다\"는 문제를 해결하는 작업에서 시작했지만, 실제로는 ",[534,2526,2527],{},"인증 상태를 어떤 계층에서 관리하고 애플리케이션 초기화 과정에서 어떻게 복구할 것인가","를 정리하는 과정이었다.",[282,2530,2531],{},"최종적인 구조는 다음과 같다.",[378,2533,2536],{"className":2534,"code":2535,"language":383,"meta":384},[381],"로그인\n\n↓\n\nBackend 인증\n\n↓\n\n인증 상태 유지\n\n↓\n\nPinia에 사용자 상태 저장\n\n\n새로고침\n\n↓\n\nNuxt Application 초기화\n\n↓\n\n01.axios.ts\n\n↓\n\n$api 초기화\n\n↓\n\nauth.ts\n\n↓\n\n\u002Fapi\u002Fauth\u002Fme\n\n↓\n\n사용자 정보 조회\n\n↓\n\nPinia Auth Store 복구\n\n↓\n\n서비스 화면 사용\n",[342,2537,2535],{"__ignoreMap":384},[282,2539,2540,2541,2544],{},"그리고 이번 과정에서 함께 확인한 또 하나의 중요한 부분은 ",[534,2542,2543],{},"Nuxt Plugin 역시 애플리케이션 초기화 순서의 일부로 관리해야 한다는 것","이었다.",[282,2546,2547],{},"특히:",[378,2549,2552],{"className":2550,"code":2551,"language":383,"meta":384},[381],"axios.ts\n    ↓\nauth.ts\n",[342,2553,2551],{"__ignoreMap":384},[282,2555,2556,2557,2559],{},"처럼 명확한 의존 관계가 존재한다면 Plugin의 실행 순서를 고려하지 않고 구현할 경우 런타임에서 ",[342,2558,1608],{}," 같은 dependency가 준비되지 않은 상태로 접근되는 문제가 발생할 수 있다.",[282,2561,2562,2563,2566,2567,2570],{},"이번에는 ",[342,2564,2565],{},"01.axios.ts","처럼 파일명 Prefix를 사용하는 간단한 방식으로 해결했지만, 중요한 것은 숫자 자체가 아니라 ",[534,2568,2569],{},"Plugin 간 의존성을 코드 구조에 명확하게 표현하는 것","이라고 생각한다.",[282,2572,2573],{},"결국 인증 상태는 단순히 클라이언트에 데이터를 \"저장해두는 것\"으로 끝나는 문제가 아니다.",[378,2575,2578],{"className":2576,"code":2577,"language":383,"meta":384},[381],"Server의 인증 상태\n\n        ↓\n\n현재 사용자 조회\n\n        ↓\n\nClient 상태 복구\n\n        ↓\n\nUI에서 사용\n",[342,2579,2577],{"__ignoreMap":384},[282,2581,2582],{},"이라는 흐름으로 설계해야 새로고침이나 애플리케이션 재초기화가 발생하더라도 사용자 경험을 안정적으로 유지할 수 있다.",[282,2584,2585],{},"BroBean 역시 이번 인증 구조를 기반으로 이후에는:",[304,2587,2588,2591,2594,2597,2600,2603],{},[307,2589,2590],{},"🔐 로그인 상태 유지",[307,2592,2593],{},"🔄 인증 만료 및 갱신",[307,2595,2596],{},"👤 사용자 프로필 동기화",[307,2598,2599],{},"💑 커플 인증 상태 관리",[307,2601,2602],{},"🛡️ 인증 페이지 Middleware",[307,2604,2605],{},"📱 모바일 환경의 인증 상태 유지",[282,2607,2608],{},"등을 단계적으로 확장할 예정이다.",[282,2610,2611,2612,2615],{},"이번 작업을 통해 단순히 로그인 API를 연결하는 것과 ",[534,2613,2614],{},"서비스에서 지속적으로 유지되는 인증 흐름을 만드는 것은 다른 문제","라는 점을 다시 확인할 수 있었다.",[2617,2618,2619],"style",{},"html pre.shiki code .sMK4o, html code.shiki .sMK4o{--shiki-light:#39ADB5;--shiki-default:#89DDFF;--shiki-dark:#89DDFF}html pre.shiki code .sBMFI, html code.shiki .sBMFI{--shiki-light:#E2931D;--shiki-default:#FFCB6B;--shiki-dark:#FFCB6B}html pre.shiki code .sfazB, html code.shiki .sfazB{--shiki-light:#91B859;--shiki-default:#C3E88D;--shiki-dark:#C3E88D}html pre.shiki code .sfNiH, html code.shiki .sfNiH{--shiki-light:#FF5370;--shiki-default:#FF9CAC;--shiki-dark:#FF9CAC}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 .spNyl, html code.shiki .spNyl{--shiki-light:#9C3EDA;--shiki-default:#C792EA;--shiki-dark:#C792EA}html pre.shiki code .sTEyZ, html code.shiki .sTEyZ{--shiki-light:#90A4AE;--shiki-default:#EEFFFF;--shiki-dark:#BABED8}html pre.shiki code .s2Zo4, html code.shiki .s2Zo4{--shiki-light:#6182B8;--shiki-default:#82AAFF;--shiki-dark:#82AAFF}html pre.shiki code .swJcz, html code.shiki .swJcz{--shiki-light:#E53935;--shiki-default:#F07178;--shiki-dark:#F07178}html pre.shiki code .s7zQu, html code.shiki .s7zQu{--shiki-light:#39ADB5;--shiki-light-font-style:italic;--shiki-default:#89DDFF;--shiki-default-font-style:italic;--shiki-dark:#89DDFF;--shiki-dark-font-style:italic}html pre.shiki code .sHdIc, html code.shiki .sHdIc{--shiki-light:#90A4AE;--shiki-light-font-style:italic;--shiki-default:#EEFFFF;--shiki-default-font-style:italic;--shiki-dark:#BABED8;--shiki-dark-font-style:italic}html pre.shiki code .sHwdD, html code.shiki .sHwdD{--shiki-light:#90A4AE;--shiki-light-font-style:italic;--shiki-default:#546E7A;--shiki-default-font-style:italic;--shiki-dark:#676E95;--shiki-dark-font-style:italic}",{"title":384,"searchDepth":411,"depth":411,"links":2621},[2622,2623,2624,2625,2626,2627,2628,2629,2630,2631],{"id":1993,"depth":434,"text":1994},{"id":2003,"depth":434,"text":2003},{"id":2012,"depth":434,"text":2013},{"id":2022,"depth":434,"text":2022},{"id":2031,"depth":434,"text":2032},{"id":2041,"depth":434,"text":2042},{"id":2051,"depth":434,"text":2052},{"id":2438,"depth":434,"text":2439},{"id":2470,"depth":434,"text":2471},{"id":2488,"depth":434,"text":2489},"2026-08-13","회원가입과 로그인 백엔드 연동 과정에서 발생한 인증 상태 초기화 문제를 해결하고, Nuxt Plugin을 활용한 사용자 정보 복구와 플러그인 실행 순서 제어까지 구현한 과정","md",null,{"tags":2637,"image":2646},[2638,2639,2403,2640,2641,2642,2643,2644,2645],"BroBean","Nuxt 4","인증","Authentication","Nuxt Plugin","Axios","SSR","Session","\u002Fimages\u002Fbrobean\u002Fog-image.png",{"icon":20},{"title":2649,"description":2650},"Nuxt 인증 상태 복구와 Plugin 실행 순서 - Pinia, Axios, Auth Plugin","Nuxt와 Pinia를 사용하는 서비스에서 새로고침 이후 인증 상태를 복구하는 방법과 Nuxt Plugin 실행 순서, Axios Plugin과 Auth Plugin의 의존성 문제를 해결한 과정을 정리합니다.","HmKmCy_Bj3WHw1dlbp0x8jbOjzfKPvD3UpCOje6rHOk",[2653,2655],{"title":234,"path":235,"stem":236,"description":2654,"icon":20,"children":-1},"BroBean을 커플 전용 서비스에서 개인 사용자까지 사용할 수 있도록 확장하면서 개인 모드와 커플 모드를 분리하고, 헤더와 사이드 메뉴의 모드 전환 UI 및 Pinia 기반 상태 관리 구조를 구현한 과정",{"title":242,"path":243,"stem":244,"description":2656,"icon":20,"children":-1},"BroBean의 개인·커플 모드 전환 기능과 씨앗심기·성장일지 백엔드 및 API를 구현하고, 월별 가계부 조회 쿼리를 작성하는 과정에서 쿼리 성능을 다시 고민해본 경험을 정리합니다.",1789947611448]