diff --git a/.claude/skills/component-refactoring/SKILL.md b/.claude/skills/component-refactoring/SKILL.md
index ea695ea442..7006c382c8 100644
--- a/.claude/skills/component-refactoring/SKILL.md
+++ b/.claude/skills/component-refactoring/SKILL.md
@@ -187,7 +187,7 @@ const Template = useMemo(() => {
**When**: Component directly handles API calls, data transformation, or complex async operations.
-**Dify Convention**: Use `@tanstack/react-query` hooks from `web/service/use-*.ts` or create custom data hooks. Project is migrating from SWR to React Query.
+**Dify Convention**: Use `@tanstack/react-query` hooks from `web/service/use-*.ts` or create custom data hooks.
```typescript
// ❌ Before: API logic in component
diff --git a/web/app/(commonLayout)/layout.tsx b/web/app/(commonLayout)/layout.tsx
index 91bdb3f99a..a0ccde957d 100644
--- a/web/app/(commonLayout)/layout.tsx
+++ b/web/app/(commonLayout)/layout.tsx
@@ -1,5 +1,6 @@
import type { ReactNode } from 'react'
import * as React from 'react'
+import { AppInitializer } from '@/app/components/app-initializer'
import AmplitudeProvider from '@/app/components/base/amplitude'
import GA, { GaType } from '@/app/components/base/ga'
import Zendesk from '@/app/components/base/zendesk'
@@ -7,7 +8,6 @@ import GotoAnything from '@/app/components/goto-anything'
import Header from '@/app/components/header'
import HeaderWrapper from '@/app/components/header/header-wrapper'
import ReadmePanel from '@/app/components/plugins/readme-panel'
-import SwrInitializer from '@/app/components/swr-initializer'
import { AppContextProvider } from '@/context/app-context'
import { EventEmitterContextProvider } from '@/context/event-emitter'
import { ModalContextProvider } from '@/context/modal-context'
@@ -20,7 +20,7 @@ const Layout = ({ children }: { children: ReactNode }) => {
<>
-
+
@@ -38,7 +38,7 @@ const Layout = ({ children }: { children: ReactNode }) => {
-
+
>
)
}
diff --git a/web/app/account/(commonLayout)/layout.tsx b/web/app/account/(commonLayout)/layout.tsx
index f264441b86..e4125015d9 100644
--- a/web/app/account/(commonLayout)/layout.tsx
+++ b/web/app/account/(commonLayout)/layout.tsx
@@ -1,9 +1,9 @@
import type { ReactNode } from 'react'
import * as React from 'react'
+import { AppInitializer } from '@/app/components/app-initializer'
import AmplitudeProvider from '@/app/components/base/amplitude'
import GA, { GaType } from '@/app/components/base/ga'
import HeaderWrapper from '@/app/components/header/header-wrapper'
-import SwrInitor from '@/app/components/swr-initializer'
import { AppContextProvider } from '@/context/app-context'
import { EventEmitterContextProvider } from '@/context/event-emitter'
import { ModalContextProvider } from '@/context/modal-context'
@@ -15,7 +15,7 @@ const Layout = ({ children }: { children: ReactNode }) => {
<>
-
+
@@ -30,7 +30,7 @@ const Layout = ({ children }: { children: ReactNode }) => {
-
+
>
)
}
diff --git a/web/app/components/swr-initializer.tsx b/web/app/components/app-initializer.tsx
similarity index 80%
rename from web/app/components/swr-initializer.tsx
rename to web/app/components/app-initializer.tsx
index 31be6d62b5..0f710abf39 100644
--- a/web/app/components/swr-initializer.tsx
+++ b/web/app/components/app-initializer.tsx
@@ -3,7 +3,6 @@
import type { ReactNode } from 'react'
import { usePathname, useRouter, useSearchParams } from 'next/navigation'
import { useCallback, useEffect, useState } from 'react'
-import { SWRConfig } from 'swr'
import {
EDUCATION_VERIFY_URL_SEARCHPARAMS_ACTION,
EDUCATION_VERIFYING_LOCALSTORAGE_ITEM,
@@ -11,12 +10,13 @@ import {
import { fetchSetupStatus } from '@/service/common'
import { resolvePostLoginRedirect } from '../signin/utils/post-login-redirect'
-type SwrInitializerProps = {
+type AppInitializerProps = {
children: ReactNode
}
-const SwrInitializer = ({
+
+export const AppInitializer = ({
children,
-}: SwrInitializerProps) => {
+}: AppInitializerProps) => {
const router = useRouter()
const searchParams = useSearchParams()
// Tokens are now stored in cookies, no need to check localStorage
@@ -69,20 +69,5 @@ const SwrInitializer = ({
})()
}, [isSetupFinished, router, pathname, searchParams])
- return init
- ? (
- new Map(),
- }}
- >
- {children}
-
- )
- : null
+ return init ? children : null
}
-
-export default SwrInitializer
diff --git a/web/knip.config.ts b/web/knip.config.ts
index 6151a78af7..a63f742a21 100644
--- a/web/knip.config.ts
+++ b/web/knip.config.ts
@@ -76,7 +76,7 @@ const config: KnipConfig = {
// Browser initialization (runs on client startup)
'app/components/browser-initializer.tsx!',
'app/components/sentry-initializer.tsx!',
- 'app/components/swr-initializer.tsx!',
+ 'app/components/app-initializer.tsx!',
// i18n initialization (server and client)
'app/components/i18n.tsx!',
diff --git a/web/package.json b/web/package.json
index fe6b8ec9f7..d506bdaa0d 100644
--- a/web/package.json
+++ b/web/package.json
@@ -138,7 +138,6 @@
"semver": "^7.7.3",
"sharp": "^0.33.5",
"sortablejs": "^1.15.6",
- "swr": "^2.3.6",
"tailwind-merge": "^2.6.0",
"tldts": "^7.0.17",
"use-context-selector": "^2.0.0",
diff --git a/web/pnpm-lock.yaml b/web/pnpm-lock.yaml
index 51f421e90b..9677c14edd 100644
--- a/web/pnpm-lock.yaml
+++ b/web/pnpm-lock.yaml
@@ -330,9 +330,6 @@ importers:
sortablejs:
specifier: ^1.15.6
version: 1.15.6
- swr:
- specifier: ^2.3.6
- version: 2.3.7(react@19.2.3)
tailwind-merge:
specifier: ^2.6.0
version: 2.6.0
@@ -7950,11 +7947,6 @@ packages:
resolution: {integrity: sha512-ot0WnXS9fgdkgIcePe6RHNk1WA8+muPa6cSjeR3V8K27q9BB1rTE3R1p7Hv0z1ZyAc8s6Vvv8DIyWf681MAt0w==}
engines: {node: '>= 0.4'}
- swr@2.3.7:
- resolution: {integrity: sha512-ZEquQ82QvalqTxhBVv/DlAg2mbmUjF4UgpPg9wwk4ufb9rQnZXh1iKyyKBqV6bQGu1Ie7L1QwSYO07qFIa1p+g==}
- peerDependencies:
- react: ^16.11.0 || ^17.0.0 || ^18.0.0 || ^19.0.0
-
symbol-tree@3.2.4:
resolution: {integrity: sha512-9QNk5KwDF+Bvz+PyObkmSYjI5ksVUYtjW7AU22r2NKcfLJcXp96hkDWU3+XndOsUb+AQ9QhfzfCT2O+CNWT5Tw==}
@@ -17385,12 +17377,6 @@ snapshots:
supports-preserve-symlinks-flag@1.0.0: {}
- swr@2.3.7(react@19.2.3):
- dependencies:
- dequal: 2.0.3
- react: 19.2.3
- use-sync-external-store: 1.6.0(react@19.2.3)
-
symbol-tree@3.2.4: {}
synckit@0.11.11:
diff --git a/web/scripts/analyze-component.js b/web/scripts/analyze-component.js
index 8b01744b3d..9347a82fa5 100755
--- a/web/scripts/analyze-component.js
+++ b/web/scripts/analyze-component.js
@@ -46,7 +46,6 @@ Features Detected:
${analysis.hasEvents ? '✓' : '✗'} Event handlers
${analysis.hasRouter ? '✓' : '✗'} Next.js routing
${analysis.hasAPI ? '✓' : '✗'} API calls
- ${analysis.hasSWR ? '✓' : '✗'} SWR data fetching
${analysis.hasReactQuery ? '✓' : '✗'} React Query
${analysis.hasAhooks ? '✓' : '✗'} ahooks
${analysis.hasForwardRef ? '✓' : '✗'} Ref forwarding (forwardRef)
@@ -236,7 +235,7 @@ Create the test file at: ${testPath}
// ===== API Calls =====
if (analysis.hasAPI) {
guidelines.push('🌐 API calls detected:')
- guidelines.push(' - Mock API calls/hooks (useSWR, useQuery, fetch, etc.)')
+ guidelines.push(' - Mock API calls/hooks (useQuery, useMutation, fetch, etc.)')
guidelines.push(' - Test loading, success, and error states')
guidelines.push(' - Focus on component behavior, not the data fetching lib')
}
diff --git a/web/scripts/component-analyzer.js b/web/scripts/component-analyzer.js
index c53b652bc2..8bd3dc4409 100644
--- a/web/scripts/component-analyzer.js
+++ b/web/scripts/component-analyzer.js
@@ -21,6 +21,7 @@ export class ComponentAnalyzer {
const resolvedPath = absolutePath ?? path.resolve(process.cwd(), filePath)
const fileName = path.basename(filePath, path.extname(filePath))
const lineCount = code.split('\n').length
+ const hasReactQuery = /\buse(?:Query|Queries|InfiniteQuery|SuspenseQuery|SuspenseInfiniteQuery|Mutation)\b/.test(code)
// Calculate complexity metrics
const { total: rawComplexity, max: rawMaxComplexity } = this.calculateCognitiveComplexity(code)
@@ -44,14 +45,13 @@ export class ComponentAnalyzer {
hasMemo: code.includes('useMemo'),
hasEvents: /on[A-Z]\w+/.test(code),
hasRouter: code.includes('useRouter') || code.includes('usePathname'),
- hasAPI: code.includes('service/') || code.includes('fetch(') || code.includes('useSWR'),
+ hasAPI: code.includes('service/') || code.includes('fetch(') || hasReactQuery,
hasForwardRef: code.includes('forwardRef'),
hasComponentMemo: /React\.memo|memo\(/.test(code),
hasSuspense: code.includes('Suspense') || /\blazy\(/.test(code),
hasPortal: code.includes('createPortal'),
hasImperativeHandle: code.includes('useImperativeHandle'),
- hasSWR: code.includes('useSWR'),
- hasReactQuery: code.includes('useQuery') || code.includes('useMutation'),
+ hasReactQuery,
hasAhooks: code.includes('from \'ahooks\''),
complexity,
maxComplexity,
diff --git a/web/scripts/refactor-component.js b/web/scripts/refactor-component.js
index f890540515..a054650ba3 100644
--- a/web/scripts/refactor-component.js
+++ b/web/scripts/refactor-component.js
@@ -123,7 +123,6 @@ Usage: ${analysis.usageCount} reference${analysis.usageCount !== 1
${analysis.hasRouter ? '✓' : '✗'} Next.js routing
${analysis.hasAPI ? '✓' : '✗'} API calls
${analysis.hasReactQuery ? '✓' : '✗'} React Query
- ${analysis.hasSWR ? '✓' : '✗'} SWR (should migrate to React Query)
${analysis.hasAhooks ? '✓' : '✗'} ahooks
━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
@@ -150,7 +149,7 @@ ${this.buildRequirements(analysis)}
Follow Dify project conventions:
- Place extracted hooks in \`hooks/\` subdirectory or as \`use-.ts\`
-- Use React Query (\`@tanstack/react-query\`) for data fetching, not SWR
+- Use React Query (\`@tanstack/react-query\`) for data fetching
- Follow existing patterns in \`web/service/use-*.ts\` for API hooks
- Keep each new file under 300 lines
- Maintain TypeScript strict typing
@@ -173,12 +172,8 @@ After refactoring, verify:
}
// Priority 2: Extract API/data logic
- if (analysis.hasAPI && (analysis.hasEffects || analysis.hasSWR)) {
- if (analysis.hasSWR) {
- actions.push('🔄 MIGRATE SWR TO REACT QUERY: Replace useSWR with useQuery from @tanstack/react-query')
- }
+ if (analysis.hasAPI)
actions.push('🌐 EXTRACT DATA HOOK: Move API calls and data fetching logic into a dedicated hook using React Query')
- }
// Priority 3: Split large components
if (analysis.lineCount > 300) {
diff --git a/web/service/annotation.ts b/web/service/annotation.ts
index acdb944386..8a19425044 100644
--- a/web/service/annotation.ts
+++ b/web/service/annotation.ts
@@ -1,4 +1,3 @@
-import type { Fetcher } from 'swr'
import type { AnnotationCreateResponse, AnnotationEnableStatus, AnnotationItemBasic, EmbeddingModelConfig } from '@/app/components/app/annotation/type'
import { ANNOTATION_DEFAULT } from '@/config'
import { del, get, post } from './base'
@@ -44,11 +43,11 @@ export const addAnnotation = (appId: string, body: AnnotationItemBasic) => {
return post(`apps/${appId}/annotations`, { body })
}
-export const annotationBatchImport: Fetcher<{ job_id: string, job_status: string }, { url: string, body: FormData }> = ({ url, body }) => {
+export const annotationBatchImport = ({ url, body }: { url: string, body: FormData }): Promise<{ job_id: string, job_status: string }> => {
return post<{ job_id: string, job_status: string }>(url, { body }, { bodyStringify: false, deleteContentType: true })
}
-export const checkAnnotationBatchImportProgress: Fetcher<{ job_id: string, job_status: string }, { jobID: string, appId: string }> = ({ jobID, appId }) => {
+export const checkAnnotationBatchImportProgress = ({ jobID, appId }: { jobID: string, appId: string }): Promise<{ job_id: string, job_status: string }> => {
return get<{ job_id: string, job_status: string }>(`/apps/${appId}/annotations/batch-import-status/${jobID}`)
}
diff --git a/web/service/apps.ts b/web/service/apps.ts
index 1e3c93a33a..60052e27dd 100644
--- a/web/service/apps.ts
+++ b/web/service/apps.ts
@@ -12,7 +12,7 @@ export const fetchAppDetail = ({ url, id }: { url: string, id: string }): Promis
return get(`${url}/${id}`)
}
-// Direct API call function for non-SWR usage
+// Direct API call function for one-off usage
export const fetchAppDetailDirect = async ({ url, id }: { url: string, id: string }): Promise => {
return get(`${url}/${id}`)
}
diff --git a/web/service/plugins.ts b/web/service/plugins.ts
index afaebf4fb5..cf05d8c20d 100644
--- a/web/service/plugins.ts
+++ b/web/service/plugins.ts
@@ -1,4 +1,3 @@
-import type { Fetcher } from 'swr'
import type {
MarketplaceCollectionPluginsResponse,
MarketplaceCollectionsResponse,
@@ -82,11 +81,11 @@ export const fetchPluginInfoFromMarketPlace = async ({
return getMarketplace<{ data: { plugin: PluginInfoFromMarketPlace, version: { version: string } } }>(`/plugins/${org}/${name}`)
}
-export const fetchMarketplaceCollections: Fetcher = ({ url }) => {
+export const fetchMarketplaceCollections = ({ url }: { url: string }): Promise => {
return get(url)
}
-export const fetchMarketplaceCollectionPlugins: Fetcher = ({ url }) => {
+export const fetchMarketplaceCollectionPlugins = ({ url }: { url: string }): Promise => {
return get(url)
}