diff --git a/web/app/(commonLayout)/layout.tsx b/web/app/(commonLayout)/layout.tsx
index 6092618bfb3..71baf83c795 100644
--- a/web/app/(commonLayout)/layout.tsx
+++ b/web/app/(commonLayout)/layout.tsx
@@ -27,25 +27,26 @@ export default async function Layout({ children }: { children: ReactNode }) {
-
-
-
-
-
-
-
- {children}
-
-
-
-
-
-
-
-
-
-
-
+
+
+
+
+
+
+
+ {children}
+
+
+
+
+
+
+
+
+
+
+
+
>
diff --git a/web/app/components/next-route-state/atoms.ts b/web/app/components/next-route-state/atoms.ts
index 1997f284976..0378415452c 100644
--- a/web/app/components/next-route-state/atoms.ts
+++ b/web/app/components/next-route-state/atoms.ts
@@ -7,6 +7,8 @@ type NextRouteState = {
params: NextRouteParams
}
+// Mirrors Next router state. NextRouteStateBridge force-hydrates this atom on
+// render so feature atoms can read route state without calling router hooks.
const nextRouteStateAtom = atom({
pathname: '',
params: {},
diff --git a/web/app/components/next-route-state/index.tsx b/web/app/components/next-route-state/index.tsx
index 7fb2d83c767..cc0bec4f4cb 100644
--- a/web/app/components/next-route-state/index.tsx
+++ b/web/app/components/next-route-state/index.tsx
@@ -1,24 +1,25 @@
'use client'
+import type { ReactNode } from 'react'
import type { NextRouteParams } from './atoms'
-import { useIsomorphicLayoutEffect } from 'foxact/use-isomorphic-layout-effect'
-import { useSetAtom } from 'jotai'
+import { useHydrateAtoms } from 'jotai/utils'
import { useParams, usePathname } from '@/next/navigation'
import {
setNextRouteStateAtom,
} from './atoms'
-export function NextRouteStateBridge() {
+export function NextRouteStateBridge({ children }: {
+ children: ReactNode
+}) {
const pathname = usePathname()
const params = useParams()
- const setNextRouteState = useSetAtom(setNextRouteStateAtom)
- useIsomorphicLayoutEffect(() => {
- setNextRouteState({
+ useHydrateAtoms([
+ [setNextRouteStateAtom, {
pathname,
params,
- })
- }, [params, pathname, setNextRouteState])
+ }],
+ ] as const, { dangerouslyForceHydrate: true })
- return null
+ return children
}
diff --git a/web/features/deployments/create-release/ui/dialog.tsx b/web/features/deployments/create-release/ui/dialog.tsx
index a0b6dde2fc6..9dd2731af6f 100644
--- a/web/features/deployments/create-release/ui/dialog.tsx
+++ b/web/features/deployments/create-release/ui/dialog.tsx
@@ -43,9 +43,11 @@ function CreateReleaseCloseButton() {
export function CreateReleaseDialogContent() {
return (
-
-
-
+
+
+
+
+
)
}
@@ -75,7 +77,7 @@ function CreateReleaseDialogSurface() {
}
return (
-
+ <>
-
+ >
)
}
diff --git a/web/features/deployments/list/index.tsx b/web/features/deployments/list/index.tsx
index af44215fe14..05eef13a39c 100644
--- a/web/features/deployments/list/index.tsx
+++ b/web/features/deployments/list/index.tsx
@@ -1,37 +1,8 @@
'use client'
-import type { ReactNode } from 'react'
-import { ScopeProvider } from 'jotai-scope'
-import { useQueryState } from 'nuqs'
-import {
- deploymentsListEnvironmentIdAtom,
- deploymentsListKeywordsAtom,
- envFilterQueryState,
- keywordsQueryState,
-} from './state'
+import { DeploymentsListStateBoundary } from './state'
import { DeploymentsListShell } from './ui/shell'
-function DeploymentsListStateBoundary({ children }: {
- children: ReactNode
-}) {
- const [envFilter] = useQueryState('env', envFilterQueryState)
- const [keywords] = useQueryState('keywords', keywordsQueryState)
- const stateKey = `${envFilter ?? 'all'}:${keywords}`
-
- return (
-
- {children}
-
- )
-}
-
export function DeploymentsList() {
return (
diff --git a/web/features/deployments/list/state/index.ts b/web/features/deployments/list/state/index.ts
index 0b8b91deae7..fd8bf674aa7 100644
--- a/web/features/deployments/list/state/index.ts
+++ b/web/features/deployments/list/state/index.ts
@@ -2,10 +2,12 @@
import type { ListAppInstanceSummariesResponse } from '@dify/contracts/enterprise/types.gen'
import type { InfiniteData, QueryKey } from '@tanstack/react-query'
+import type { ReactNode } from 'react'
import { keepPreviousData } from '@tanstack/react-query'
import { atom } from 'jotai'
import { atomWithInfiniteQuery, atomWithQuery } from 'jotai-tanstack-query'
-import { parseAsString } from 'nuqs'
+import { useHydrateAtoms } from 'jotai/utils'
+import { parseAsString, useQueryState } from 'nuqs'
import { consoleQuery } from '@/service/client'
import { getNextPageParamFromPagination, SOURCE_APPS_PAGE_SIZE } from '../../shared/domain/pagination'
import { deploymentStatusPollingInterval } from '../../shared/domain/runtime-status'
@@ -13,8 +15,24 @@ import { deploymentStatusPollingInterval } from '../../shared/domain/runtime-sta
export const envFilterQueryState = parseAsString.withOptions({ history: 'push' })
export const keywordsQueryState = parseAsString.withDefault('').withOptions({ history: 'push' })
-export const deploymentsListKeywordsAtom = atom('')
-export const deploymentsListEnvironmentIdAtom = atom(null)
+// Mirrors nuqs URL state. DeploymentsListStateBoundary force-hydrates these
+// atoms on render so query atoms can read URL filters through Jotai.
+const deploymentsListKeywordsAtom = atom('')
+const deploymentsListEnvironmentIdAtom = atom(null)
+
+export function DeploymentsListStateBoundary({ children }: {
+ children: ReactNode
+}) {
+ const [envFilter] = useQueryState('env', envFilterQueryState)
+ const [keywords] = useQueryState('keywords', keywordsQueryState)
+
+ useHydrateAtoms([
+ [deploymentsListEnvironmentIdAtom, envFilter],
+ [deploymentsListKeywordsAtom, keywords],
+ ] as const, { dangerouslyForceHydrate: true })
+
+ return children
+}
function listDeploymentStatusPollingInterval(data?: InfiniteData) {
const rows = data?.pages?.flatMap(page =>