diff --git a/examples/next-wagmi-app-router/src/app/layout.tsx b/examples/next-wagmi-app-router/src/app/layout.tsx
index 9b363bc6b5..5492f2d11b 100755
--- a/examples/next-wagmi-app-router/src/app/layout.tsx
+++ b/examples/next-wagmi-app-router/src/app/layout.tsx
@@ -1,5 +1,7 @@
import { Metadata } from 'next'
+import { headers } from 'next/headers'
+import { wagmiAdapter } from '@/config'
import ContextProvider from '@/context'
import './globals.css'
@@ -33,11 +35,18 @@ export const metadata: Metadata = {
}
}
-export default function RootLayout({ children }: { children: React.ReactNode }) {
+export default async function RootLayout({ children }: { children: React.ReactNode }) {
+ const cookies = (await headers()).get('cookie')
+
+ // Reset SSR state when no cookies to prevent cross-request state leakage
+ if (!cookies) {
+ wagmiAdapter.resetSSRState()
+ }
+
return (
- {children}
+ {children}
)
diff --git a/examples/next-wagmi-app-router/src/config/index.ts b/examples/next-wagmi-app-router/src/config/index.ts
index 9c4a0aab22..0a49d67d6f 100755
--- a/examples/next-wagmi-app-router/src/config/index.ts
+++ b/examples/next-wagmi-app-router/src/config/index.ts
@@ -22,7 +22,8 @@ export const networks = [mainnet, polygon, arbitrum, optimism] as [
// Setup wagmi adapter
export const wagmiAdapter = new WagmiAdapter({
networks,
- projectId
+ projectId,
+ ssr: true
})
// Create modal
diff --git a/packages/adapters/wagmi/src/client.ts b/packages/adapters/wagmi/src/client.ts
index 745f07a8bd..c907a93005 100644
--- a/packages/adapters/wagmi/src/client.ts
+++ b/packages/adapters/wagmi/src/client.ts
@@ -116,6 +116,46 @@ export class WagmiAdapter extends AdapterBlueprint {
this.setupWatchers()
}
+ /**
+ * Resets the wagmi config state for SSR (Server-Side Rendering).
+ *
+ * In Next.js App Router with SSR, the WagmiAdapter is typically created as a module-level
+ * singleton. This can cause cross-request state leakage where one user's wallet connection
+ * state bleeds into another user's server-rendered HTML.
+ *
+ * Call this method before each SSR request when `cookieToInitialState` returns undefined
+ * (i.e., when the user has no wallet cookies) to reset the config state and prevent
+ * showing another user's wallet address.
+ *
+ * @example
+ * ```tsx
+ * // In your layout.tsx (server component)
+ * const cookies = (await headers()).get('cookie')
+ * const initialState = cookieToInitialState(wagmiAdapter.wagmiConfig, cookies)
+ *
+ * // Reset state if no cookies to prevent state leakage
+ * if (!initialState) {
+ * wagmiAdapter.resetSSRState()
+ * }
+ * ```
+ */
+ public resetSSRState(): void {
+ if (!this.wagmiConfig) {
+ return
+ }
+
+ // Reset the wagmi config state to disconnected
+ this.wagmiConfig.setState(state => ({
+ ...state,
+ connections: new Map(),
+ current: null,
+ status: 'disconnected'
+ }))
+
+ // Clear the connections map
+ this.wagmiConfig.state.connections.clear()
+ }
+
override async getAccounts(
params: AdapterBlueprint.GetAccountsParams
): Promise {
diff --git a/packages/adapters/wagmi/src/tests/client.test.ts b/packages/adapters/wagmi/src/tests/client.test.ts
index 3345fd07f6..9df7d6c46f 100644
--- a/packages/adapters/wagmi/src/tests/client.test.ts
+++ b/packages/adapters/wagmi/src/tests/client.test.ts
@@ -1734,3 +1734,105 @@ describe('WagmiAdapter - BaseAccount lazy initialization', () => {
expect(result.provider).toBe(providedProvider as any)
})
})
+
+describe('WagmiAdapter - resetSSRState', () => {
+ it('should reset wagmi config state to disconnected', () => {
+ const mockConnections = new Map([
+ ['connector1', { connector: { id: 'connector1' }, accounts: ['0x123'] }]
+ ])
+
+ const setStateSpy = vi.fn((fn: (state: any) => any) => {
+ const newState = fn({
+ connections: mockConnections,
+ current: 'connector1',
+ status: 'connected'
+ })
+ expect(newState.connections).toBeInstanceOf(Map)
+ expect(newState.connections.size).toBe(0)
+ expect(newState.current).toBeNull()
+ expect(newState.status).toBe('disconnected')
+ })
+
+ const mockConfig = {
+ chains: mockCaipNetworks,
+ connectors: [],
+ state: {
+ connections: mockConnections
+ },
+ setState: setStateSpy,
+ _internal: {
+ connectors: {
+ setup: vi.fn(connector => connector),
+ setState: vi.fn()
+ }
+ }
+ } as unknown as Config
+
+ vi.spyOn(wagmiCore, 'createConfig').mockReturnValue(mockConfig)
+
+ const adapter = new WagmiAdapter({
+ networks: mockNetworks,
+ projectId: mockProjectId,
+ ssr: true
+ })
+
+ adapter.wagmiConfig = mockConfig
+
+ adapter.resetSSRState()
+
+ expect(setStateSpy).toHaveBeenCalled()
+ expect(mockConnections.size).toBe(0)
+ })
+
+ it('should return early when wagmiConfig is not set', () => {
+ const adapter = new WagmiAdapter({
+ networks: mockNetworks,
+ projectId: mockProjectId,
+ ssr: true
+ })
+
+ // Set wagmiConfig to undefined to test early return
+ adapter.wagmiConfig = undefined as unknown as Config
+
+ // This should not throw
+ expect(() => adapter.resetSSRState()).not.toThrow()
+ })
+
+ it('should clear connections map after setState', () => {
+ const mockConnections = new Map([
+ ['connector1', { connector: { id: 'connector1' }, accounts: ['0x123'] }],
+ ['connector2', { connector: { id: 'connector2' }, accounts: ['0x456'] }]
+ ])
+
+ const mockConfig = {
+ chains: mockCaipNetworks,
+ connectors: [],
+ state: {
+ connections: mockConnections
+ },
+ setState: vi.fn(),
+ _internal: {
+ connectors: {
+ setup: vi.fn(connector => connector),
+ setState: vi.fn()
+ }
+ }
+ } as unknown as Config
+
+ vi.spyOn(wagmiCore, 'createConfig').mockReturnValue(mockConfig)
+
+ const adapter = new WagmiAdapter({
+ networks: mockNetworks,
+ projectId: mockProjectId,
+ ssr: true
+ })
+
+ adapter.wagmiConfig = mockConfig
+
+ expect(mockConnections.size).toBe(2)
+
+ adapter.resetSSRState()
+
+ expect(mockConnections.size).toBe(0)
+ })
+})