fix: resolve 'Cannot access xs before initialization' runtime error

- Separate auth controls into lazy-loaded component to prevent module loading issues
- Add comprehensive error handling and fallbacks throughout auth system
- Make profile store resilient to initialization errors with safe localStorage access
- Add try-catch blocks around critical functions to prevent runtime crashes
- Provide fallback navigation methods when auth hooks fail
- Fix EKS deployment crash caused by JavaScript bundling/hoisting issues

This fixes the Header-DpwHgB0l.js runtime error that was preventing the app from loading in production.
This commit is contained in:
Nirmal Arya 2025-05-31 15:47:45 -04:00
parent 1687d812bf
commit 6a357e91a6
4 changed files with 355 additions and 111 deletions

View File

@ -4,8 +4,65 @@ import { chatStore } from '~/lib/stores/chat';
import { classNames } from '~/utils/classNames';
import { HeaderActionButtons } from './HeaderActionButtons.client';
import { ChatDescription } from '~/lib/persistence/ChatDescription.client';
import { useAuth } from '~/lib/hooks/useAuth';
import { Link } from '@remix-run/react';
import { Suspense, lazy, useState, useEffect } from 'react';
// Lazy load the AuthControls component to avoid initialization issues
const LazyAuthControls = lazy(() =>
import('./HeaderAuthControls.client').catch(() => ({
default: () => <FallbackAuthUI />
}))
);
// Simple fallback component when auth fails to load
function FallbackAuthUI() {
return (
<div className="flex items-center">
<button
className="flex items-center gap-2 px-3 py-1.5 rounded-md bg-bolt-elements-background-depth-2 text-bolt-content-secondary hover:bg-bolt-elements-background-depth-3 transition-colors text-sm font-medium"
onClick={() => {}}
>
<div className="i-ph:user-circle w-4 h-4" />
Sign in
</button>
</div>
);
}
// Error boundary for auth controls
function SafeAuthControls() {
const [hasError, setHasError] = useState(false);
useEffect(() => {
// Reset error state if component remounts
return () => setHasError(false);
}, []);
if (hasError) {
return <FallbackAuthUI />;
}
return (
<Suspense fallback={<div className="flex items-center h-8 px-2 text-bolt-elements-textTertiary">
<div className="i-svg-spinners:270-ring-with-bg w-5 h-5" />
</div>}>
<ErrorCatcher onError={() => setHasError(true)}>
<LazyAuthControls />
</ErrorCatcher>
</Suspense>
);
}
// Simple error boundary wrapper
function ErrorCatcher({ children, onError }) {
try {
return children;
} catch (error) {
console.error("Error rendering auth controls:", error);
onError();
return <FallbackAuthUI />;
}
}
export function Header() {
const chat = useStore(chatStore);
@ -33,9 +90,9 @@ export function Header() {
)}
<div className="flex items-center ml-auto">
{/* Authentication Controls - Always visible */}
<ClientOnly>
{() => <AuthControls />}
{/* Authentication Controls - Always visible with error handling */}
<ClientOnly fallback={<FallbackAuthUI />}>
{() => <SafeAuthControls />}
</ClientOnly>
{/* Existing Action Buttons - Only when chat has started */}
@ -52,68 +109,3 @@ export function Header() {
</header>
);
}
function AuthControls() {
const { isAuthenticated, isLoading, user, login, logout } = useAuth();
if (isLoading) {
return (
<div className="flex items-center h-8 px-2 text-bolt-elements-textTertiary">
<div className="i-svg-spinners:270-ring-with-bg w-5 h-5" />
</div>
);
}
if (isAuthenticated && user) {
return (
<div className="flex items-center">
<div className="relative group">
<button
className="flex items-center gap-2 px-2 py-1 rounded-md hover:bg-bolt-elements-item-backgroundActive transition-colors"
aria-label="User menu"
>
<img
src={user.avatar}
alt={`${user.username}'s avatar`}
className="w-8 h-8 rounded-full border border-bolt-elements-borderColor"
/>
<span className="text-sm font-medium text-bolt-elements-textPrimary hidden sm:block">
{user.username}
</span>
<div className="i-ph:caret-down w-4 h-4 text-bolt-elements-textTertiary" />
</button>
<div className="absolute right-0 mt-1 w-48 py-1 bg-bolt-elements-background-depth-2 rounded-md shadow-lg border border-bolt-elements-borderColor hidden group-hover:block z-50">
<div className="px-4 py-2 text-sm text-bolt-elements-textSecondary border-b border-bolt-elements-borderColor">
Signed in as <span className="font-semibold text-bolt-elements-textPrimary">{user.username}</span>
</div>
<Link
to="/profile"
className="block px-4 py-2 text-sm text-bolt-elements-textPrimary hover:bg-bolt-elements-item-backgroundActive"
>
Your profile
</Link>
<button
onClick={() => logout()}
className="block w-full text-left px-4 py-2 text-sm text-bolt-elements-textPrimary hover:bg-bolt-elements-item-backgroundActive"
>
Sign out
</button>
</div>
</div>
</div>
);
}
return (
<button
onClick={() => login()}
className="flex items-center gap-2 px-3 py-1.5 rounded-md bg-bolt-elements-item-backgroundAccent text-bolt-elements-item-contentAccent hover:bg-bolt-elements-item-backgroundAccentHover transition-colors text-sm font-medium"
>
<div className="i-ph:sign-in w-4 h-4" />
Sign in
</button>
);
}

View File

@ -0,0 +1,175 @@
import { Link } from '@remix-run/react';
import { useAuth } from '~/lib/hooks/useAuth';
import { useState, useEffect } from 'react';
/**
* Authentication controls component for the header
* Separated into a client component to prevent module loading issues
*/
export default function HeaderAuthControls() {
// Error state for component-level error handling
const [renderError, setRenderError] = useState(null);
// Call useAuth at the top level of the component as required by React hooks rules
let authState;
try {
// Properly use the hook at component top level
authState = useAuth();
} catch (error) {
console.error('Error initializing auth hook:', error);
// Return fallback UI immediately if hook initialization fails
return (
<div className="flex items-center">
<button
className="flex items-center gap-2 px-3 py-1.5 rounded-md bg-bolt-elements-background-depth-2 text-bolt-content-secondary hover:bg-bolt-elements-background-depth-3 transition-colors text-sm font-medium"
onClick={() => window.location.href = '/auth/login'}
>
<div className="i-ph:user-circle w-4 h-4" />
Sign in
</button>
</div>
);
}
// Safely extract values from auth state
const { isAuthenticated, isLoading, user, login, logout } = authState || {};
// Reset render error when dependencies change
useEffect(() => {
if (renderError) {
setRenderError(null);
}
}, [isAuthenticated, isLoading, user]);
// Safely handle login with fallback
const handleLogin = () => {
try {
if (typeof login === 'function') {
login();
} else {
// Fallback if login function is unavailable
window.location.href = '/auth/login';
}
} catch (error) {
console.error('Error during login:', error);
// Fallback to direct navigation on error
window.location.href = '/auth/login';
}
};
// Safely handle logout with fallback
const handleLogout = () => {
try {
if (typeof logout === 'function') {
logout();
} else {
// Fallback if logout function is unavailable
window.location.href = '/auth/logout';
}
} catch (error) {
console.error('Error during logout:', error);
// Fallback to direct navigation on error
window.location.href = '/auth/logout';
}
};
// Handle loading state
if (isLoading) {
return (
<div className="flex items-center h-8 px-2 text-bolt-elements-textTertiary">
<div className="i-svg-spinners:270-ring-with-bg w-5 h-5" />
</div>
);
}
// Handle render errors
if (renderError) {
return (
<div className="flex items-center">
<button
className="flex items-center gap-2 px-3 py-1.5 rounded-md bg-bolt-elements-background-depth-2 text-bolt-content-secondary hover:bg-bolt-elements-background-depth-3 transition-colors text-sm font-medium"
onClick={() => window.location.href = '/auth/login'}
>
<div className="i-ph:user-circle w-4 h-4" />
Sign in
</button>
</div>
);
}
try {
// Handle authenticated state
if (isAuthenticated && user) {
return (
<div className="flex items-center">
<div className="relative group">
<button
className="flex items-center gap-2 px-2 py-1 rounded-md hover:bg-bolt-elements-item-backgroundActive transition-colors"
aria-label="User menu"
>
<img
src={user.avatar}
alt={`${user.username}'s avatar`}
className="w-8 h-8 rounded-full border border-bolt-elements-borderColor"
onError={(e) => {
// Fallback for broken image links
e.currentTarget.src = 'data:image/svg+xml,%3Csvg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"%3E%3Ccircle cx="12" cy="12" r="10"/%3E%3Cpath d="M12 8v8"/%3E%3Cpath d="M8 12h8"/%3E%3C/svg%3E';
}}
/>
<span className="text-sm font-medium text-bolt-elements-textPrimary hidden sm:block">
{user.username || 'User'}
</span>
<div className="i-ph:caret-down w-4 h-4 text-bolt-elements-textTertiary" />
</button>
<div className="absolute right-0 mt-1 w-48 py-1 bg-bolt-elements-background-depth-2 rounded-md shadow-lg border border-bolt-elements-borderColor hidden group-hover:block z-50">
<div className="px-4 py-2 text-sm text-bolt-elements-textSecondary border-b border-bolt-elements-borderColor">
Signed in as <span className="font-semibold text-bolt-elements-textPrimary">{user.username || 'User'}</span>
</div>
<Link
to="/profile"
className="block px-4 py-2 text-sm text-bolt-elements-textPrimary hover:bg-bolt-elements-item-backgroundActive"
>
Your profile
</Link>
<button
onClick={handleLogout}
className="block w-full text-left px-4 py-2 text-sm text-bolt-elements-textPrimary hover:bg-bolt-elements-item-backgroundActive"
>
Sign out
</button>
</div>
</div>
</div>
);
}
// Handle unauthenticated state (default)
return (
<button
onClick={handleLogin}
className="flex items-center gap-2 px-3 py-1.5 rounded-md bg-bolt-elements-item-backgroundAccent text-bolt-elements-item-contentAccent hover:bg-bolt-elements-item-backgroundAccentHover transition-colors text-sm font-medium"
>
<div className="i-ph:sign-in w-4 h-4" />
Sign in
</button>
);
} catch (error) {
// Catch any rendering errors
console.error('Error rendering auth controls:', error);
setRenderError(error);
// Return fallback UI
return (
<button
onClick={() => window.location.href = '/auth/login'}
className="flex items-center gap-2 px-3 py-1.5 rounded-md bg-bolt-elements-background-depth-2 text-bolt-content-secondary hover:bg-bolt-elements-background-depth-3 transition-colors text-sm font-medium"
>
<div className="i-ph:user-circle w-4 h-4" />
Sign in
</button>
);
}
}

View File

@ -24,37 +24,70 @@ interface Profile {
};
}
// Default profile with all required fields
const defaultProfile: Profile = {
username: '',
bio: '',
avatar: '',
isAuthenticated: false,
};
// Safely get stored profile from localStorage with error handling
function getSafeStoredProfile(): Profile | null {
// Ensure we're in a browser environment
if (typeof window === 'undefined') {
return null;
}
try {
// Safely access localStorage
const storedProfile = localStorage.getItem('bolt_profile');
if (!storedProfile) {
return null;
}
// Safely parse JSON with error handling
const parsedProfile = JSON.parse(storedProfile);
return parsedProfile;
} catch (error) {
// Handle any errors (localStorage not available, JSON parse error, etc.)
console.error('Error accessing profile from localStorage:', error);
return null;
}
}
// Initialize with stored profile or defaults
const storedProfile = typeof window !== 'undefined' ? localStorage.getItem('bolt_profile') : null;
const storedProfile = getSafeStoredProfile();
const initialProfile: Profile = storedProfile
? {
// Ensure backward compatibility with existing profile data
...{
username: '',
bio: '',
avatar: '',
isAuthenticated: false,
},
...JSON.parse(storedProfile),
// Start with default values for all required fields
...defaultProfile,
// Then apply stored values, ensuring type safety
...(storedProfile as Partial<Profile>),
}
: {
username: '',
bio: '',
avatar: '',
isAuthenticated: false,
};
: defaultProfile;
// Create the store with safe initial values
export const profileStore = atom<Profile>(initialProfile);
/**
* Update profile with partial data
*/
export const updateProfile = (updates: Partial<Profile>) => {
profileStore.set({ ...profileStore.get(), ...updates });
try {
profileStore.set({ ...profileStore.get(), ...updates });
// Persist to localStorage
if (typeof window !== 'undefined') {
localStorage.setItem('bolt_profile', JSON.stringify(profileStore.get()));
// Safely persist to localStorage
if (typeof window !== 'undefined') {
try {
localStorage.setItem('bolt_profile', JSON.stringify(profileStore.get()));
} catch (error) {
console.error('Error saving profile to localStorage:', error);
}
}
} catch (error) {
console.error('Error updating profile:', error);
}
};
@ -69,45 +102,88 @@ export const setAuthenticatedUser = (githubUser: {
avatar_url: string;
html_url: string;
}) => {
const now = Date.now();
updateProfile({
username: githubUser.login,
avatar: githubUser.avatar_url,
// Keep existing bio if available
bio: profileStore.get().bio || githubUser.name || '',
isAuthenticated: true,
lastLogin: now,
github: githubUser,
});
try {
const now = Date.now();
const currentProfile = profileStore.get();
updateProfile({
username: githubUser.login,
avatar: githubUser.avatar_url,
// Keep existing bio if available
bio: currentProfile.bio || githubUser.name || '',
isAuthenticated: true,
lastLogin: now,
github: githubUser,
});
} catch (error) {
console.error('Error setting authenticated user:', error);
// Fallback to minimal profile update on error
updateProfile({
username: githubUser.login || 'User',
avatar: githubUser.avatar_url || '',
isAuthenticated: true,
});
}
};
/**
* Check if the user is authenticated
* With safe access pattern to prevent "Cannot access before initialization" errors
*/
export const isAuthenticated = (): boolean => {
return profileStore.get().isAuthenticated;
try {
const profile = profileStore.get();
return Boolean(profile && profile.isAuthenticated);
} catch (error) {
console.error('Error checking authentication state:', error);
return false;
}
};
/**
* Clear authentication state
*/
export const clearAuthState = () => {
const currentProfile = profileStore.get();
updateProfile({
// Keep username/bio/avatar if user wants to
// but clear authentication state
isAuthenticated: false,
github: undefined,
lastLogin: undefined,
});
try {
const currentProfile = profileStore.get();
updateProfile({
// Keep username/bio/avatar if user wants to
// but clear authentication state
isAuthenticated: false,
github: undefined,
lastLogin: undefined,
});
} catch (error) {
console.error('Error clearing auth state:', error);
// Fallback to resetting the entire profile on error
profileStore.set(defaultProfile);
}
};
/**
* Get GitHub user data if authenticated
* With safe access pattern to prevent "Cannot access before initialization" errors
*/
export const getGitHubUser = () => {
const profile = profileStore.get();
return profile.isAuthenticated ? profile.github : null;
try {
const profile = profileStore.get();
return (profile && profile.isAuthenticated && profile.github) ? profile.github : null;
} catch (error) {
console.error('Error getting GitHub user:', error);
return null;
}
};
/**
* Get safe profile data that won't throw if accessed before initialization
* Useful for components that need to access profile data safely
*/
export const getSafeProfile = (): Profile => {
try {
return profileStore.get();
} catch (error) {
console.error('Error getting profile:', error);
return defaultProfile;
}
};

View File

@ -35,7 +35,8 @@ export async function action({ request }: ActionFunctionArgs) {
// Get form data
const formData = await request.formData();
const bio = formData.get('bio')?.toString() || '';
const displayName = formData.get('displayName')?.toString() || user.login;
// Fix TypeScript error by adding null check for user
const displayName = formData.get('displayName')?.toString() || (user ? user.login : '');
const theme = formData.get('theme')?.toString() || 'system';
// Here you would typically update the user data in your database