important: Who should use this guide: Only existing PWA Kit v2.x projects that want to add Hybrid Auth by upgrading to v2.10.0. PWA Kit v2.10.0 supports SLAS Public Clients only.
Starting a new Hybrid Auth implementation? Don't use this guide. New implementations must start on PWA Kit v3—see Configure a Hybrid Storefront with Hybrid Auth (PWA Kit) for the supported version and prerequisites.
Use this guide to upgrade your existing PWA Kit application to version 2.10.0 and implement Hybrid Auth. The PWA Kit 2.10.0 release introduces significant changes, such as compatibility with Hybrid Auth and integration with the @salesforce/commerce-sdk-react package. For additional details, check out the complete pwa-kit GitHub diff to get a better understanding of the code changes.
Prerequisites
Before starting the upgrade process, make sure that you have:
- Node.js version 18.x or 20.x (upgraded from 14.x/16.x support)
- NPM version 9.x or 10.x (upgraded from 6.x/7.x/8.x support)
- Enable Hybrid Auth on your Business Manager instance. For details, see Shared Hybrid Auth Setup. Then enable cookies on Managed Runtime, as described in Enable Cookies on Managed Runtime.
Authentication System Updates
The most significant update is the introduction of the new Commerce SDK React authentication system.
New Dependencies
{
"@salesforce/commerce-sdk-react": "^4.0.0",
"@tanstack/react-query": "4.28.0"
}
Step-by-Step Upgrade Process
Step 1: Prepare Your Environment
-
Check your current Node.js and npm versions.
node --version npm --versionIf your Node.js version is below 18.x, upgrade it first. If your npm version is below 9.x, upgrade it first.
Before the upgrade (PWA Kit v2.9.x):
{ "engines": { "node": "^14.0.0 || ^16.0.0 || ^18.0.0 || ^20.0.0", "npm": "^6.14.4 || ^7.0.0 || ^8.0.0 || ^9.0.0 || ^10.0.0" } }After the upgrade (PWA Kit v2.10.0):
{ "engines": { "node": "^18.0.0 || ^20.0.0", "npm": "^9.0.0 || ^10.0.0" } }
Step 2: Update Dependencies
-
Open your
package.jsonfile and update the following sections.Update the
"engines"section.{ "engines": { "node": "^18.0.0 || ^20.0.0", "npm": "^9.0.0 || ^10.0.0" } }Update the
"devDependencies"section with these new dependencies and version bumps.{ "devDependencies": { "@salesforce/commerce-sdk-react": "^3.4.0", "@tanstack/react-query": "4.28.0", "commerce-sdk-isomorphic": "^3.3.0", "jwt-decode": "^4.0.0", "pwa-kit-dev": "2.10.0", "pwa-kit-react-sdk": "2.10.0", "pwa-kit-runtime": "2.10.0", "@testing-library/react": "^12.1.5", "@testing-library/react-hooks": "^8.0.1", "@testing-library/user-event": "^14.4.3", "ajv": "^8.17.1", "ajv-keywords": "^5.1.0" } } -
Save the
package.jsonfile. -
Clean install the new dependencies.
rm -rf node_modules package-lock.json npm install
note: The @testing-library/* dependencies are optional and required only if you've implemented unit tests using react-testing-library.
Step 3: Update the Configuration
-
Open your
config/default.jsfile. -
Find the line that contains
ssrFunctionNodeVersionand update it.// Change this line: ssrFunctionNodeVersion: '18.x', // Or 20.x if you upgraded to Node 20. -
Save the
config/default.jsfile.
Step 4: Authentication Migration
Update your authentication implementation with the new Commerce SDK React system.
-
Remove old authentication files.
rm app/commerce-api/auth.js rm app/commerce-api/pkce.js -
Update the
utils.jsfile.# Open app/commerce-api/utils.js and remove authentication-related functionsRemove these imports from the top of the file.
// REMOVE these lines from app/commerce-api/utils.js import jwtDecode from "jwt-decode"; import { refreshTokenGuestStorageKey, refreshTokenRegisteredStorageKey } from "./constants";Remove these functions from
app/commerce-api/utils.js.// REMOVE these functions from app/commerce-api/utils.js export function isTokenExpired(token) { ... } export function createGetTokenBody(urlString, slasCallbackEndpoint, codeVerifier) { ... } export function hasSFRAAuthStateChanged(storage, storageCopy) { ... }Keep all other utility functions, including
keysToCamelandcreateOcapiFetch, as they're still needed. -
Update the
useCustomerhook. Open yourapp/hooks/use-customer.jsfile and update the authentication methods.// Add these new imports at the top of the file: import { AuthHelpers } from '@salesforce/commerce-sdk-react' import { useAuthHelper } from '@salesforce/commerce-sdk-react' // Find the login and logout methods in useCustomer hook and update them: // Old implementation (v2.9.x) const login = async (credentials) => { const auth = new Auth(); await auth.login(credentials); // ... rest of login logic }; const logout = async () => { const auth = new Auth(); await auth.logout(); // ... rest of logout logic }; // New implementation (v2.10.0) export default function useCustomer() { const api = useCommerceAPI() const {customer, setCustomer} = useContext(CustomerContext) const login = useAuthHelper(AuthHelpers.LoginRegisteredUserB2C) const logout = useAuthHelper(AuthHelpers.Logout) const getSkeletonCustomer = () => { return { customerId: api.auth.get('customer_id'), authType: api.auth.get('customer_type') } } const self = useMemo(() => { return { ... customer, // Other useCustomer hook functions ... // The login method is now handled by the useAuthHelper hook async login(credentials) { await api.auth.ready() let skeletonCustomer = getSkeletonCustomer() if (credentials) { await login.mutateAsync({ username: credentials.email, password: credentials.password }) skeletonCustomer = getSkeletonCustomer() } // ... rest of login logic }, async logout() { await logout.mutateAsync() await api.auth.ready() const skeletonCustomer = getSkeletonCustomer() setCustomer(skeletonCustomer) // ... rest of logout logic } } })
note: The useCustomer hook now uses the new Commerce SDK React authentication methods. Your existing login and registration components don't need any changes since they call the same login() and register() functions.
Step 5: Update the Commerce API Implementation
note: The Commerce API implementation has been significantly updated to use the new transformSDKClient utility from @salesforce/commerce-sdk-react package. See the README.md for the package to learn more.
-
Open your
app/commerce-api/index.jsfile. -
Update the imports at the top of the file.
// Remove these old imports: import Auth from "./auth"; import { isError } from "./utils"; // Add these new imports: import Auth from "@salesforce/commerce-sdk-react/auth"; import { transformSDKClient } from "@salesforce/commerce-sdk-react/utils"; import { DWSID_HEADER_KEY } from "./constants"; -
Update the auth module instantiation.
// Old implementation (v2.9.x) this._config = { proxy, ...restConfig }; this.auth = new Auth(this); // Update this with new authConfigReplace with this new code:
// Old implementation (v2.9.x) this._config = { proxy, ...restConfig }; // Add new authConfig object this._authConfig = { redirectURI: `${getAppOrigin()}/callback`, proxy, locale: this._config.locale, currency: this._config.currency, ...this._config.parameters, ...this._config.headers, }; this.auth = new Auth(this._authConfig); // pass authConfig as param to Auth class constructor. -
Find the section where SDK clients are instantiated (around line 100-150) and replace the entire SDK instantiation logic. Remove this old code:
// Old implementation (v2.9.x) - using Proxy const SdkClass = apiConfigs[key].api; self._sdkInstances = { ...self._sdkInstances, [key]: new Proxy(new SdkClass(this._config), { get: function (obj, prop) { // ... proxy implementation }, }), };Replace with this new code:
// New implementation (v2.10.0) - using transformSDKClient const SdkClass = apiConfigs[key].api; const sdkClient = new SdkClass(this._config); self._sdkInstances = { ...self._sdkInstances, [key]: transformSDKClient(sdkClient, { props: this._config, transformer: async (_, methodName, options) => { const { fetchOptions = {} } = options; if (fetchOptions.ignoreHooks) { return options; } const { locale, currency } = this._config; // Inject the locale and currency to the API call via its parameters. const { sendLocale = true, sendCurrency = false } = apiConfigs[key]; const includeGlobalLocale = Array.isArray(sendLocale) ? sendLocale.includes(methodName) : !!sendLocale; const includeGlobalCurrency = Array.isArray(sendCurrency) ? sendCurrency.includes(methodName) : !!sendCurrency; fetchOptions["parameters"] = { ...(includeGlobalLocale ? { locale } : {}), ...(includeGlobalCurrency ? { currency } : {}), ...fetchOptions?.parameters, }; // Handle auth logic (replacing willSendRequest functionality) let dwsidHeader = {}; const dwsid = self.auth.get("dwsid"); if (dwsid) { dwsidHeader = { [DWSID_HEADER_KEY]: dwsid, }; } // Special handling for auth methods if ( methodName === "authenticateCustomer" || methodName === "authorizeCustomer" || methodName === "getAccessToken" ) { return { ...options.parameters, headers: { ...options.headers, ...fetchOptions.headers, }, credentials: "same-origin", // Required for SLAS calls to set dwsid cookie ...fetchOptions, }; } const { access_token: token } = await self.auth.ready(); return { ...options, headers: { ...options.headers, ...dwsidHeader, Authorization: `Bearer ${token}`, }, // Add cache breaker for Storefront Preview parameters: { ...options.parameters, ...(this.isStorefrontPreview ? { c_cache_breaker: Date.now() } : {}), }, }; }, }), }; -
Find and remove these methods from the CommerceAPI class:
// Remove these methods completely: async willSendRequest(methodName, ...params) { // ... entire method } didReceiveResponse(response, args) { // ... entire method } -
Update the constants file. Add a new constant in
app/commerce-api/constants.js.export const DWSID_HEADER_KEY = "sfdc_dwsid";
Step 6: Update Commerce API Contexts
note: The Commerce API contexts have been updated to integrate with Commerce SDK React and React Query.
-
Open your
app/commerce-api/contexts.jsfile. -
Add these new imports at the top.
import { CommerceApiProvider as CommerceSDKReactProvider } from "@salesforce/commerce-sdk-react"; import { QueryClient, QueryClientProvider } from "@tanstack/react-query"; import { getAppOrigin } from "pwa-kit-react-sdk/utils/url"; import { isServer } from "../../pwa-kit-managed-runtime/utils/utils"; -
Add the
QueryClientconfiguration after the imports.const queryClientOptions = { queryClientConfig: { defaultOptions: { queries: { retry: false, refetchOnWindowFocus: false, staleTime: 10 * 1000, ...(isServer ? { retryOnMount: false } : {}), }, mutations: { retry: false, }, }, }, beforeHydrate: (data) => { const now = Date.now(); const updateQueryTimeStamp = ({ state }) => { state.dataUpdatedAt = now; }; data?.mutations?.forEach(updateQueryTimeStamp); data?.queries?.forEach(updateQueryTimeStamp); return data; }, }; const queryClient = new QueryClient(queryClientOptions); -
Replace the existing
CommerceAPIProviderwith this new implementation.export const CommerceAPIProvider = ({ value, children }) => { const { api, site, locale } = value; const apiClients = api._sdkInstances; const { shortCode, clientId, organizationId } = api.getConfig().parameters; const { proxy } = api.getConfig(); return ( <CommerceAPIContext.Provider value={api}> <QueryClientProvider client={queryClient}> <CommerceSDKReactProvider shortCode={shortCode} clientId={clientId} organizationId={organizationId} siteId={site?.id} locale={locale?.id} currency={locale?.preferredCurrency} redirectURI={`${getAppOrigin()}/callback`} proxy={proxy} apiClients={apiClients} disableAuthInit={true} > {children} </CommerceSDKReactProvider> </QueryClientProvider> </CommerceAPIContext.Provider> ); }; -
Save the
app/commerce-api/contexts.jsfile.
Step 7: Update the App Configuration Provider
important: Critical Change: The CommerceAPIProvider now requires different props structure.
-
Open your
app/components/_app-config/index.jsxfile. -
Find the
CommerceAPIProvidercomponent and update it.Replace this code:
// Old implementation (v2.9.x) <CommerceAPIProvider value={locals.api}>With this code:
// New implementation (v2.10.0) <CommerceAPIProvider value={locals}> -
Save the
app/components/_app-config/index.jsxfile.
Step 8: Cleanup Auth Initialization
-
Open your
app/components/_app/index.jsxfile. -
Remove the login call.
// Auth initialization is now handled by calling await self.auth.ready() in commerce-api/index.js // Remove this line. await api.auth.login();
Step 9: Test Your Upgrade
-
Start the development server.
npm start -
Run the build to check for errors.
npm run build
(Optional) Use Commerce SDK React Query Hooks
With PWA Kit v2.10.0, you now have access to powerful query hooks from @salesforce/commerce-sdk-react that provide automatic caching, loading states, and error handling for both hybrid and non-hybrid PWA Kit v2.x storefronts. Here's how to use them in your pages.
Example: Simple Order List Component
Create a minimal order list component using the new query hooks.
import React from "react";
import { useCustomerOrders } from "@salesforce/commerce-sdk-react";
import useCustomer from "../../commerce-api/hooks/useCustomer";
const SimpleOrderList = () => {
const customer = useCustomer();
const { data: { data: orders } = {}, isLoading } = useCustomerOrders(
{ parameters: { customerId: customer?.customerId } },
{ enabled: !!customer?.customerId },
);
if (isLoading) return <div>Loading orders...</div>;
return (
<div>
<h2>My Orders</h2>
{orders?.map((order) => (
<div key={order.orderNo}>
<p>
Order #{order.orderNo} - {order.status}
</p>
<p>Total: ${order.orderTotal}</p>
</div>
))}
</div>
);
};
export default SimpleOrderList;
Key Benefits
Check out the key benefits of the new query hooks.
- Automatic caching—Data is cached and shared.
- Loading states—Built-in loading management.
- Error handling—Automatic error states.
- Optimized fetching—Only fetches when customer is available.
- Simplified code—Less manual state management.