mui / material-ui

Material UI: Comprehensive React component library that implements Google's Material Design. Free forever.
https://mui.com/material-ui/
MIT License
93.9k stars 32.26k forks source link

Next.js 13 example with the `app` router #34898

Closed MartinXPN closed 1 year ago

MartinXPN commented 2 years ago

Duplicates

Latest version

Summary πŸ’‘

There is a great example of how to use MUI with next.js (https://github.com/mui/material-ui/blob/master/examples/nextjs-with-typescript) in this repository.

Yet, a new version of Next.js was released recently: https://nextjs.org/blog/next-13

This introduces several new concepts and ways to write React apps with server-side components in mind. To migrate from the old Next.js to the new version, one needs to do several things:

It would be great to get directions on how to use MUI with emotion styles and cache in this scenario. Would be even better to have a sample app like the one above.

Examples 🌈

No response

Motivation πŸ”¦

Next.js 13 is a major new version that introduces several exciting features that would be really great to use in our apps. It would be great to have a guide or directions on how to integrate MUI with the new version of Next.js instead of the old _document and _app based one.

I think there will be many developers that would want to update to the newer version and might get stuck (like myself) on the MUI integration and how the emotion styles and cache work on this new "server-first" environment without getInitialProps.

Clumsy-Coder commented 2 years ago

Also next/link no longer require <a> as a child of Link component

Check https://nextjs.org/blog/next-13#nextlink

Code example affected https://github.com/mui/material-ui/blob/012c95f917a5db74fd530c63260616d3c3803cd9/examples/nextjs-with-typescript/src/Link.tsx#L8-L37

Temporary workaround

add prop legacyBehavior to next/link component

MartinXPN commented 2 years ago

Another workaround for Link might be removing the Anchor and adding ref and ...other right to the NextLink. But this might break some edge case that I'm not familiar with.

JanderSilv commented 2 years ago

Probably for the full compatibility of Mui 5 with NextJs 13, Emotion must have support for React Server Components and Concurrent Rendering.

This already has been requested at Emotion's repo: https://github.com/emotion-js/emotion/issues/2928

MartinXPN commented 2 years ago

Is there any workaround that would enable us to use the app directory now without waiting for emotion and MUI library updates?

mnajdova commented 2 years ago

Duplicate of https://github.com/mui/material-ui/issues/34893

MartinXPN commented 2 years ago

@mnajdova I don't think this is a duplicate of #34893 Here we ask for a working example app that would work with Next.js 13 (similar to the previous Next.js version https://github.com/mui/material-ui/blob/master/examples/nextjs-with-typescript ), while the other issue reports a bug with hydration.

I think those are completely different requests/issues.

pomubry commented 2 years ago

Temporary workaround

add prop legacyBehavior to next/link component

I thought this commit would be a good read at least for the next/link issues. But there would still be minor ts error around

interface NextLinkComposedProps
  extends Omit<React.AnchorHTMLAttributes<HTMLAnchorElement>, "href">,
    Omit<NextLinkProps, "href" | "as" | "onClick" | "onMouseEnter"> {
  to: NextLinkProps["href"];
  linkAs?: NextLinkProps["as"];
}
dohomi commented 2 years ago

I think the NextJS examples needs to be split into two separate examples: pages and app folder based. For now only pages will work due to the underlying Emotion issue https://github.com/vercel/next.js/issues/41994

chris-askbrian commented 2 years ago

Wouldnt it make sense to pin the versions of the used libs at least to the major versions?

mnajdova commented 2 years ago

Wouldnt it make sense to pin the versions of the used libs at least to the major versions?

Locally in your projects yes. In our examples, we intentionally use latest, so that we can catch problems immediately when they happen

PetroSilenius commented 2 years ago

For next/link I just opened #34970 that sets up support for the legacyBehaviour prop to fix existing errors in the examples

I think one option to properly migrate to v13 would be to remove the need for NextLinkComposed and just show examples of doing <MuiLink component={NextLink}/>. This would require some changes to docs as well to support both the v12 and v13 routers

And as for @next/font I tried out the package in a few of my projects and I think I landed on a fairly good way to use it with MUI. Happy to hear other takes on it though!

gijsbotje commented 2 years ago

@PetroSilenius I just got this link from my colleague: https://mui.com/material-ui/guides/routing/#global-theme-link I'm mad I didn't find this earlier fix every use of an MUI Link or component based on ButtonBase. No need to add the NextLink as a component to every link and button, just override the default linkComponent for those components and your set.

PetroSilenius commented 2 years ago

Yea the global theme Link is a great tool! It might be a bit opionated for the nextjs examples though as there are some cases where NextLink doesn't behave as needed for example with external links with a certain target. Could point it up better in the docs thoughπŸ‘πŸ»

gijsbotje commented 2 years ago

@PetroSilenius I'm going to add a check if the href is relative, if so use NextLink otherwise use an a. moving that logic to the theme saves a lot of boilerplate.

akomm commented 1 year ago

Can workaround the createContext is undefined error because the stateful component is rendered on the server by wrapping it:

"use client" // <--- this

import {Button} from "@mui/material"

And then use this new file to import the component.

I don't like it but if maybe someone has no choice and has to move it NOW to next 13, then he could re-export it this way... There are sure way make it less of a pain to undo later.

AlbinoGeek commented 1 year ago

I work on the following boilerplate, and had to self-solve most of these issues.

https://github.com/Rethunk-Tech/nextjs-boilerplate

Is there any more progress on this issue I should be aware of?

Is it better to track #34905 instead?

mnajdova commented 1 year ago

Is it better to track https://github.com/mui/material-ui/issues/34905 instead?

Yep, that's better. We already have an next.js example using 13, but it doesn't cover the experimental app folder.

nmajor commented 1 year ago

A related discussion is happening over in the Emotion js repo: https://github.com/emotion-js/emotion/issues/2928

rtrembecky commented 1 year ago

Hi, I'm just setting up a new website using Next.js and MUI. I really wanted to try the app/ directory out but found out it doesn't work the hard way. I see #34905 and #34896, but I'd like to ask anyway - what's the progress on this? An example app would really come in handy, even if it contained workarounds that are planned to be removed in time. Also, on this note - is there somewhere a list of workarounds currently needed to make this work? Or the recommended way is not to use app/ directory for now?

re-thc commented 1 year ago

@rtrembecky it definitely works with some workarounds. See the emotion thread above for some hints.

iamcrisb commented 1 year ago

Hi, I'm just setting up a new website using Next.js and MUI. I really wanted to try the app/ directory out but found out it doesn't work the hard way.

I see #34905 and #34896, but I'd like to ask anyway - what's the progress on this? An example app would really come in handy, even if it contained workarounds that are planned to be removed in time. Also, on this note - is there somewhere a list of workarounds currently needed to make this work? Or the recommended way is not to use app/ directory for now?

Hi, did you find any resources for those workarounds?

danielcolgan commented 1 year ago

up

Hi, I'm just setting up a new website using Next.js and MUI. I really wanted to try the app/ directory out but found out it doesn't work the hard way. I see #34905 and #34896, but I'd like to ask anyway - what's the progress on this? An example app would really come in handy, even if it contained workarounds that are planned to be removed in time. Also, on this note - is there somewhere a list of workarounds currently needed to make this work? Or the recommended way is not to use app/ directory for now?

Hi, did you find any resources for those workarounds?

Hey, did you find any solutions?

rtrembecky commented 1 year ago

Hi, did you find any resources for those workarounds?

Hey, did you find any solutions?

@cristianbuta @danielcolgan hey guys πŸ˜„ yes, but I was lazy to report back here, though I expected such questions to appear πŸ˜„ it does indeed work and this mentioned emotion issue thread is really pretty insightful: https://github.com/emotion-js/emotion/issues/2928 - there are several examples, I picked the lowest-code one, find the usage below. (also here are some useful StackBlitzes from @karlhorky, though more related to the emotion compiler option: https://github.com/vercel/next.js/issues/41994#issuecomment-1445061359)

let me try to summarize my setup (using next 13.2.1, so with new metadata structure = no <head /> tag). note the "use client" directives - these are telling next not to treat these files as server components (BTW: server components are just a new optimization for the app directory, you are perfectly fine not using them). these directives will be necessary for all the files with components using MUI. my example uses the tss-react package just to use the NextAppDirEmotionCacheProvider utility they have πŸ™‚ but you can also copy its code.

// src/app/layout.tsx
import { Metadata } from "next/dist/lib/metadata/types/metadata-interface"

import { MuiSetup } from "./MuiSetup"

export const metadata: Metadata = {
  title: "My title",
  description: "My description",
}

export default function RootLayout({ children }: { children: React.ReactNode }) {
  return (
    <html lang="en">
      <body>
        <MuiSetup>{children}</MuiSetup>
      </body>
    </html>
  )
}
// src/app/theme.ts
import { createTheme } from "@mui/material"

export const theme = createTheme()
// src/app/MuiSetup.tsx
"use client"

import { CssBaseline, ThemeProvider } from "@mui/material"
import { ReactNode } from "react"
import { NextAppDirEmotionCacheProvider } from "tss-react/next/appDir"

import { theme } from "./theme"

type Props = {
  children: ReactNode
}

export const MuiSetup = ({ children }: Props) => {
  return (
    <>
      <CssBaseline />
      {/* MUI (but actually underlying Emotion) isn't ready to work with Next's experimental `app/` directory feature.
          I'm using the lowest-code approach suggested by this guy here: https://github.com/emotion-js/emotion/issues/2928#issuecomment-1386197925 */}
      <NextAppDirEmotionCacheProvider options={{ key: "css" }}>
        <ThemeProvider theme={theme}>{children}</ThemeProvider>
      </NextAppDirEmotionCacheProvider>
    </>
  )
}
// src/app/page.tsx
import { Home } from "./Home"

export default function Page() {
  return <Home />
}
// src/app/Home.tsx
"use client"

import { Stack, Typography } from "@mui/material"

export const Home = () => (
  <Stack sx={{ minHeight: "100vh", padding: "64px" }}>
    <Stack sx={{ flexGrow: 1, justifyContent: "center", alignItems: "center" }} component="main">
      <Typography variant="h5">main content</Typography>
    </Stack>
    <Stack direction="row" sx={{ justifyContent: "space-between" }} component="footer">
      <Typography>footer</Typography>
      <Typography>stuff</Typography>
    </Stack>
  </Stack>
)

these 5 files are all the files I have in the src/app/ folder (ok, maybe plus newly moved favicon.ico) and there's no change needed elsewhere.

Cielquan commented 1 year ago

I am using next-themes and Tailwind to add a dark mode option.

Based on the code provided by @rtrembecky I made this example:

// tailwind.config.js
/** @type {import('tailwindcss').Config} */
const config = {
  darkMode: "class",
  content: ["./src/app/**/*.tsx"],
  theme: {
    extend: {},
  },
  variants: {},
  plugins: [],
};

module.exports = config;
/* src/app/globals.css */
@tailwind components;
@tailwind utilities;
// src/app/layout.tsx
import "./globals.css";

import Providers from "./Providers";

export const metadata = {
  title: "My title",
  description: "My description",
};

const RootLayout = ({ children }: { children: React.ReactNode }) => (
  // suppressHydrationWarning is for next-themes - see: https://github.com/pacocoursey/next-themes#with-app
  <html lang="en" suppressHydrationWarning>
    <head />
    <body>
      <Providers>{children}</Providers>
    </body>
  </html>
);

export default RootLayout;
// src/app/theme.ts
export const DEFAULT_THEME: "dark" | "light" = "dark";

export const getOtherTheme = (theme: string | undefined): "dark" | "light" => {
  switch (theme) {
    case "dark":
      return "light";
    case "light":
      return "dark";
    case "system":
    default:
      return DEFAULT_THEME;
  }
};
// src/app/Providers.tsx
"use client";

import { createTheme as createMuiTheme, ThemeProvider as MuiThemeProvider } from "@mui/material";
import { ThemeProvider, useTheme } from "next-themes";
import { NextAppDirEmotionCacheProvider } from "tss-react/next/appDir";

import { DEFAULT_THEME } from "./theme";

const MuiProvider = ({ children }: { children: React.ReactNode }) => {
  const { theme: themeState } = useTheme();
  const themeName = themeState === "dark" || themeState === "light" ? themeState : DEFAULT_THEME;
  const theme = createMuiTheme({ palette: { mode: themeName } });

  return (
    // CssBaseline causes the theme switch to stop working
    <NextAppDirEmotionCacheProvider options={{ key: "css" }}>
      <MuiThemeProvider theme={theme}>{children}</MuiThemeProvider>
    </NextAppDirEmotionCacheProvider>
  );
};

const NextThemeProvider = ({ children }: { children: React.ReactNode }) => (
  // Separate next-themes Provider from MUI, so is does not get rerendered on theme switch
  <ThemeProvider attribute="class" defaultTheme={DEFAULT_THEME}>
    {children}
  </ThemeProvider>
);

const Providers = ({ children }: { children: React.ReactNode }) => (
  <NextThemeProvider>
    <MuiProvider>{children}</MuiProvider>
  </NextThemeProvider>
);

export default Providers;
// src/app/page.tsx
import MuiContent from "./MuiContent";
import ThemeButton from "./ThemeButton";

const Index = () => (
  <>
    <ThemeButton />
    <MuiContent />
  </>
);

export default Index;
// src/app/ThemeButton.tsx
"use client";

import { useTheme } from "next-themes";
import { useEffect, useState } from "react";

import { DEFAULT_THEME, getOtherTheme } from "./theme";

const ThemeButton = () => {
  const { theme: themeState, setTheme } = useTheme();
  const [themeName, setThemeName] = useState(DEFAULT_THEME);

  useEffect(() => setThemeName(getOtherTheme(themeState)), [themeState]);

  return (
    <button type="button" onClick={() => setTheme(getOtherTheme(themeState))}>
      {`Activate ${themeName} Theme`}
    </button>
  );
};

export default ThemeButton;
// src/app/MuiContent.tsx
"use client";

import { Stack, Typography } from "@mui/material";
import { useTheme } from "next-themes";
import { useEffect, useState } from "react";

import { DEFAULT_THEME, validThemeOrDefault } from "@/theme";

const MuiContent = () => {
  const { theme } = useTheme();
  const [themeName, setThemeName] = useState(DEFAULT_THEME);
  useEffect(() => setThemeName(validThemeOrDefault(theme)), [theme]);

  return (
    <Stack sx={{ minHeight: "100vh", padding: "64px" }}>
      <Stack sx={{ flexGrow: 1, justifyContent: "center", alignItems: "center" }} component="main">
        <Typography variant="h5">main content with Theme: {`${themeName}`}</Typography>
      </Stack>
      <Stack direction="row" sx={{ justifyContent: "space-between" }} component="footer">
        <Typography>footer</Typography>
        <Typography>stuff</Typography>
      </Stack>
    </Stack>
  );
};

export default MuiContent;
renanrms commented 1 year ago

I tested @rtrembecky's sample code, and it worked, but then I tried a different way of using MUI components in application pages. The layout.tsx, theme.ts and MuiSetup.tsx files remain as the originals, so I'll only show the different files.

This file exports all Material content but has a "use client" line, which forces all components to be client side.

// src/components/@mui/material/index.tsx
'use client';

export * from '@mui/material';

On the page we can import the components as in Material. It can still be a server component, and we also don't need to group the material components into a different component with the "use client" line.

// src/app/page.tsx
import { Alert, AlertTitle, Button, Container } from '../components/@mui/material';

export default function Home() {
  return (
    <Container>
      <Alert severity="error">
        <AlertTitle>Error</AlertTitle>
        This is an error alert with title β€” check it out!
      </Alert>
      <Alert severity="success">
        <AlertTitle>Success</AlertTitle>
        This is a success alert with title β€” check it out!
      </Alert>
      <Button variant="contained">
        Contained
      </Button>
    </Container>
  );
}

We can also create specific files to export each material component, which reduces page load time by about 0.4s in my tests. Each file looks like this:

// src/components/@mui/material/Alert.tsx
'use client';

import Alert from '@mui/material/Alert';
export default Alert;

And we can import the component like in material with:

import Alert from '@/components/@mui/material/Alert';

The code seems to work fine. I don't know if this decreases performance as I am creating server components which in some cases may have many client components inside it. If anyone has an opinion on this, I'd like to know.

dougiefresh49 commented 1 year ago

I tested @rtrembecky's sample code, and it worked, but then I tried a different way of using MUI components in application pages. The layout.tsx, theme.ts and MuiSetup.tsx files remain as the originals, so I'll only show the different files.

This file exports all Material content but has a "use client" line, which forces all components to be client side.

// src/components/@mui/material/index.tsx
'use client';

export * from '@mui/material';

On the page we can import the components as in Material. It can still be a server component, and we also don't need to group the material components into a different component with the "use client" line.

// src/app/page.tsx
import { Alert, AlertTitle, Button, Container } from '../components/@mui/material';

export default function Home() {
  return (
    <Container>
      <Alert severity="error">
        <AlertTitle>Error</AlertTitle>
        This is an error alert with title β€” check it out!
      </Alert>
      <Alert severity="success">
        <AlertTitle>Success</AlertTitle>
        This is a success alert with title β€” check it out!
      </Alert>
      <Button variant="contained">
        Contained
      </Button>
    </Container>
  );
}

We can also create specific files to export each material component, which reduces page load time by about 0.4s in my tests. Each file looks like this:

// src/components/@mui/material/Alert.tsx
'use client';

import Alert from '@mui/material/Alert';
export default Alert;

And we can import the component like in material with:

import Alert from '@/components/@mui/material/Alert';

The code seems to work fine. I don't know if this decreases performance as I am creating server components which in some cases may have many client components inside it. If anyone has an opinion on this, I'd like to know.

@renanrms this worked great! thanks for the suggestion.

I used the simple // src/components/@mui/material/index.tsx example you gave for now but will most likely move to the more performant solution you provided with making the individual files when the app gets larger.

thanks again πŸŽ‰ and thanks @rtrembecky for the original suggestion / workaround!

acomanescu commented 1 year ago

@Cielquan Thanks for the example. Did you encounter this error? For some reason when generating the theme based on the selected theme name (light/dark) it fails to match the classnames between re-renders.

Also, the CssBaseline is required, otherwise styles such as typography are broken. Did you manage to find a solution for that?

image

dougiefresh49 commented 1 year ago

@arobert93,

TL;DR

example repo I created link that fixes issues with CssBaseLine and removes need for next-themes based on a culmination of posts. Details and links below

edit the repo has been updated to include next-themes to prevent flashing on load.


  1. in @Cielquan example code, he has this line with a comment
// suppressHydrationWarning is for next-themes - see: https://github.com/pacocoursey/next-themes#with-app
<html lang="en" suppressHydrationWarning>

Looks like someone else had an idea around cookie usage over on your other post link

  1. Also, the CssBaseline is required, otherwise styles such as typography are broken

I experienced the same. I found this link to a codesandbox that used some experimental features but the CodeSB didnt work out of the box (outdated dependency issues).

I forked it and created a repo you can check out link. It is basic and just uses the system color scheme (no theme changing button etc) but i didnt need to include next-themes, it uses all of the default out of the box MUI theme switching logic.

I did not update the example to use @renanrms idea but the example repo should be enough for a jumping off point, will try to update it with all the bells and whistles at a later point

hope this helps πŸŽ‰

Cielquan commented 1 year ago

I just did a little testing and tried to get all my prior used providers to work, when I came up with my solution above. I did not notice any issues with the Typography component yet.

Unfortunately I will have no time to dig into the topic again more deeply for some weeks. But I guess @dougiefresh49 answer can solve the issue, which is nice. πŸŽ‰

Shivaansh-Agarwal commented 1 year ago

Hi Since NextJS 13.4 is now released, which claims the app router directory to be stable, when can we expect an official example code to use MUI with NextJS for server components?

Web3ThomasC commented 1 year ago

Good day everyone! I am also curious to see some examples on how to transition from pages to app directory. I am working on a project using a template from https://minimals.cc.

Currently, in the pages folder, my _app.tsx, _document.tsx, and index.tsx appears as follows:

// _app.tsx

// i18n
import '../locales/i18n';

// scroll bar
import 'simplebar-react/dist/simplebar.min.css';

// lightbox
import 'yet-another-react-lightbox/styles.css';
import 'yet-another-react-lightbox/plugins/captions.css';
import 'yet-another-react-lightbox/plugins/thumbnails.css';

// map
import 'mapbox-gl/dist/mapbox-gl.css';

// editor
import 'react-quill/dist/quill.snow.css';

// slick-carousel
import 'slick-carousel/slick/slick.css';
import 'slick-carousel/slick/slick-theme.css';

// lazy image
import 'react-lazy-load-image-component/src/effects/blur.css';

// ----------------------------------------------------------------------

import { CacheProvider, EmotionCache } from '@emotion/react';
// next
import { NextPage } from 'next';
import Head from 'next/head';
import { AppProps } from 'next/app';
// redux
import { Provider as ReduxProvider } from 'react-redux';
// @mui
import { LocalizationProvider } from '@mui/x-date-pickers';
import { AdapterDateFns } from '@mui/x-date-pickers/AdapterDateFns';
// redux
import { store } from '../redux/store';
// utils
import createEmotionCache from '../utils/createEmotionCache';
// theme
import ThemeProvider from '../theme';
// locales
import ThemeLocalization from '../locales';
// components
import { StyledChart } from '../components/chart';
import ProgressBar from '../components/progress-bar';
import SnackbarProvider from '../components/snackbar';
import { MotionLazyContainer } from '../components/animate';
import { ThemeSettings, SettingsProvider } from '../components/settings';

// Check our docs
// https://docs.minimals.cc/authentication/ts-version

import { AuthProvider } from '../auth/JwtContext';
// import { AuthProvider } from '../auth/Auth0Context';
// import { AuthProvider } from '../auth/FirebaseContext';
// import { AuthProvider } from '../auth/AwsCognitoContext';

// ----------------------------------------------------------------------

const clientSideEmotionCache = createEmotionCache();

type NextPageWithLayout = NextPage & {
  getLayout?: (page: React.ReactElement) => React.ReactNode;
};

interface MyAppProps extends AppProps {
  emotionCache?: EmotionCache;
  Component: NextPageWithLayout;
}

export default function MyApp(props: MyAppProps) {
  const { Component, pageProps, emotionCache = clientSideEmotionCache } = props;

  const getLayout = Component.getLayout ?? ((page) => page);

  return (
    <CacheProvider value={emotionCache}>
      <Head>
        <meta name="viewport" content="initial-scale=1, width=device-width" />
      </Head>

      <AuthProvider>
        <ReduxProvider store={store}>
          <LocalizationProvider dateAdapter={AdapterDateFns}>
            <SettingsProvider>
              <MotionLazyContainer>
                <ThemeProvider>
                  <ThemeSettings>
                    <ThemeLocalization>
                      <SnackbarProvider>
                        <StyledChart />
                        <ProgressBar />
                        {getLayout(<Component {...pageProps} />)}
                      </SnackbarProvider>
                    </ThemeLocalization>
                  </ThemeSettings>
                </ThemeProvider>
              </MotionLazyContainer>
            </SettingsProvider>
          </LocalizationProvider>
        </ReduxProvider>
      </AuthProvider>
    </CacheProvider>
  );
}
// _document.tsx

import * as React from 'react';
// next
import Document, { Html, Head, Main, NextScript } from 'next/document';
// @emotion
import createEmotionServer from '@emotion/server/create-instance';
// utils
import createEmotionCache from '../utils/createEmotionCache';
// theme
import palette from '../theme/palette';
import { primaryFont } from '../theme/typography';

// ----------------------------------------------------------------------

export default class MyDocument extends Document {
  render() {
    return (
      <Html lang="en" className={primaryFont.className}>
        <Head>
          <meta charSet="utf-8" />
          <link rel="manifest" href="/manifest.json" />

          {/* PWA primary color */}
          <meta name="theme-color" content={palette('light').primary.main} />

          {/* Favicon */}
          <link rel="apple-touch-icon" sizes="180x180" href="/favicon/apple-touch-icon.png" />
          <link rel="icon" type="image/png" sizes="32x32" href="/favicon/favicon-32x32.png" />
          <link rel="icon" type="image/png" sizes="16x16" href="/favicon/favicon-16x16.png" />

          {/* Emotion */}
          <meta name="emotion-insertion-point" content="" />
          {(this.props as any).emotionStyleTags}

          {/* Meta */}
          <meta
            name="description"
            content="Alert 360 Home Security Systems up to 25% off RETAIL! Save big on Alarm Monitoring starting at $15.95 & Top-Rated Security Cameras!"
          />
          <meta name="keywords" content="home,security,alarm,system,wireless,monitoring,cameras" />
          <meta name="author" content="Alert 360" />
        </Head>

        <body>
          <Main />
          <NextScript />
        </body>
      </Html>
    );
  }
}

// ----------------------------------------------------------------------

MyDocument.getInitialProps = async (ctx) => {
  const originalRenderPage = ctx.renderPage;

  const cache = createEmotionCache();

  const { extractCriticalToChunks } = createEmotionServer(cache);

  ctx.renderPage = () =>
    originalRenderPage({
      enhanceApp: (App: any) =>
        function EnhanceApp(props) {
          return <App emotionCache={cache} {...props} />;
        },
    });

  const initialProps = await Document.getInitialProps(ctx);

  const emotionStyles = extractCriticalToChunks(initialProps.html);

  const emotionStyleTags = emotionStyles.styles.map((style) => (
    <style
      data-emotion={`${style.key} ${style.ids.join(' ')}`}
      key={style.key}
      // eslint-disable-next-line react/no-danger
      dangerouslySetInnerHTML={{ __html: style.css }}
    />
  ));

  return {
    ...initialProps,
    emotionStyleTags,
  };
};
// index.tsx

// next
import Head from 'next/head';
// @mui
import { Box } from '@mui/material';
// layouts
import MainLayout from '../layouts/main';
// components
import ScrollProgress from '../components/scroll-progress';
// sections
import {
  HomeHero,
  HomeMinimal,
  HomeDarkMode,
  HomeLookingFor,
  HomeForDesigner,
  HomeColorPresets,
  HomePricingPlans,
  HomeAdvertisement,
  HomeCleanInterfaces,
  HomeHugePackElements,
} from '../sections/home';

// ----------------------------------------------------------------------

HomePage.getLayout = (page: React.ReactElement) => <MainLayout> {page} </MainLayout>;

// ----------------------------------------------------------------------

export default function HomePage() {
  return (
    <>
      <Head>
        <title> The starting point for your next project | Minimal UI</title>
      </Head>

      <ScrollProgress />

      <HomeHero />

      <Box
        sx={{
          overflow: 'hidden',
          position: 'relative',
          bgcolor: 'background.default',
        }}
      >
        <HomeMinimal />

        <HomeHugePackElements />

        <HomeForDesigner />

        <HomeDarkMode />

        <HomeColorPresets />

        <HomeCleanInterfaces />

        <HomePricingPlans />

        <HomeLookingFor />

        <HomeAdvertisement />
      </Box>
    </>
  );
}

What would the new layout.tsx, MuiSetup.tsx, theme.ts, and page.tsx (in the app folder) look like? Looking at the outstanding contributions by others in this thread, I have an idea and will give it a try today!! Thank you. :)

ZeeshanTamboli commented 1 year ago

Here is an example to look at using the Next.js's /app directory with tss-react (which uses emotion under the hood) until the PR is reviewed - https://github.com/mui/material-ui/pull/37315.

alaindeurveilher commented 1 year ago

Here is an example to look at using the Next.js's /app directory with tss-react (which uses emotion under the hood) until the PR is reviewed - #37315.

So basically, does the whole application become a client side application then with 'use client'; in every single component??!

caedes commented 1 year ago

@alaindeurveilher Really weird. Seems absurd that to use the new RSC version of NextJS, we have to make our whole app client. Either the MUI doc is not understood or it is the NextJS one ; MUI seems compatible with a server side render: https://mui.com/material-ui/guides/server-rendering/

I have to prepare some POC for the teams of my company, I'll give it a try this weekend.

AlbinoGeek commented 1 year ago

use client on every component would defeat any benefit we'd get from using apps :/

Guess we're not using Material-UI for new projects on NextJS 13 in the meantime.

caedes commented 1 year ago

@alaindeurveilher I have misunderstood; using "use client" doesn't mean that everything is rendering on client side. NextJS always pre-render all our components on server side. With "use client" it also sends the JS for the client for interactivity, lifecycle and hooks. It will remain a beautiful performing server app, and that's nice of it! πŸ‘Œ

@AlbinoGeek I thought the same thing at first. But in the "Good to know" part from the doc above, they say:

"use client" does not need to be defined in every file. The Client module boundary only needs to be defined once, at the "entry point", for all modules imported into it to be considered a Client Component.

So we could be good, I'll try it. πŸ‘€

Tigatok commented 1 year ago

I wanted to use MUI with Tailwind CSS. This is what I was able to do, though I am not sure if everything is implemented fine or not:

layout.tsx

import "./globals.css";
import Navigation from "./Navigation";
import ThemeWrapper from "./ThemeWrapper";

export default function RootLayout({
  children,
}: {
  children: React.ReactNode;
}) {
  let menuLinks: { title: string; url: string }[] = [
    { title: "Home", url: "#" },
    { title: "Contact", url: "#" },
  ];
  return (
    <html lang="en">
      <ThemeWrapper>
        <body>
          <Navigation menuLinks={menuLinks} />
          {children}
        </body>
      </ThemeWrapper>
    </html>
  );
}

globals.css

@tailwind base;
@tailwind components;
@tailwind utilities;

ThemeWrapper.tsx

"use client";

import {
  CssBaseline,
  StyledEngineProvider,
  ThemeProvider,
  createTheme,
} from "@mui/material";

export default function ThemeWrapper({ children }: { children: any }) {

  const theme = createTheme({});
  return (
    <StyledEngineProvider injectFirst>
      <CssBaseline />
      <ThemeProvider theme={theme}>{children}</ThemeProvider>
    </StyledEngineProvider>
  );
}

And then I'm seemingly able to use Tailwind css and mui components pretty easily. Here is an example component:

Hero.tsx

"use client"
...
export default function MainHero() {
  const theme = useTheme();
  return (
    <div className="relative isolate overflow-hidden bg-gray-900">
      <svg
        className="absolute inset-0 -z-10 h-full w-full stroke-white/10 [mask-image:radial-gradient(100%_100%_at_top_right,white,transparent)]"
        aria-hidden="true"
      >
// more stuff
    </div>
  );
}

tailwind.config.js

/** @type {import('tailwindcss').Config} */
module.exports = {
  corePlugins: {
    preflight: false,
  },
...
}
xn1cklas commented 1 year ago

@Tigatok It seems to me that this approach forces all children components in the body to be client-sided, or am I overlooking something?

AdamQuadmon commented 1 year ago

This PR with app router example looks close to merge https://github.com/mui/material-ui/pull/37315

mnajdova commented 1 year ago

This was fixed in https://github.com/mui/material-ui/pull/37315. Well done @smo043

Seanmclem commented 1 year ago

I feel like, forcing every MUI component to render client side with "use client" kind of defeats the purpose of an SSR-first framework like Next JS and Next 13. Since MUI is the UI framework, it will basically get used everywhere and make proper SSR useless or nearly impossible.

karlhorky commented 1 year ago

@Seanmclem I agree with what I interpret as the core of your argument (that MUI should make styling possible in server-only components aka React Server Components aka RSC), but it sounds like you're mixing things up - SSR is not the same as what Server Components provide. I think you may mean "Server Components-first", not "SSR-first" (since "use client" components are also server-side rendered)

Nikhilthadani commented 1 year ago

Hey guys, sorry I was not on track, is this issue still there with app router?

smo043 commented 1 year ago

Hey guys, sorry I was not on track, is this issue still there with app router?

same problem still with app router. core problem is that className or Initial UIs are different on server side and client side. I using tailwindcss, and only condition ui and tailwindcss className with some props.

I really want to quit app router....

This might be helpful if you are using with tailwind css - https://ui.shadcn.com