Skip to content

Commit 3d897b2

Browse files
committed
SSR: Header safety
1 parent cb00dd1 commit 3d897b2

1 file changed

Lines changed: 45 additions & 28 deletions

File tree

src/app/components/Header.tsx

Lines changed: 45 additions & 28 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
'use client';
22

33
import * as React from 'react';
4+
import dynamic from 'next/dynamic';
45
import {
56
AppBar,
67
Box,
@@ -30,8 +31,8 @@ import {
3031
gbfsMetricsNavItems,
3132
} from '../constants/Navigation';
3233
import type NavigationItem from '../interface/Navigation';
33-
import { usePathname, useRouter, useSearchParams } from 'next/navigation';
34-
import LogoutConfirmModal from './LogoutConfirmModal';
34+
import { usePathname, useRouter } from 'next/navigation';
35+
import Image from 'next/image';
3536
import { BikeScooterOutlined, OpenInNew } from '@mui/icons-material';
3637
import { useRemoteConfig } from '../context/RemoteConfigProvider';
3738
import { NestedMenuItem } from 'mui-nested-menu';
@@ -40,7 +41,6 @@ import DepartureBoardIcon from '@mui/icons-material/DepartureBoard';
4041
import { fontFamily } from '../Theme';
4142
import { defaultRemoteConfigValues } from '../interface/RemoteConfig';
4243
import { animatedButtonStyling } from './Header.style';
43-
import DrawerContent from './HeaderMobileDrawer';
4444
import ThemeToggle from './ThemeToggle';
4545
import { useTranslations, useLocale } from 'next-intl';
4646
import { useSelector } from 'react-redux';
@@ -49,16 +49,36 @@ import {
4949
selectUserEmail,
5050
} from '../store/profile-selectors';
5151

52+
// Lazy load components not needed for initial render
53+
const LogoutConfirmModal = dynamic(() => import('./LogoutConfirmModal'), {
54+
ssr: false,
55+
});
56+
const DrawerContent = dynamic(() => import('./HeaderMobileDrawer'), {
57+
ssr: false,
58+
});
59+
60+
// Hook to safely access search params only on client
61+
function useClientSearchParams() {
62+
const [searchParams, setSearchParams] = React.useState<URLSearchParams | null>(null);
63+
64+
React.useEffect(() => {
65+
if (typeof window !== 'undefined') {
66+
setSearchParams(new URLSearchParams(window.location.search));
67+
}
68+
}, []);
69+
70+
return searchParams;
71+
}
72+
5273
export default function DrawerAppBar(): React.ReactElement {
53-
const searchParams = useSearchParams();
74+
const clientSearchParams = useClientSearchParams();
5475
const hasTransitFeedsRedirectParam =
55-
searchParams.get('utm_source') === 'transitfeeds';
76+
clientSearchParams?.get('utm_source') === 'transitfeeds';
77+
5678
const theme = useTheme();
5779
const pathname = usePathname();
5880
const [mobileOpen, setMobileOpen] = React.useState(false);
59-
const [hasTransitFeedsRedirect, setHasTransitFeedsRedirect] = React.useState(
60-
hasTransitFeedsRedirectParam,
61-
);
81+
const [hasTransitFeedsRedirect, setHasTransitFeedsRedirect] = React.useState(false);
6282
const [openDialog, setOpenDialog] = React.useState(false);
6383
const [activeTab, setActiveTab] = React.useState('');
6484
const [navigationItems, setNavigationItems] = React.useState<
@@ -68,6 +88,12 @@ export default function DrawerAppBar(): React.ReactElement {
6888
const { config } = useRemoteConfig();
6989
const t = useTranslations('common');
7090

91+
React.useEffect(() => {
92+
if (hasTransitFeedsRedirectParam) {
93+
setHasTransitFeedsRedirect(true);
94+
}
95+
}, [hasTransitFeedsRedirectParam]);
96+
7197
React.useEffect(() => {
7298
setActiveTab(pathname ?? '');
7399
}, [pathname]);
@@ -99,8 +125,7 @@ export default function DrawerAppBar(): React.ReactElement {
99125
handleMenuClose();
100126
};
101127

102-
const container =
103-
window !== undefined ? () => window.document.body : undefined;
128+
const container = typeof window !== 'undefined' ? () => window.document.body : undefined;
104129

105130
const [anchorEl, setAnchorEl] = React.useState<null | HTMLElement>(null);
106131

@@ -159,22 +184,14 @@ export default function DrawerAppBar(): React.ReactElement {
159184
}}
160185
className='btn-link'
161186
>
162-
<picture style={{ display: 'flex' }}>
163-
<source
164-
media='(min-width: 50px)'
165-
srcSet='/assets/MOBILTYDATA_logo_purple_M.webp'
166-
width='50'
167-
height='50'
168-
/>
169-
<source
170-
src='/assets/MOBILTYDATA_logo_purple_M.png'
171-
type='image/png'
172-
/>
173-
<img
174-
alt='MobilityData logo'
175-
src='/assets/MOBILTYDATA_logo_purple_M.png'
176-
/>
177-
</picture>
187+
<Image
188+
src='/assets/MOBILTYDATA_logo_purple_M.webp'
189+
alt='MobilityData logo'
190+
width={50}
191+
height={50}
192+
priority
193+
fetchPriority='high'
194+
/>
178195
<Typography
179196
variant='h5'
180197
component='h1'
@@ -429,9 +446,9 @@ export default function DrawerAppBar(): React.ReactElement {
429446
severity='warning'
430447
onClose={() => {
431448
setHasTransitFeedsRedirect(false);
432-
if (hasTransitFeedsRedirectParam) {
449+
if (hasTransitFeedsRedirectParam && clientSearchParams) {
433450
// Remove utm_source from URL
434-
const newSearchParams = new URLSearchParams(searchParams);
451+
const newSearchParams = new URLSearchParams(clientSearchParams);
435452
newSearchParams.delete('utm_source');
436453
const newPath = `${pathname}?${newSearchParams.toString()}`;
437454
router.replace(newPath);

0 commit comments

Comments
 (0)