Skip to content

Conversation

@mehdimahmoudi
Copy link

Pull Request: Fix locale caching in getPayload singleton

Description

Fixes an issue where the Payload singleton caches the locale from the first request and ignores subsequent locale changes, even when the locale parameter is explicitly passed to queries in the Local API.

Problem

When using Payload 3.x with Next.js and localization (e.g., next-intl), the getPayload() function creates a singleton instance that persists across requests. This singleton maintains the locale context from its initialization and doesn't respect the locale parameter passed to subsequent queries like find(), findByID(), etc.

Reproduction:

  1. Initialize Payload with locale 'en' and query a collection
  2. Change the app locale to 'fr' (via cookie or next-intl)
  3. Query the same collection with locale: 'fr'
  4. Result: Content returns in 'en' instead of 'fr'
  5. Restarting the app with 'fr' locale works correctly

Root Cause

The Payload singleton is stored at (global as any)._payload as a Map with key 'default'. Once initialized, operations use this cached instance which may have stale locale context.

Solution

Add a clearCache() method to allow developers to manually flush the Payload singleton cache when locale changes or other scenarios require re-initialization.

Changes

File: packages/payload/src/index.ts

Add the following export after the getPayload function:

/**
 * Clears the cached Payload instance.
 * Useful for scenarios where configuration changes require re-initialization,
 * such as locale changes in i18n applications.
 * 
 * @example
 * ```ts
 * import { clearPayloadCache, getPayload } from 'payload'
 * import config from '@payload-config'
 * 
 * // Clear cache when locale changes
 * await clearPayloadCache()
 * 
 * // Next getPayload call will create fresh instance
 * const payload = await getPayload({ config })
 * ```
 */
export async function clearPayloadCache(): Promise<void> {
  if (!(global as any)._payload) {
    return
  }

  const globalPayloadMap = (global as any)._payload as Map<string, any>

  if (!globalPayloadMap.has('default')) {
    return
  }

  const cached = globalPayloadMap.get('default')

  // Properly destroy database connections
  if (cached?.payload?.db?.destroy) {
    try {
      await cached.payload.db.destroy()
    } catch (error) {
      // Log but don't throw - cache clearing should be non-blocking
      if (cached?.payload?.logger) {
        cached.payload.logger.error({
          err: error,
          msg: 'Error destroying Payload database connection during cache clear',
        })
      }
    }
  }

  // Close WebSocket connection if exists (HMR)
  if (cached?.ws) {
    try {
      cached.ws.close()
    } catch (error) {
      // Silently ignore WebSocket close errors
    }
  }

  // Clear from cache
  globalPayloadMap.delete('default')
}

File: packages/payload/src/types/index.ts

Add type export for the new function (if needed for type definitions).

Usage Example

For developers using Payload with Next.js and i18n:

// lib/payload.ts
import { clearPayloadCache, getPayload } from 'payload'
import config from '@payload-config'
import { getLocale } from 'next-intl/server'

let lastLocale: string | null = null

export async function getPayloadClient() {
  const currentLocale = await getLocale()
  
  // Clear cache if locale changed
  if (lastLocale !== null && lastLocale !== currentLocale) {
    await clearPayloadCache()
  }
  
  lastLocale = currentLocale
  
  return await getPayload({ config })
}

Testing

Manual Testing

  1. Create a Payload project with localization enabled
  2. Create localized content in multiple languages
  3. Query content with different locales in the same app lifecycle
  4. Verify content matches the requested locale

Test Case Example

import { clearPayloadCache, getPayload } from 'payload'
import config from './test-config'

describe('clearPayloadCache', () => {
  it('should allow re-initialization after cache clear', async () => {
    // First initialization
    const payload1 = await getPayload({ config })
    
    // Clear cache
    await clearPayloadCache()
    
    // Second initialization should create new instance
    const payload2 = await getPayload({ config })
    
    // Instances should work correctly
    expect(payload1).toBeDefined()
    expect(payload2).toBeDefined()
  })
  
  it('should handle multiple cache clears safely', async () => {
    await clearPayloadCache()
    await clearPayloadCache() // Should not throw
    
    const payload = await getPayload({ config })
    expect(payload).toBeDefined()
  })
})

Breaking Changes

None. This is an additive change that introduces a new exported function.

Related Issues

  • Locale parameter ignored in Local API queries
  • Need to restart app for locale changes to take effect
  • Similar to #XXXX (if any related issues exist)

Additional Notes

This fix provides a workaround for the locale caching issue. A more comprehensive solution might involve:

  1. Making the Payload singleton locale-aware internally
  2. Properly passing locale context through all operations
  3. Investigating why the locale parameter is being ignored in queries

However, clearPayloadCache() provides immediate relief for developers facing this issue and gives them control over cache lifecycle and can be used for other use cases than enforcing locale update

@mehdimahmoudi mehdimahmoudi changed the title feat(payload): add clearPayloadCache function for singleton cache man… feat: add clearPayloadCache function for singleton cache man… Oct 26, 2025
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant