@uniai-fe/next-providers 0.1.15 → 0.1.17

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md CHANGED
@@ -1,11 +1,36 @@
1
- # client / next-providers
1
+ # @uniai-fe/next-providers
2
2
 
3
- 상태관리 도구 \<Provider /> 보조 도구 커스텀 관리
3
+ Next.js Root Layout에서 반복되는 Provider 묶음과 런타임 가드를 번에 구성할 수 있는 패키지입니다.
4
4
 
5
- ## Exported utilities
5
+ ## 설치
6
6
 
7
- - `NextRoots` — React Query/Jotai/Airtable 등 루트 Provider 묶음
8
- - `modalProviders` 슬롯을 통해 `@uniai-fe/ui-legacy`나 신규 `@uniai-fe/uds-*` 모달 Provider를 서비스 측에서 직접 주입한다.
9
- - `onRouteChange` 콜백을 전달해 라우트 변경 시 모달 스택 초기화 등 앱 전용 부가 로직을 실행할 수 있다.
10
- - `SetQueryCookie` — query string 기반 쿠키 동기화
11
- - `RuntimeRouteGuard` — 런타임 환경에 따라 `/mobile` 등 경로를 강제하거나 차단하는 가드 (disabled prop으로 토글)
7
+ ```bash
8
+ pnpm add @uniai-fe/next-providers
9
+ ```
10
+
11
+ ## 사용 예시
12
+
13
+ ```tsx
14
+ import NextRoots, { RuntimeRouteGuard } from "@uniai-fe/next-providers";
15
+
16
+ export default function RootLayout({
17
+ children,
18
+ }: {
19
+ children: React.ReactNode;
20
+ }) {
21
+ return (
22
+ <html lang="ko">
23
+ <body>
24
+ <RuntimeRouteGuard mobileBasePath="/mobile" desktopFallbackPath="/" />
25
+ <NextRoots locale="ko" modalProviders={<ModalProvider />}>
26
+ {children}
27
+ </NextRoots>
28
+ </body>
29
+ </html>
30
+ );
31
+ }
32
+ ```
33
+
34
+ ## Codex 참고용 카탈로그
35
+
36
+ `NextRoots`, `RuntimeRouteGuard`, `SetQueryCookie` 등 세부 엔트리와 사용 팁은 [`catalog.md`](./catalog.md)에 정리했습니다. 새로운 Next.js 앱을 만들거나 Provider 구성을 바꿀 때 catalog를 먼저 확인하세요.
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@uniai-fe/next-providers",
3
- "version": "0.1.15",
3
+ "version": "0.1.17",
4
4
  "description": "Next.js State Providers for UNIAI FE Projects",
5
5
  "type": "module",
6
6
  "private": false,
@@ -10,7 +10,7 @@
10
10
  "publishConfig": {
11
11
  "access": "public"
12
12
  },
13
- "packageManager": "pnpm@10.28.0",
13
+ "packageManager": "pnpm@10.33.3",
14
14
  "engines": {
15
15
  "node": ">=24",
16
16
  "pnpm": ">=10"
@@ -34,7 +34,8 @@
34
34
  "module:typecheck": "pnpm typecheck",
35
35
  "module:build": "pnpm build",
36
36
  "next-providers:build": "pnpm run build",
37
- "next-providers:dev": "pnpm run dev"
37
+ "next-providers:dev": "pnpm run dev",
38
+ "publish:public": "bash ../../../scripts/npm-publish.sh"
38
39
  },
39
40
  "main": "./src/index.tsx",
40
41
  "module": "./src/index.tsx",
@@ -45,12 +46,16 @@
45
46
  "next": ">= 15",
46
47
  "react": ">= 19",
47
48
  "react-dom": ">= 19",
48
- "@tanstack/react-query": ">= 5",
49
+ "@tanstack/react-query": ">= 5.100.9",
49
50
  "jotai": ">= 2",
50
51
  "styled-components": ">= 6",
51
52
  "airtable": ">= 0.12",
52
53
  "@uniai-fe/runtime-env": ">= 0.1.6",
53
- "@uniai-fe/util-jotai": ">= 0.1.5"
54
+ "@uniai-fe/util-jotai": ">= 0.1.5",
55
+ "@uniai-fe/i18n": ">= 0.1.5",
56
+ "@uniai-fe/next-devkit": ">= 0.1.7",
57
+ "@uniai-fe/util-functions": ">= 0.2.11",
58
+ "jotai-tanstack-query": ">= 0.11"
54
59
  },
55
60
  "peerDependenciesMeta": {
56
61
  "optional": {
@@ -67,19 +72,19 @@
67
72
  "@uniai-fe/runtime-env": "workspace:*",
68
73
  "@uniai-fe/util-jotai": "workspace:*",
69
74
  "@uniai-fe/i18n": "workspace:*",
70
- "@tanstack/react-query": "^5.90.16",
75
+ "@tanstack/react-query": "^5.100.9",
71
76
  "@types/node": "^24.10.2",
72
- "@types/react": "^19.2.8",
77
+ "@types/react": "^19.2.14",
73
78
  "@types/react-dom": "^19.2.3",
74
79
  "airtable": "^0.12.2",
75
80
  "eslint": "^9.39.2",
76
- "jotai": "^2.16.1",
81
+ "jotai": "^2.20.0",
77
82
  "jotai-tanstack-query": "^0.11.0",
78
83
  "next": "^15.5.11",
79
- "prettier": "^3.7.4",
80
- "react": "^19.2.3",
81
- "react-dom": "^19.2.3",
84
+ "prettier": "^3.8.3",
85
+ "react": "^19.2.5",
86
+ "react-dom": "^19.2.5",
82
87
  "styled-components": "6.1.19",
83
- "typescript": "~5.9.3"
88
+ "typescript": "5.9.3"
84
89
  }
85
90
  }
package/src/NextRoots.tsx CHANGED
@@ -1,11 +1,37 @@
1
1
  import StyledComponentsRegistry from "./lib/StyledComponentsRegistry";
2
2
  import AirtableProvider from "./provider/AirtableProvider";
3
- import { JotaiProvider } from "./provider/JotaiProvider";
4
- import { ReactQueryProvider } from "./provider/ReactQueryProvider";
3
+ import {
4
+ JotaiProvider,
5
+ type JotaiProviderProps,
6
+ } from "./provider/JotaiProvider";
7
+ import {
8
+ ReactQueryProvider,
9
+ type ReactQueryProviderProps,
10
+ } from "./provider/ReactQueryProvider";
5
11
  import SetQueryCookie from "./lib/SetQueryCookie";
6
12
  import SystemThemeChecker from "./lib/SystemThemeChecker";
7
13
  import type { SystemLanguageType } from "@uniai-fe/next-devkit/types";
8
14
 
15
+ /**
16
+ * Next.js Root Layout Provider 구성 props
17
+ * @typedef {object} NextRootsProps
18
+ * @property {SystemLanguageType} [locale] AirtableProvider에 전달할 시스템 언어
19
+ * @property {{ exceptPaths: string[] }} [systemThemeOptions] 시스템 테마 체크 옵션
20
+ * @property {React.ReactNode} [modalProviders] 서비스별 modal provider 슬롯
21
+ * @property {Omit<ReactQueryProviderProps, "children">} [reactQueryProviderProps] ReactQueryProvider로 전달할 설정
22
+ * @property {Omit<JotaiProviderProps, "children">} [jotaiProviderProps] JotaiProvider로 전달할 설정
23
+ * @property {React.ReactNode} children Root Provider 하위에 배치할 앱 트리
24
+ */
25
+ type NextRootsProps = {
26
+ locale?: SystemLanguageType;
27
+ systemThemeOptions?: { exceptPaths: string[] };
28
+ modalProviders?: React.ReactNode;
29
+ reactQueryProviderProps?: Omit<ReactQueryProviderProps, "children">;
30
+ jotaiProviderProps?: Omit<JotaiProviderProps, "children">;
31
+
32
+ children: React.ReactNode;
33
+ };
34
+
9
35
  /**
10
36
  * Next.js Root Layout에 적용할 Provider 및 유틸리티 도구 집합
11
37
  * @component
@@ -13,38 +39,42 @@ import type { SystemLanguageType } from "@uniai-fe/next-devkit/types";
13
39
  * @param {SystemLanguageType} [props.locale] 언어설정; "ko", "en"
14
40
  * @param {object} [props.systemThemeOptions] 시스템 테마 체크 옵션
15
41
  * @param {string[]} [props.systemThemeOptions.exceptPaths] 테마 체크 제외 경로
16
- * @param {React.ReactNode} [props.modalProviders] 모달 Provider 슬롯(@uniai-fe/ui-legacy 등 서비스 선택)
42
+ * @param {object} [props.reactQueryProviderProps] React Query Provider 전달 props
43
+ * @param {object} [props.jotaiProviderProps] Jotai Provider 전달 props
44
+ * @param {React.ReactNode} [props.modalProviders] 모달 Provider 슬롯(서비스별 modal provider 선택)
17
45
  * @param {() => void} [props.onRouteChange] 경로 변경 시 실행할 콜백(예: 모달 스택 초기화)
18
46
  * @param {React.ReactNode} props.children
47
+ * @returns {React.JSX.Element}
19
48
  * @desc
20
- * - React Query
21
- * - Jotai
22
- * - Airtable
23
- * - System Theme Checker
24
- * - Router Event Detector
49
+ * - React Query Provider를 최상위에 두어 하위 Jotai hydration에서 동일 QueryClient를 읽을 수 있게 한다.
50
+ * - Jotai Provider는 react-query 연동 atom과 서비스별 store를 함께 제공한다.
51
+ * - StyledComponentsRegistry 내부에서 AirtableProvider와 SystemThemeChecker를 렌더링한다.
52
+ * - modalProviders는 서비스별 modal stack/provider 조합을 children 옆 슬롯으로 합성한다.
25
53
  */
26
54
  export default function NextRoots({
27
55
  locale = "ko",
28
56
  systemThemeOptions,
29
57
  modalProviders,
58
+ reactQueryProviderProps,
59
+ jotaiProviderProps,
30
60
 
31
61
  children,
32
- }: {
33
- locale?: SystemLanguageType;
34
- systemThemeOptions?: { exceptPaths: string[] };
35
- modalProviders?: React.ReactNode;
36
-
37
- children: React.ReactNode;
38
- }) {
62
+ }: NextRootsProps) {
39
63
  return (
40
- <ReactQueryProvider>
41
- <JotaiProvider>
64
+ // React Query client는 JotaiHydrateAtoms에서도 같은 인스턴스를 참조해야 하므로 가장 바깥에 둔다.
65
+ <ReactQueryProvider {...reactQueryProviderProps}>
66
+ {/* Jotai store 설정은 React Query context 하위에서 적용한다. */}
67
+ <JotaiProvider {...jotaiProviderProps}>
42
68
  <SetQueryCookie />
69
+ {/* styled-components SSR registry는 실제 화면 트리를 감싸서 style collection 범위를 만든다. */}
43
70
  <StyledComponentsRegistry>
71
+ {/* locale은 Airtable 기반 다국어/환경 데이터 provider에만 전달한다. */}
44
72
  <AirtableProvider locale={locale}>
45
73
  {children}
74
+ {/* 서비스별 modal provider는 앱 children과 같은 provider scope에 둔다. */}
46
75
  {modalProviders}
47
76
  </AirtableProvider>
77
+ {/* theme check는 provider scope 안에서 route/path 예외 설정을 읽어 실행한다. */}
48
78
  <SystemThemeChecker exceptPaths={systemThemeOptions?.exceptPaths} />
49
79
  </StyledComponentsRegistry>
50
80
  </JotaiProvider>
@@ -1,22 +1,58 @@
1
1
  "use client";
2
2
 
3
+ import { useQueryClient } from "@tanstack/react-query";
3
4
  import { Provider } from "jotai";
4
5
  import { useHydrateAtoms } from "jotai/react/utils";
5
6
  import { queryClientAtom } from "jotai-tanstack-query";
6
- import { getQueryClient } from "./ReactQueryProvider";
7
+ import type { ComponentProps } from "react";
7
8
 
8
- export function JotaiHydrateAtoms({ children }: { children: React.ReactNode }) {
9
- useHydrateAtoms([[queryClientAtom, getQueryClient()]]);
9
+ /**
10
+ * Jotai Provider 전달 props
11
+ * @typedef {ComponentProps<typeof Provider>} JotaiProviderProps
12
+ * @property {React.ReactNode} [children] Jotai store 범위 안에서 렌더링할 하위 요소
13
+ * @property {ReturnType<import("jotai").createStore>} [store] 서비스 또는 테스트에서 주입할 Jotai store 인스턴스
14
+ */
15
+ export type JotaiProviderProps = ComponentProps<typeof Provider>;
16
+
17
+ /**
18
+ * Jotai atom에 React Query client를 연결하는 Hydration 컴포넌트
19
+ * @component
20
+ * @param {Pick<JotaiProviderProps, "children">} props
21
+ * @param {React.ReactNode} [props.children] hydrate 이후 렌더링할 하위 요소
22
+ * @desc
23
+ * - ReactQueryProvider 하위에서만 사용한다.
24
+ * - queryClientAtom에는 React Query Context의 QueryClient와 동일한 인스턴스를 주입한다.
25
+ * - getQueryClient를 직접 재호출하지 않아 SSR/CSR 간 QueryClient 불일치를 방지한다.
26
+ */
27
+ export function JotaiHydrateAtoms({
28
+ children,
29
+ }: Pick<JotaiProviderProps, "children">) {
30
+ // React Query Provider가 context에 올린 QueryClient 인스턴스를 그대로 읽는다.
31
+ const queryClient = useQueryClient();
32
+
33
+ // jotai-tanstack-query atom이 React Query Provider와 동일한 client를 보도록 hydrate한다.
34
+ useHydrateAtoms([[queryClientAtom, queryClient]]);
35
+
36
+ // Hydration 전용 래퍼이므로 별도 DOM 없이 children만 반환한다.
10
37
  return children;
11
38
  }
12
39
 
13
40
  /**
14
41
  * Jotai 상태 공급자
15
42
  * @component
43
+ * @param {JotaiProviderProps} props
44
+ * @param {React.ReactNode} [props.children] Jotai Provider 하위에 배치할 앱 트리
45
+ * @param {JotaiProviderProps["store"]} [props.store] 외부에서 생성한 Jotai store 인스턴스
46
+ * @desc
47
+ * - store가 전달되면 해당 store를 Provider에 그대로 주입한다.
48
+ * - store가 없으면 Jotai Provider의 기본 store 생성 정책을 따른다.
49
+ * - 하위 Hydration 컴포넌트에서 queryClientAtom을 React Query Context와 동기화한다.
16
50
  */
17
- export function JotaiProvider({ children }: { children: React.ReactNode }) {
51
+ export function JotaiProvider({ children, store }: JotaiProviderProps) {
18
52
  return (
19
- <Provider>
53
+ // 서비스별 store 주입을 허용하되 기본 사용자는 별도 설정 없이 사용할 수 있게 둔다.
54
+ <Provider store={store}>
55
+ {/* React Query client atom hydration은 Jotai store 범위 안에서 실행되어야 한다. */}
20
56
  <JotaiHydrateAtoms>{children}</JotaiHydrateAtoms>
21
57
  </Provider>
22
58
  );
@@ -2,10 +2,33 @@
2
2
 
3
3
  import {
4
4
  defaultShouldDehydrateQuery,
5
- isServer,
5
+ environmentManager,
6
6
  QueryClient,
7
7
  QueryClientProvider,
8
8
  } from "@tanstack/react-query";
9
+ import type { QueryClientConfig } from "@tanstack/react-query";
10
+
11
+ /**
12
+ * React Query Provider 전달 props
13
+ * @typedef {object} ReactQueryProviderProps
14
+ * @property {React.ReactNode} children React Query Provider 하위에 배치할 앱 트리
15
+ * @property {QueryClient} [queryClient] 외부에서 직접 생성해 주입할 QueryClient 인스턴스
16
+ * @property {QueryClientConfig} [queryClientConfig] 내부 QueryClient 생성 시 병합할 설정
17
+ */
18
+ export type ReactQueryProviderProps = {
19
+ children: React.ReactNode;
20
+ queryClient?: QueryClient;
21
+ queryClientConfig?: QueryClientConfig;
22
+ };
23
+
24
+ /**
25
+ * QueryClient 기본 옵션 타입
26
+ * @typedef {NonNullable<QueryClientConfig["defaultOptions"]>} QueryClientDefaultOptions
27
+ * @desc QueryClientConfig.defaultOptions는 optional이므로 내부 병합 로직에서는 NonNullable 타입으로 고정한다.
28
+ */
29
+ type QueryClientDefaultOptions = NonNullable<
30
+ QueryClientConfig["defaultOptions"]
31
+ >;
9
32
 
10
33
  /**
11
34
  * 클라이언트 단에서의 React Query client
@@ -14,47 +37,105 @@ import {
14
37
  let browserQueryClient: QueryClient | undefined = undefined;
15
38
 
16
39
  /**
17
- * React Query client 생성
18
- * - 서비스 전역에 적용할 쿼리옵션을 사전에 지정하여 생성
19
- * @return {QueryClient}
40
+ * next-providers가 기본으로 제공하는 React Query 전역 옵션
41
+ * @type {QueryClientDefaultOptions}
42
+ * @desc
43
+ * - 서비스별 queryClientConfig가 전달되면 같은 depth의 옵션을 덮어쓴다.
44
+ * - shouldDehydrateQuery는 pending query dehydration 정책을 기본값 위에 추가한다.
20
45
  */
21
- function makeQueryClient(): QueryClient {
22
- return new QueryClient({
23
- // react-query 전역 설정
46
+ const defaultQueryClientOptions: QueryClientDefaultOptions = {
47
+ queries: {
48
+ // onError: queryErrorHandler,
49
+ staleTime: 600000,
50
+ gcTime: 900000, // 기존 cacheTime에서 명칭 변경, 원래 "가비지 콜렉션 시간"을 의미하였음.
51
+ refetchOnMount: false,
52
+ refetchOnReconnect: false,
53
+ refetchOnWindowFocus: false,
54
+ retry: false,
55
+ },
56
+ dehydrate: {
57
+ // include pending queries in dehydration
58
+ shouldDehydrateQuery: query =>
59
+ defaultShouldDehydrateQuery(query) || query.state.status === "pending",
60
+ },
61
+ };
62
+
63
+ /**
64
+ * 기본 QueryClient 옵션과 서비스별 옵션을 병합한다.
65
+ * @param {QueryClientConfig} [queryClientConfig] 서비스에서 전달한 QueryClient 생성 설정
66
+ * @returns {QueryClientConfig}
67
+ * @desc
68
+ * - 최상위 QueryClientConfig는 서비스 설정을 우선한다.
69
+ * - defaultOptions 하위의 queries/mutations/hydrate/dehydrate는 depth별로 병합한다.
70
+ * - 서비스가 dehydrate.shouldDehydrateQuery를 넘기면 기본 pending dehydration 정책을 대체한다.
71
+ */
72
+ function resolveQueryClientConfig(
73
+ queryClientConfig?: QueryClientConfig,
74
+ ): QueryClientConfig {
75
+ return {
76
+ // queryCache, mutationCache, logger 등 최상위 설정은 서비스 설정을 그대로 보존한다.
77
+ ...queryClientConfig,
24
78
  defaultOptions: {
79
+ // next-providers 기본값을 먼저 놓고, 서비스 defaultOptions로 덮어쓸 수 있게 한다.
80
+ ...defaultQueryClientOptions,
81
+ ...queryClientConfig?.defaultOptions,
25
82
  queries: {
26
- // onError: queryErrorHandler,
27
- staleTime: 600000,
28
- gcTime: 900000, // 기존 cacheTime에서 명칭 변경, 원래 "가비지 콜렉션 시간"을 의미하였음.
29
- refetchOnMount: false,
30
- refetchOnReconnect: false,
31
- refetchOnWindowFocus: false,
32
- retry: false,
83
+ // query 기본 정책은 유지하되 서비스별 query option이 있으면 우선한다.
84
+ ...defaultQueryClientOptions.queries,
85
+ ...queryClientConfig?.defaultOptions?.queries,
86
+ },
87
+ mutations: {
88
+ // mutation 기본 옵션도 future-proofing을 위해 같은 병합 규칙을 적용한다.
89
+ ...defaultQueryClientOptions.mutations,
90
+ ...queryClientConfig?.defaultOptions?.mutations,
91
+ },
92
+ hydrate: {
93
+ // hydrate 옵션은 React Query SSR hydration 정책 확장을 위해 분리 병합한다.
94
+ ...defaultQueryClientOptions.hydrate,
95
+ ...queryClientConfig?.defaultOptions?.hydrate,
33
96
  },
34
97
  dehydrate: {
35
- // include pending queries in dehydration
36
- shouldDehydrateQuery: query =>
37
- defaultShouldDehydrateQuery(query) ||
38
- query.state.status === "pending",
98
+ // dehydrate 옵션은 pending query 포함 정책을 기본값으로 제공한다.
99
+ ...defaultQueryClientOptions.dehydrate,
100
+ ...queryClientConfig?.defaultOptions?.dehydrate,
39
101
  },
40
102
  },
41
- });
103
+ };
42
104
  }
43
105
 
44
106
  /**
45
107
  * React Query client 생성
108
+ * - 서비스 전역에 적용할 쿼리옵션을 사전에 지정하여 생성
109
+ * @param {QueryClientConfig} [queryClientConfig] 기본 옵션과 병합할 QueryClient 생성 설정
46
110
  * @return {QueryClient}
47
111
  */
48
- export function getQueryClient(): QueryClient {
49
- if (isServer) {
112
+ function makeQueryClient(queryClientConfig?: QueryClientConfig): QueryClient {
113
+ // QueryClient 생성 전 next-providers 기본값과 서비스 override를 한 번만 병합한다.
114
+ return new QueryClient(resolveQueryClientConfig(queryClientConfig));
115
+ }
116
+
117
+ /**
118
+ * React Query client 생성
119
+ * @param {QueryClientConfig} [queryClientConfig] 내부 QueryClient 생성 시 사용할 설정
120
+ * @return {QueryClient}
121
+ * @desc
122
+ * - 서버에서는 요청 간 캐시 공유를 피하기 위해 항상 새 QueryClient를 생성한다.
123
+ * - 브라우저에서는 최초 생성한 QueryClient를 재사용해 초기 suspense 중 client 재생성을 막는다.
124
+ * - 브라우저 singleton이 이미 생성된 뒤 들어온 queryClientConfig는 반영되지 않는다.
125
+ */
126
+ export function getQueryClient(
127
+ queryClientConfig?: QueryClientConfig,
128
+ ): QueryClient {
129
+ if (environmentManager.isServer()) {
50
130
  // Server: always make a new query client
51
- return makeQueryClient();
131
+ return makeQueryClient(queryClientConfig);
52
132
  } else {
53
133
  // Browser: make a new query client if we don't already have one
54
134
  // This is very important, so we don't re-make a new client if React
55
135
  // suspends during the initial render. This may not be needed if we
56
136
  // have a suspense boundary BELOW the creation of the query client
57
- if (!browserQueryClient) browserQueryClient = makeQueryClient();
137
+ if (!browserQueryClient)
138
+ browserQueryClient = makeQueryClient(queryClientConfig);
58
139
  return browserQueryClient;
59
140
  }
60
141
  }
@@ -62,21 +143,33 @@ export function getQueryClient(): QueryClient {
62
143
  /**
63
144
  * ReactQuery 상태 공급자
64
145
  * @component
146
+ * @param {ReactQueryProviderProps} props
147
+ * @param {React.ReactNode} props.children React Query Provider 하위에 배치할 앱 트리
148
+ * @param {QueryClient} [props.queryClient] 외부에서 직접 생성해 주입할 QueryClient 인스턴스
149
+ * @param {QueryClientConfig} [props.queryClientConfig] 내부 QueryClient 생성 시 병합할 설정
150
+ * @desc
151
+ * - queryClient가 전달되면 내부 singleton보다 전달받은 인스턴스를 우선한다.
152
+ * - queryClientConfig는 queryClient를 직접 전달하지 않을 때만 내부 생성에 사용한다.
153
+ * - JotaiProvider는 이 Provider 하위에서 useQueryClient로 동일 인스턴스를 공유한다.
65
154
  * @see https://tanstack.com/query/latest/docs/framework/react/guides/advanced-ssr#prefetching-and-dehydrating-data
66
155
  * @see https://tanstack.com/query/latest/docs/framework/react/guides/advanced-ssr#nesting-server-components
67
156
  */
68
157
  export function ReactQueryProvider({
69
158
  children,
70
- }: {
71
- children: React.ReactNode;
72
- }) {
159
+ queryClient,
160
+ queryClientConfig,
161
+ }: ReactQueryProviderProps) {
73
162
  // NOTE: Avoid useState when initializing the query client if you don't
74
163
  // have a suspense boundary between this and the code that may
75
164
  // suspend because React will throw away the client on the initial
76
165
  // render if it suspends and there is no boundary
77
- const queryClient = getQueryClient();
166
+ // 외부 주입 client가 있으면 Provider 전체에서 그 인스턴스를 단일 SOT로 사용한다.
167
+ const resolvedQueryClient = queryClient ?? getQueryClient(queryClientConfig);
78
168
 
79
169
  return (
80
- <QueryClientProvider client={queryClient}>{children}</QueryClientProvider>
170
+ // React Query context에 올린 client는 JotaiHydrateAtoms에서도 동일하게 참조된다.
171
+ <QueryClientProvider client={resolvedQueryClient}>
172
+ {children}
173
+ </QueryClientProvider>
81
174
  );
82
175
  }