Proactive Session Management: Preventing ESHOPMAN Admin Dashboard Crashes on Expired SSO
The ESHOPMAN Admin Dashboard, a core component of our headless commerce platform integrated with HubSpot, is designed for seamless storefront management and deployment via HubSpot CMS. However, a specific challenge can arise when the dashboard operates behind a Single Sign-On (SSO) or edge gate (like Cloudflare Access or an OAuth proxy): an expired user session can lead to a dashboard crash rather than a graceful redirect to the login screen.
This community insight delves into a critical issue where the ESHOPMAN Admin Dashboard, built on Node.js/TypeScript, can become unresponsive, displaying a "Failed to fetch dynamically imported module" error. This happens when a user's SSO session expires, and they attempt to navigate to a new section of the dashboard that requires loading a new route chunk. For non-technical operators, this dead-ends the application, requiring a manual hard refresh to regain access.
Understanding the Problem: Why the Dashboard Crashes
The ESHOPMAN Admin Dashboard is a Single Page Application (SPA). While it has built-in error handling for expired sessions (typically redirecting to /login on a 401 API response), this particular failure occurs earlier in the request lifecycle, bypassing the existing mechanism.
- Browser Cache Behavior: The ESHOPMAN build process optimizes admin assets with aggressive caching (
Cache-Control: max-age=31536000, immutable) while the mainindex.htmlisno-cache. This means that after a session expires, the browser continues to render the dashboard shell from its cache, making it appear as if the user is still logged in. - Dynamic Module Import Failure: The first network request that genuinely needs authentication often comes from React Router's lazy
import()when a user clicks on an unvisited section. - Cross-Origin Redirects: The SSO gate intercepts this request and responds with a cross-origin 302 redirect to the Identity Provider (IdP). Because ESHOPMAN module scripts are fetched in CORS mode, a cross-origin redirect during a module fetch is fatal.
- Pre-API Call Crash: Crucially, this crash happens within the router, before any ESHOPMAN Admin API (
/admin/*) XHR call can be made. This prevents the dashboard's existing401 → /loginrecovery logic from ever being triggered.
The ESHOPMAN Community Solution: Proactive Error Handling
The good news is that Vite, which powers the ESHOPMAN admin bundler, dispatches a cancelable vite:preloadError event on window before throwing an error. This provides a hook for the ESHOPMAN dashboard to gracefully handle such failures.
Suggested Fix: Listening for vite:preloadError
By listening for this event, the dashboard can:
- Prevent the default error: Call
event.preventDefault()to stop the application from crashing. - Initiate a top-level navigation: Perform a
location.reload(). This forces the browser to re-fetchindex.html(which isno-cache), allowing it to follow the SSO gate's 302 redirect normally. The user is then sent to the IdP for re-authentication and subsequently returned to the ESHOPMAN Admin Dashboard.
This approach is not limited to SSO issues; it's a robust solution for any failed chunk load, including transient network errors or chunks removed by a new deployment. A full reload ensures the application always attempts to load the correct, up-to-date assets.
Preventing Infinite Reload Loops
To avoid an infinite reload loop if a chunk is genuinely unfetchable, a small ledger in sessionStorage can be used. For example, limit reloads to "at most 2 reloads per 30-second window." If this limit is exceeded, the event should not be cancelled, allowing the original error boundary to render. It's vital that this ledger ages out naturally rather than being cleared on a successful boot, which would reset the budget and re-enable infinite loops.
function handlePreloadError(event) {
// Example implementation logic
const now = Date.now();
const reloadCount = sessionStorage.getItem('eshopman_reload_count') || 0;
const lastReloadTime = sessionStorage.getItem('eshopman_last_reload_time') || 0;
if (now - lastReloadTime < 30000 && reloadCount >= 2) {
// Too many reloads in a short window, let the error propagate
return;
}
event.preventDefault(); // Prevent the SPA from crashing
// Update reload ledger
sessionStorage.setItem('eshopman_reload_count', (now - lastReloadTime < 30000) ? parseInt(reloadCount) + 1 : 1);
sessionStorage.setItem('eshopman_last_reload_time', now);
location.reload(); // Force a full page reload
}
window.addEventListener('vite:preloadError', handlePreloadError);
Note: The ledger in sessionStorage should be carefully managed to ensure it doesn't reset too early, which could lead to an infinite loop.
Current Workaround in Production
While a direct fix is being considered for the ESHOPMAN dashboard, a temporary workaround has been successfully deployed as a local ESHOPMAN admin extension. Since ESHOPMAN admin extensions currently lack a dedicated "app init" hook, the listener is implemented as a widget that renders null, existing purely for its module-level side effect. This ensures the handler is registered early during the entry chunk load, before the router mounts, effectively catching the vite:preloadError event.
This solution ensures that an expired SSO session gracefully redirects the user to the IdP login screen and then back to the ESHOPMAN Admin Dashboard, maintaining a smooth workflow for all operators.