Server Components

Jumbo React Next uses Next.js 16's React Server Components (RSC) architecture. Understanding where the client boundary sits — and why — is essential for adding new pages and components correctly.

The provider hierarchy

The root layout (src/app/[lang]/layout.tsx) is a server component that renders a tree of providers. Most of those providers are client components, so the actual client boundary sits at AppRouterCacheProvider, the outermost client wrapper:

RootLayout (server)
└── <html lang dir>
    └── AppRouterCacheProvider (client) ← Emotion SSR cache
        └── JumboConfigProvider (client)
            └── AppProvider (client) ← Customizer state
                └── JumboTheme (client) ← MUI ThemeProvider
                    └── JumboRTL (client) ← stylis RTL
                        └── JumboDialogProvider (client)
                            └── JumboDialog (client)
                                └── AppSnackbar (client) ← notistack
                                    └── AuthProvider (client) ← SessionProvider
                                        └── {children} ← page tree

Pages and leaf components can be server components by default. Only components that use React state, effects, browser APIs, or Next.js client hooks need 'use client'.

AppRouterCacheProvider and Emotion

MUI uses Emotion for CSS-in-JS. In the App Router, Emotion styles must be collected on the server and sent with the HTML response to avoid a flash of unstyled content. AppRouterCacheProvider from @mui/material-nextjs handles this:

tsx
// src/app/[lang]/layout.tsx
import { AppRouterCacheProvider } from '@mui/material-nextjs/v15-appRouter';
 
export default async function RootLayout({ children, params }) {
  return (
    <html>
      <body>
        <AppRouterCacheProvider>
          {/* All MUI + @jumbo client providers go here */}
          {children}
        </AppRouterCacheProvider>
      </body>
    </html>
  );
}

Writing server components

Any page.tsx or component that does not use client-only APIs is a server component by default. Server components can fetch data, access the session with auth(), and call getDictionary:

tsx
// src/app/[lang]/(common)/dashboards/intranet/page.tsx
import { getDictionary } from '@/app/[lang]/dictionaries';
import { getCurrentUser } from '@/lib/auth';
import { IntranetDashboard } from '@/components/IntranetDashboard';
 
interface Props {
  params: Promise<{ lang: string }>;
}
 
export default async function IntranetPage({ params }: Props) {
  const { lang } = await params;
  const [dict, user] = await Promise.all([
    getDictionary(lang),
    getCurrentUser(),
  ]);
 
  return <IntranetDashboard dict={dict} user={user} />;
}

Marking client components

Add 'use client' at the top of any file that uses React state, effects, or browser APIs:

tsx
// src/components/CustomizerButton/CustomizerButton.tsx
'use client';
 
import { useState } from 'react';
import { useAppContext } from '@/components/AppProvider/AppProvider';
 
export function CustomizerButton() {
  const { setCustomizerOpen } = useAppContext();
  return <button onClick={() => setCustomizerOpen(true)}>Customize</button>;
}

All @jumbo context hooks (useJumboLayoutSidebarOptions, useJumboTheme, etc.) are client-only, so any component that uses them must be a client component.

Static params generation

The root layout generates static params for the default locale at build time:

typescript
// src/app/[lang]/layout.tsx
export async function generateStaticParams() {
  return [{ lang: 'en-US' }];
}

Adding more locales here generates statically optimized HTML for each locale:

typescript
export async function generateStaticParams() {
  return [
    { lang: 'en-US' },
    { lang: 'ar-SA' },
    { lang: 'fr-FR' },
  ];
}

Passing data from server to client

When a page fetches data server-side and a child component needs it, pass it as props. The child component can be a client component:

tsx
// page.tsx (server)
export default async function Page({ params }) {
  const data = await fetchData();
  return <ClientWidget data={data} />;  // ClientWidget is 'use client'
}

Do not use useEffect + fetch when a server component can fetch the data directly. Server-side data loading is faster, has no loading state flash, and keeps sensitive logic off the client.

Loading states

Next.js supports a loading.tsx file next to any page.tsx. It renders immediately while the page server component is streaming:

tsx
// src/app/[lang]/(common)/loading.tsx
import { CircularProgress, Box } from '@mui/material';
 
export default function Loading() {
  return (
    <Box display="flex" justifyContent="center" alignItems="center" minHeight="60vh">
      <CircularProgress />
    </Box>
  );
}

Loading files exist for the common layout group and the mail/contact dynamic routes.