I have a dynamic page like this in nextjs:
/* Imports */
async function getData(id: string) {
const res = await fetch(`${process.env.NEXT_PUBLIC_API_URL}/homework/${id}`, {
cache: 'no-cache',
});
if (!res.ok) {
throw new Error('Failed to fetch data');
}
return (await res.json()) as Homework;
}
export default async function HomeworkPage({
params,
}: {
params: { name: string };
}) {
const data = await getData(params.name);
return (
<>
/* Some client and server components */
</>
);
}
This page is rendered using this layout:
'use client';
import { AnnouncementBar } from '@/components/editor/components/announcement-bar';
import { Footer } from '@/components/footer';
import { HeaderApp } from '@/components/header-app';
import { QuickBar } from '@/components/ui/quick-bar';
interface AppLayoutProps {
children: React.ReactNode;
}
export default function AppLayout({ children }: AppLayoutProps) {
return (
<div className='flex min-h-screen flex-col'>
<AnnouncementBar />
<HeaderApp />
<main className='flex flex-1 pt-[calc(var(--announcement-bar-height)+var(--header-height))]'>
<div className='container px-3 py-6 md:px-6'>{children}</div>
<QuickBar />
</main>
<Footer />
</div>
);
}
Where for example in AnnounementBar
I have the following logic that uses both lcoalStorage
in getUserOrGuest
method and useMediaQuery
in useIsDesktop
:
"use client"
export const AnnouncementBar = () => {
const user = getUserOrGuest();
const isDesktop = useIsDesktop();
return (
<div
className={clsx(
'z-[50] h-[40px] w-full border-y-[1px] border-border/90 scrolled:h-0',
'flex items-center justify-center text-sm transition-[opacity,height] duration-200',
'overflow-hidden scrolled:border-none scrolled:leading-[0] scrolled:opacity-0',
'fixed left-0 top-0 bg-header p-2'
)}
>
<div className='flex items-center gap-1.5'>
<AlertTriangle className='mr-2' />
{isDesktop && <span>Platforma jest w wersji</span>}
<ResponsiveDialog
trigger={
<Button
variant='ghost'
className='hover:bg-initial m-0 flex gap-1 p-0 text-violet-11 hover:text-violet-12 hover:underline'
>
<span className='font-bold'>
{isDesktop ? 'testowej!' : 'wersja testowa!'}
</span>
</Button>
}
title='Wersja testowa'
content={
<div className='flex flex-col gap-3'>
<p>
Ponieważ jesteśmy nowo powstałą platformą, potrzebujemy czasu
aby przetestować wszystkie funkcjonalności oraz uzupełnić
platformę o wartościowe treści. Do tego czasu{' '}
<span className='font-bold text-red-10 underline underline-offset-4'>
rejestracja kont ucznia jest wyłączona!
</span>
</p>
<p className='font-bold'>
Jesteś nauczycielem?{' '}
<Link className='mw-link' href='/register/teacher'>
Dołącz do nas!
</Link>
</p>
</div>
}
/>
</div>
{user && (
<Link
className='ml-5 whitespace-nowrap font-bold text-red-10 hover:text-red-11'
href='/feedback'
>
Zgłoś błąd lub sugestię
</Link>
)}
</div>
);
};
The problem is that for some reason this component is server side rendered and then I have a hydration error because when I reload page, initially the mobile view is rendered and user
is null and then it is replaced by the proper view (user
is taken from localStorage and isDesktop
is false). I don’t really get why is that a case. Any ideas?