diff --git a/package.json b/package.json
index 766fded..10b1682 100644
--- a/package.json
+++ b/package.json
@@ -22,6 +22,7 @@
]
},
"dependencies": {
+ "@tanstack/react-query": "^5.90.16",
"@vanilla-extract/css": "^1.18.0",
"@vanilla-extract/vite-plugin": "^5.1.4",
"eslint-import-resolver-typescript": "^4.4.4",
@@ -36,6 +37,7 @@
"@storybook/addon-docs": "^10.1.11",
"@storybook/react-vite": "^10.1.11",
"@svgr/plugin-svgo": "^8.1.0",
+ "@tanstack/react-query-devtools": "^5.91.2",
"@types/node": "^24.10.1",
"@types/react": "^19.2.5",
"@types/react-dom": "^19.2.3",
@@ -55,6 +57,7 @@
"lint-staged": "^16.2.7",
"storybook": "^10.1.11",
"prettier": "3.7.4",
+ "storybook": "^10.1.11",
"typescript": "~5.9.3",
"typescript-eslint": "^8.46.4",
"vite": "^7.2.4",
diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml
index 6856129..c9bdaaf 100644
--- a/pnpm-lock.yaml
+++ b/pnpm-lock.yaml
@@ -8,6 +8,9 @@ importers:
.:
dependencies:
+ '@tanstack/react-query':
+ specifier: ^5.90.16
+ version: 5.90.16(react@19.2.3)
'@vanilla-extract/css':
specifier: ^1.18.0
version: 1.18.0
@@ -45,6 +48,9 @@ importers:
'@svgr/plugin-svgo':
specifier: ^8.1.0
version: 8.1.0(@svgr/core@8.1.0(typescript@5.9.3))(typescript@5.9.3)
+ '@tanstack/react-query-devtools':
+ specifier: ^5.91.2
+ version: 5.91.2(@tanstack/react-query@5.90.16(react@19.2.3))(react@19.2.3)
'@types/node':
specifier: ^24.10.1
version: 24.10.4
@@ -831,6 +837,23 @@ packages:
'@swc/types@0.1.25':
resolution: {integrity: sha512-iAoY/qRhNH8a/hBvm3zKj9qQ4oc2+3w1unPJa2XvTK3XjeLXtzcCingVPw/9e5mn1+0yPqxcBGp9Jf0pkfMb1g==}
+ '@tanstack/query-core@5.90.16':
+ resolution: {integrity: sha512-MvtWckSVufs/ja463/K4PyJeqT+HMlJWtw6PrCpywznd2NSgO3m4KwO9RqbFqGg6iDE8vVMFWMeQI4Io3eEYww==}
+
+ '@tanstack/query-devtools@5.92.0':
+ resolution: {integrity: sha512-N8D27KH1vEpVacvZgJL27xC6yPFUy0Zkezn5gnB3L3gRCxlDeSuiya7fKge8Y91uMTnC8aSxBQhcK6ocY7alpQ==}
+
+ '@tanstack/react-query-devtools@5.91.2':
+ resolution: {integrity: sha512-ZJ1503ay5fFeEYFUdo7LMNFzZryi6B0Cacrgr2h1JRkvikK1khgIq6Nq2EcblqEdIlgB/r7XDW8f8DQ89RuUgg==}
+ peerDependencies:
+ '@tanstack/react-query': ^5.90.14
+ react: ^18 || ^19
+
+ '@tanstack/react-query@5.90.16':
+ resolution: {integrity: sha512-bpMGOmV4OPmif7TNMteU/Ehf/hoC0Kf98PDc0F4BZkFrEapRMEqI/V6YS0lyzwSV6PQpY1y4xxArUIfBW5LVxQ==}
+ peerDependencies:
+ react: ^18 || ^19
+
'@testing-library/dom@10.4.1':
resolution: {integrity: sha512-o4PXJQidqJl82ckFaXUeoAW+XysPLauYI43Abki5hABd853iMhitooc6znOnczgbTYmEP6U6/y1ZyKAIsvMKGg==}
engines: {node: '>=18'}
@@ -3494,6 +3517,21 @@ snapshots:
dependencies:
'@swc/counter': 0.1.3
+ '@tanstack/query-core@5.90.16': {}
+
+ '@tanstack/query-devtools@5.92.0': {}
+
+ '@tanstack/react-query-devtools@5.91.2(@tanstack/react-query@5.90.16(react@19.2.3))(react@19.2.3)':
+ dependencies:
+ '@tanstack/query-devtools': 5.92.0
+ '@tanstack/react-query': 5.90.16(react@19.2.3)
+ react: 19.2.3
+
+ '@tanstack/react-query@5.90.16(react@19.2.3)':
+ dependencies:
+ '@tanstack/query-core': 5.90.16
+ react: 19.2.3
+
'@testing-library/dom@10.4.1':
dependencies:
'@babel/code-frame': 7.27.1
diff --git a/src/app/main.tsx b/src/app/main.tsx
index 6af872b..aa57223 100644
--- a/src/app/main.tsx
+++ b/src/app/main.tsx
@@ -2,6 +2,7 @@ import { StrictMode } from "react";
import { createRoot } from "react-dom/client";
import App from "@app/App";
+import AppProviders from "@app/providers";
import "@app/styles/global.css";
@@ -10,6 +11,8 @@ if (!rootEl) throw new Error('Root element "#root" not found');
createRoot(rootEl).render(
-
+
+
+
);
diff --git a/src/app/providers/index.tsx b/src/app/providers/index.tsx
new file mode 100644
index 0000000..0ff33e1
--- /dev/null
+++ b/src/app/providers/index.tsx
@@ -0,0 +1,7 @@
+import type { PropsWithChildren } from "react";
+
+import QueryProvider from "./query-provider";
+
+export default function AppProviders({ children }: PropsWithChildren) {
+ return {children};
+}
diff --git a/src/app/providers/query-provider.tsx b/src/app/providers/query-provider.tsx
new file mode 100644
index 0000000..20b077c
--- /dev/null
+++ b/src/app/providers/query-provider.tsx
@@ -0,0 +1,26 @@
+import type { PropsWithChildren } from "react";
+import { Suspense, lazy } from "react";
+import { QueryClientProvider } from "@tanstack/react-query";
+
+import { queryClient } from "@/shared/api";
+
+const ReactQueryDevtools = import.meta.env.DEV
+ ? lazy(async () => {
+ const mod = await import("@tanstack/react-query-devtools");
+ return { default: mod.ReactQueryDevtools };
+ })
+ : null;
+
+export default function QueryProvider({ children }: PropsWithChildren) {
+ return (
+
+ {children}
+
+ {import.meta.env.DEV && ReactQueryDevtools ? (
+
+
+
+ ) : null}
+
+ );
+}
diff --git a/src/shared/api/index.ts b/src/shared/api/index.ts
new file mode 100644
index 0000000..4a7c883
--- /dev/null
+++ b/src/shared/api/index.ts
@@ -0,0 +1 @@
+export { queryClient } from "./query-client";
\ No newline at end of file
diff --git a/src/shared/api/query-client.ts b/src/shared/api/query-client.ts
new file mode 100644
index 0000000..f889618
--- /dev/null
+++ b/src/shared/api/query-client.ts
@@ -0,0 +1,36 @@
+import { MutationCache, QueryCache, QueryClient } from "@tanstack/react-query";
+
+export const queryClient = new QueryClient({
+ defaultOptions: {
+ queries: {
+ retry: 0,
+ refetchOnWindowFocus: false,
+ refetchOnReconnect: true,
+
+ staleTime: 30 * 1000,
+ gcTime: 10 * 60 * 1000,
+
+ throwOnError: false,
+ },
+ mutations: {
+ retry: 0,
+ throwOnError: false,
+ },
+ },
+
+ queryCache: new QueryCache({
+ onError: (error: unknown) => {
+ // TODO: API 초기 세팅 PR merge 후 공통 에러 핸들러 연결
+ // handleApiError(error);
+ console.error(error);
+ },
+ }),
+
+ mutationCache: new MutationCache({
+ onError: (error: unknown) => {
+ // TODO: API 초기 세팅 PR merge 후 공통 에러 핸들러 연결
+ // handleApiError(error);
+ console.error(error);
+ },
+ }),
+});