DEV Community

Cover image for Next.Js + MUI v5 tutorial
Hosein Pouyanmehr
Hosein Pouyanmehr

Posted on • Updated on

Next.Js + MUI v5 tutorial

If you are looking for a starter check Quick Start
If you also need to use TypeScript check this tutorial

Table of contents

What is this post about?

This post will help you to start a project using Next.js and MUI (formerly Material-UI). As you may notice using Next.js together with MUI may be a little bit tricky. I will explain each step to you to make it much more clear.

Why Next.js?

Well, You can find lots of articles and videos talking about Next.js benefits and I'm not really going to explain everything about Next.js features in this post, but shortly these are some of its amazing features:

  • It just load the JavaScript and CSS which page really need. This will make the page loading a lot faster.

  • All images can be optimized with the help of Next.js Image component.

  • Fast refresh

to discover more about Next.js click here.

Why MUI?

as you can read on MUI site:

MUI provides a robust, customizable, and accessible library of foundational and advanced components, enabling you to build your own design system and develop React applications faster.

MUI is well documented and for every single component you can find out how to use or how to customize it.

the latest version of MUI is faster and also has got a smaller bundle size.

you can click here to find more about MUI latest version.

Step one: Installing Next.js

  1. Run npx create-next-app@latest project-name or yarn create next-app project-name and replace project-name with your own.

Tip: If you already have a project folder created and wanna install Next.js in that directory simply add period instead of project-name. So, it'll be npx create-next-app@latest . or yarn create next-app .

I am going to name this starter "muxt" which is combination of MUI and Next. Also I am using NPM as package manager.

so for me it'll be npx create-next-app muxt.

  1. After that the installation is done go to the project folder by cd project-name and open it in your favorite code editor which for me is VS Code.

the initial folders and files structure should be like this image:

step-01-installing-nextjs-folder-struction

  1. To make sure that everything works perfectly, run npm run dev or yarn dev and head over to localhost:3000 in your browser. You should see a page like this.

step-01-nextjs-welcome-page

Step two: Installing emotion

as the MUI docs says:

The default style library used for generating CSS styles for MUI components is emotion.

in order to use MUI with Next.js we have to install these packages:

  • @emotion/cache
  • @emotion/react
  • @emotion/server
  • @emotion/styled

So, Run npm i @emotion/cache @emotion/react @emotion/server @emotion/styled or yarn add @emotion/cache @emotion/react @emotion/server @emotion/styled

Step three: Installing MUI

  1. install MUI with this command npm i @mui/material or in case you use yarn run yarn add @mui/material

  2. MUI uses Roboto as the default font so you should install that with this command: npm i @fontsource/roboto or yarn add @fontsource/roboto

  3. (OPTIONAL) If you think you are going to use MUI Icon components you need to install its package as well, otherwise there is no need to install this package. But I'm going to install it in order to have that in my starter. To do that run npm i @mui/icons-material or yarn add @mui/icons-material

Alright, we have installed everything that we need to. Let's take a look at all packages that we installed.

step-02-package.json

Step four: Creating a MUI theme

After installations, first we need to create a theme. With the help of MUI theme files you can create custom styles or different styles for light or dark mode. Here we are just going to create a theme file with only one option.

  1. create a folder named 'theme' in styles folder
    step-03-creating-theme-folder

  2. create a file with the name of 'lightTheme.js' in the theme folder. The idea behind naming this file lightTheme instead of theme is that we can come back later and add another file with the name of darkTheme which contains our dark mode theme options. in that file add these lines:

import { createTheme } from '@mui/material/styles';

const lightTheme = createTheme({
  palette: {
    mode: 'light',
  },
});

export default lightTheme;
Enter fullscreen mode Exit fullscreen mode

Tip: If you wanna start your app with dark mode you can name the file darkTheme, then set the palette mode to dark and follow next steps with that file.

You may think why we're going to create this file while it has just one option? well,later we need to pass a theme to ThemeProvider component of MUI and the reason that I am put everything in a separate file is that I want to structure files and folders properly from the beginning.

This is how the new folder gonna look like after following steps above:

step-04-creating-lightTheme-file

Step five: Creating an emotion cache

You need to create a cache for your styles because you are using Next.js and this will help the app to figure out what style to apply. For now let's just create a folder name utility and create a file name createEmotionCache.js in that folder.

Then add the following code:

import createCache from '@emotion/cache';

const createEmotionCache = () => {
  return createCache({ key: 'css', prepend: true });
};

export default createEmotionCache;
Enter fullscreen mode Exit fullscreen mode

Setting the prepend key to true moves MUI styles to the top of the

so they're loaded first.

This is what MUI says about setting prepend to true:

It allows developers to easily override MUI styles with other styling solutions, like CSS modules.

Here's the result in editor:

step-05-creating-emotion-cache-function

Step six: Edit _app.js

So, until now we've created a lightTheme for our app and we also create a function which create a cache for us, This step and the next one is about using them.

  1. First open _app.js file in the pages directory.
  2. Replace the code with the following one:
import React from 'react';
import { CacheProvider } from '@emotion/react';
import { ThemeProvider, CssBaseline } from '@mui/material';

import createEmotionCache from '../utility/createEmotionCache';
import lightTheme from '../styles/theme/lightTheme';
import '../styles/globals.css';

const clientSideEmotionCache = createEmotionCache();

const MyApp = (props) => {
  const { Component, emotionCache = clientSideEmotionCache, pageProps } = props;

  return (
    <CacheProvider value={emotionCache}>
      <ThemeProvider theme={lightTheme}>
        <CssBaseline />
        <Component {...pageProps} />
      </ThemeProvider>
    </CacheProvider>
  );
};

export default MyApp;
Enter fullscreen mode Exit fullscreen mode

Explanation of the code above:

  • First we import React
  • Next we import CacheProvider component from '@emotion/react', we use this component to provide a shared client-side cache for a user session.
  • We also import ThemeProvider and CssBaseline from '@mui/material'; using themeProvider let us to pass our theme throw the app and CssBaseline as mui says:

CssBaseline kickstart an elegant, consistent, and simple baseline to build upon.

  • Below the import statements we create a constant which contains a emotion cache and we use it as default value for emotionCache prop.

Step seven: Creating a custom document file in Next.js

In the pages folder add _document.js file. for now just add these lines of code to the file. I'll explain theme in a second.

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

export default class MyDocument extends Document {
  render() {
    return (
      <Html lang="en">
        <Head>
          <link
            rel="stylesheet"
            href="https://fonts.googleapis.com/css?family=Roboto:300,400,500,700&display=swap"
          />
        </Head>
        <body>
          <Main />
          <NextScript />
        </body>
      </Html>
    );
  }
}

// `getInitialProps` belongs to `_document` (instead of `_app`),
// it's compatible with static-site generation (SSG).
MyDocument.getInitialProps = async (ctx) => {
  // Resolution order
  //
  // On the server:
  // 1. app.getInitialProps
  // 2. page.getInitialProps
  // 3. document.getInitialProps
  // 4. app.render
  // 5. page.render
  // 6. document.render
  //
  // On the server with error:
  // 1. document.getInitialProps
  // 2. app.render
  // 3. page.render
  // 4. document.render
  //
  // On the client
  // 1. app.getInitialProps
  // 2. page.getInitialProps
  // 3. app.render
  // 4. page.render

  const originalRenderPage = ctx.renderPage;

  // You can consider sharing the same emotion cache between all the SSR requests to speed up performance.
  // However, be aware that it can have global side effects.
  const cache = createEmotionCache();
  const { extractCriticalToChunks } = createEmotionServer(cache);

  /* eslint-disable */
  ctx.renderPage = () =>
    originalRenderPage({
      enhanceApp: (App) =>
        function EnhanceApp(props) {
          return <App emotionCache={cache} {...props} />;
        },
    });
  /* eslint-enable */

  const initialProps = await Document.getInitialProps(ctx);
  // This is important. It prevents emotion to render invalid HTML.
  // See https://github.com/mui-org/material-ui/issues/26561#issuecomment-855286153
  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,
    // Styles fragment is rendered after the app and page rendering finish.
    styles: [
      ...React.Children.toArray(initialProps.styles),
      ...emotionStyleTags,
    ],
  };
};
Enter fullscreen mode Exit fullscreen mode

Ok, time to explain what exactly going to happen.

  • On the first line we import React
  • On the second line we import Document, HTML, Head, Main, NextScript
  • We extend our custom Document component with imported Document from 'next/document'. Generally the purpose is to have everything from Document component by default and then customize something in it.
  • Imported Html component help us to set some properties like lang or dir for our app.
  • Imported Head component is useful if you wanna have some general thing in you app, for example you can import your app icon here. Just be aware that this component is different from the one that we can import from 'next/head'
  • In addition to Html and Head component, Main and NextScript are also required for the page to render properly.
  • Next, when we use getInitialProps we enable server-side rendering and it let us to have initial data population. as the Next.js docs says:

it means sending the page with the data already populated from the server.

Step eight (Optional but recommended): Using prop-types package

It is a good practice to provide types for our props to avoid runtime errors and also make development easier. Since we don't use typescript in this starter, we can use "props-types" package to define type and enable runtime type checking for our app.

So, run npm i prop-types or yarn add prop-types.

after that open _app.js and replace the code with this one:

import React from 'react';
import PropTypes from 'prop-types';
import { CacheProvider } from '@emotion/react';
import { ThemeProvider, CssBaseline } from '@mui/material';

import createEmotionCache from '../utility/createEmotionCache';
import lightTheme from '../styles/theme/lightTheme';
import '../styles/globals.css';

const clientSideEmotionCache = createEmotionCache();

const MyApp = (props) => {
  const { Component, emotionCache = clientSideEmotionCache, pageProps } = props;

  return (
    <CacheProvider value={emotionCache}>
      <ThemeProvider theme={lightTheme}>
        <CssBaseline />
        <Component {...pageProps} />
      </ThemeProvider>
    </CacheProvider>
  );
};

export default MyApp;

MyApp.propTypes = {
  Component: PropTypes.elementType.isRequired,
  emotionCache: PropTypes.object,
  pageProps: PropTypes.object.isRequired,
};
Enter fullscreen mode Exit fullscreen mode

What we do here is that we define some keys and their type which can be accessible through props of MyApp component.

Quick start

If you feel this process a bit boring or if you want to start faster you can check the link below which is the result of steps that I explained above. Just download the code and rename the project to whatever you want and run npm i or yarn.

get starter from here

Conclusion

That's it, your app is now ready. Let me know if you need help or something else.


A banner of become a backer

Hi! I'm Hosein Pouyanmehr. I enjoy sharing what I learn and what I find interesting. Let's connect on LinkedIn.

See my code interests on GitHub.

Top comments (22)

Collapse
 
thekooldev1232 profile image
thedev1232

Nice article! Keep it going

An extension to this article will be - how effectively can we css using next js - if you see right now we got a bunch of ways to write the css and there is no definitive guide to do that

Food for thought!

cheers
skt

Collapse
 
Sloan, the sloth mascot
Comment deleted
Collapse
 
hpouyanmehr profile image
Hosein Pouyanmehr

My pleasure

Collapse
 
akbarss profile image
Akbarss

Image descriptiongog

Collapse
 
joey000 profile image
J0m0k0

Thank you Hossein. Your post helped me alot. Keep writing :)

Collapse
 
hpouyanmehr profile image
Hosein Pouyanmehr

I'm really glad that it was useful for you.

Collapse
 
igor_bykov profile image
Igor Bykov

Nice article! I noticed that it's loosely based on the official example as well.

The only problem with this approach is that this set up alone produces ~80kb of shared JS, which is a little scary considering that bundles of about ~100kb or more are already considered heavy by Next.js itself.

By the way, I didn't really noticed any difference (apart from increased bundle size) after adding the recommended set-up. Do you have any idea on why is this needed conceptually?

Collapse
 
laurenzhonauer profile image
laurenzhonauer

I think the Idea is that you want to include the styles into the first Response - that means in the original Document your browser recieves.

If you don't do it like that, you can experience flickering when first visiting the page. Depending on how much your page depends on the styles this can be more or less apparent.

As with everywhere, you are facing a trade off here. Do you want to have the styles immediatly on visit? Then you have to accept the larger bundle size.

Collapse
 
igor_bykov profile image
Igor Bykov

That sounds about to be right (or, at least, very aligned with what MUI itself says).

What's interesting is that I tried to load my page with JS disabled & without this set-up.
100% of styles were present on the initial page load.

Thread Thread
 
laurenzhonauer profile image
laurenzhonauer

I actually did not read the MUI page before, so i feel very good about my answer haha :D

I think the styles are not loaded via javascript but via style tags. So it should not matter that much if js is enabled or not.

Styles being present on initial page load - im sceptical about this. Maybe its just very fast and you dont notice the "flicker". You can try to throttle network speed on your browser. Or fire a request via postman and see if the actual styles are in the response you see there.

Without the setup the styles should be missing, but there should be a style tag that will load the styles as soon as the browser interprets it.

If the styles are already there, you uncoverd a next.js mystery here ^^

Collapse
 
austinit profile image
Austin

High quality article.
Thx.

Collapse
 
hpouyanmehr profile image
Hosein Pouyanmehr

You're welcome.

Collapse
 
epilogue_ profile image
P

hossein jan faghat ye soali, tadakholi ba chizi nadare in code ha?

Collapse
 
hpouyanmehr profile image
Hosein Pouyanmehr • Edited

سلام به شما

مواردی که گفته شد، تنظیماتی هستن که برای عملکرد صحیح ام‌یو‌آی داخل نکست ضروری هستن و تداخلی با چیزی ایجاد نمیکنه؛ توی کدها چیزی دور زده نشده که منجر به تداخل بشه و در حقیقت یه مسیر جدید برای عملکرد نکست تعریف شده که کَشِ مربوط به ام‌یوآی رو در هر دو سمت کلاینت و سرور یکسان نگه‌اش داریم.

با این حال اگه جایی به مشکلی برخوردی یا تداخلی ایجاد شده بود از نظرت، منو در جریان بذار، خوشحال میشم کمکت کنم.

Collapse
 
epilogue_ profile image
P

ممنون ازت حسین جان. من تازه نکست رو شروع کردم و چیزی از بک اند نمیدونم و هر آموزشی که میبینم یک سری کدهای بک هم داره که من اصلا متوجه نمیشم و گرفتن دیتا مثل ری اکت راحت نیست. نکست واقعا جزو مهارت های فرانت محسوب میشه؟

Thread Thread
 
hpouyanmehr profile image
Hosein Pouyanmehr

خواهش می‌کنم

یکی از دلایلی که باعث شد من بیام سمت نکست این بود که عیب و نقص های ری‌اکت رو نداشت، یه سری موارد داخلش باعث میشه که دیگه نیازی به نصب و کانفیگ پکیج‌های اضافه نباشه. نکست جی اس در کل یه پله فراتر از ری‌اکته چون نواقصی که ری‌اکت داره از جمله موارد مربوط به سئو رو به خوبی حل میکنه و اگه میخوای توی بازار کار موفق‌تر باشی به نظرم نکست میتونه بهت کمک کنه چون که داخل کشور خودمون علاقه به نکست روز به روز بیشتر میشه.

برای اینکه بتونی با بک‌اند کار کنی نیازی نیست کاملاً مسلط به اتفاقای اون سمت باشی، ارتباط با بک‌اند هم میتونه به شیوه‌های مختلفی داخل نکست جی اس پیاده بشه و حتی میتونی همون روشای ری‌اکت رو اینجا هم پیاده کنی.

از اونجایی که میگی تازه استارت زدی نکست رو این چیزا طبیعیه زیاد نگران این نباش که همه مسائل رو متوجه نمیشی اول کار، اولش برا همه سخته؛ حتی اگه چندین سال هم نکست کار کنی بازم به مسائلی بر میخوری که برات جدید باشن و ندونی.

من که از فنای نکست محسوب میشم 😂و هرچی بیشتر باهاش کار میکنم بیشتر متوجه قدرتش و انعطاف‌پذیریش میشم. اگه دوست داشتی آموزش زبان اصلی بهت معرفی کنم یه راه ارتباطی برام بذار من سعی میکنم هم راجع به نکست هم ری‌اکت و هم بک‌اند برات موارد باحالیو بفرستم.

Thread Thread
 
epilogue_ profile image
P

آره خیلی عالی میشه بهم معرفی کنی من همینجوری دارم جسته گریخته از جاهای مختلف یاد میگیرمش مثل همین مقاله ی خودت که تو سرچم بهش برخوردم. این آی دی تلگراممه حسین جان @parham_kns

Collapse
 
ricardorien profile image
Ricardo Aguilar

it's not used anymore? Sorry, all this stuff is kinda confusing.

Collapse
 
ricardorien profile image
Ricardo Aguilar

It should be nice an updated tutorial. Thanks!

Collapse
 
hpouyanmehr profile image
Hosein Pouyanmehr

Hey Ricardo, Thanks for reading. I hope to find some good free time. This article needs an update as there are updates on Next.js.

Collapse
 
reza-jajimi profile image
reza

Hi ... Nice article
Would you explain the same code for app router? Of course, without using 'use client' !

Collapse
 
hpouyanmehr profile image
Hosein Pouyanmehr

Hey Reza, Thanks for your feedback.
I need some free time to update this article or post another one to meet the "app" folder of Next.js. I'll let you know about that as soon as it was published.

Shortly about the frequent 'use client' convention in Next.js, as each component is a server component by default, and you have to keep the styles consice between client and server to avoid issues in MUI and Next.js, you need to use that phrase in some cases like the MUI theme related components. However you don't need to use that keyword everywhere as the MUI docs mentioned.

To support the App Router, currently all components and hooks from MUI libraries (Material UI, Joy UI, Base UI etc.) are exported with the "use client" directive. link

I will clarify where to use and where not to use the 'use client' in that article.