DEV Community

Cover image for Next.js + MUI v5 + Typescript tutorial and starter
Hosein Pouyanmehr
Hosein Pouyanmehr

Posted on • Updated on

Next.js + MUI v5 + Typescript tutorial and starter

If you're just looking for a quick start or in other words for a starter, you can check and download the result of this post on GitHub.

Table of Contents

What is this post about?

In this article, we're going to start a project with Next.js, MUI v5 (formerly Material-UI), and we're also going to use TypeScript. I don't want to talk about What is Next.js or MUI in this post because you can find a lot to read about them on the web. But, briefly, I will list some of their benefits here.

While you are using Next.js:

  • Only required JavaScript and CSS for the page will be loaded which means your app pages will load a lot faster 🚀.
  • Next.js Image component optimize images in your app and this also helps you to have a faster page load and a better user experience.
  • Next.js will take care of Code-splitting and Bundling for you.

And lots of other benefits. Check the Next.js site to read more.

MUI v5:

Generally, when you use MUI for creating UI there will be a lot of power and freedom in design.

  • You have a lot of power because there are so many components and features that you can use in your app, even more than your needs.
  • You can customize components and almost everything the way you want. You can redesign them with an entirely new look but with the same base functionality.
  • You can have light and dark mode in your app as easy as ABC.
  • In the fifth version, you also have a smaller bundle size.

To read and learn more about MUI check it here.

TypeScript:

You know that Js doesn't care about typing, for instance you can put a string in a variable which you want as a number, so with Ts:

  • Your code will always be the way you want.
  • If you use an editor like VS Code, IntelliSense will be so satisfying 👌.
  • You won't have many runtime errors as you instantly notice if there is a mistake in the code.

And a lot more. Check this link.

Step One: Installation

Alright, it's time to install.

Next.js and TypeScript Installation

Generally, there are two ways to install Next.js. You can install it manually or via a single command. For this tutorial, I'll use the second approach. In comparison to the first method, the second one is much faster, Also with providing the "--typescript" flag, you'll have typescript and the required configurations in one go.

So, to install Next.js and TypeScript together run:
npx create-next-app@latest --typescript app-name
or if you use yarn:
yarn create next-app --typescript app-name

I use NPM as a package manager and I am also going to name this project "muxt-ts" (combination of MUI + Next and Typescript). So, for me it will be npx create-next-app@latest --typescript muxt-ts

When the installation is done, initial files and folders structure should be like this:

A screenshot of Next.js (typescript version) folder structure after installation

And, this is how package.json will look:

A screenshot of package.json after installing Next.js

Up to this point, you have your Next.js installed. To see if everything works perfectly, run npm run dev or yarn dev, and open http://localhost:3000 in your browser. You should see a page like this:

A screenshot of Next.js landing page after installlation

Emotion Installation

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

Using server and cache packages in upcoming steps will help us to avoid errors about mismatching of styles files.

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

MUI v5 Installation

  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.

A screenshot of package.json after installing packages

Step Two: Creating a MUI theme

Ok, First of all, I have to say that This step isn't necessary. You can start using MUI in your Next.js app without creating a theme and providing it to your components. But, In my book, it's better to create and provide it right now as you may need it very soon.

To create a theme you need to import "createTheme" function and pass some theme options. Briefly, what I am going to do here is to create a file named "lightThemeOption" and set the light mode for that for now. Later I will use these options to create my light mode theme. Let me explain the reasons that I do things like this:

  1. You know that if you have fewer import statements in your code, it can be more maintainable and independent.
  2. We want to create a theme, this may seem just one thing, but if you look closer, there are two concerns here; you define some options first, and then you use them to create the theme.
  3. I could name this file "themeOptions" but I didn't. The reason is you may need just one theme right now, but what about tomorrow? If you need another, what will you name it? If you want to rename this file, you should also double-check and rename the file name in the import statement.
  4. If you call this file "themeOptions", isn't it a bit generic? How you can find out if it is light or dark or reddish or whatever without opening it?

Always be as clear as possible. Naming this file "lightThemeOptions" tells us that first, this file contains some options for a theme; second, the options are for a light mode theme.

So, first, create a folder with the name of "theme" in the styles folder, with this approach if you decide to add another theme tomorrow you know where to store it; and after that, create a file named "lightThemeOptions.ts" in it.(If you want to start in dark mode you can name it "darkThemeOptions.ts")

A screenshot of VS Code showing folder structure of theme options

We're using TypeScript, and as you know MUI supports it perfectly. So first we import the type for theme options and then create a constant with that type. In the end, we export the created const as the default export. This is the code for "lightThemeOptions.ts":

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

const lightThemeOptions: ThemeOptions = {
  palette: {
    mode: 'light',
  },
};

export default lightThemeOptions;
Enter fullscreen mode Exit fullscreen mode

I keep this so simple, you can always come back and add your favorite options to that.

Step Three: Creating a utility to create emotion cache

Now it's time to create a utility that creates an emotion cache for us. First, create a top-level directory named "utility" which will contain all of your utilities in the future. Then add a file name "createEmotionCache.ts".

A screenshot of VS Code folder structure of utilities

Paste the code below in it.

import createCache from '@emotion/cache';

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

export default createEmotionCache;
Enter fullscreen mode Exit fullscreen mode

So, this function first will import "createCache " from "@emotion/cache" and then use it to create a cache that has got a key with the value of "css".
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.

Step Four: Create a custom document

In the pages directory add a file named "_document.tsx". This is the way that we can have our own document for a Next.js project. For now just copy the code below and paste it in it.

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: any) => (props) =>
        <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 Documents from "next/document". Generally, the purpose is to have everything from the 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 want to have some general thing in your 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 components, 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 have initial data population. as the Next.js docs say:

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

Step Five: Update _app.tsx

Ok, the last step. In this step, we need to update the code in the "_app.tsx" to provide the theme and the cache for our entire app.

  1. Open the "_app.tsx" file in the pages directory.
  2. Replace the code with the following one:
import * as React from 'react';
import type { AppProps } from 'next/app';
import { CacheProvider, EmotionCache } from '@emotion/react';
import { ThemeProvider, CssBaseline, createTheme } from '@mui/material';

import '@fontsource/roboto/300.css';
import '@fontsource/roboto/400.css';
import '@fontsource/roboto/500.css';
import '@fontsource/roboto/700.css';

import createEmotionCache from '../utility/createEmotionCache';
import lightThemeOptions from '../styles/theme/lightThemeOptions';
import '../styles/globals.css';
interface MyAppProps extends AppProps {
  emotionCache?: EmotionCache;
}

const clientSideEmotionCache = createEmotionCache();

const lightTheme = createTheme(lightThemeOptions);

const MyApp: React.FunctionComponent<MyAppProps> = (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.
  • We also import the "AppProps" type from "next"
  • "CacheProvider" is a component that we can use to pass our cache, and "EmotionCache" is the type for that provided cache.
  • "ThemeProvider" is something like "CacheProvider" which makes the theme accessible for all its children and because we are using that in "_app.tsx" it will make the theme accessible for all our components and pages.
  • As MUI docs say:

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

  • "createTheme" is the function which we use to pass our "lightThemeOptions" and create our light theme.
  • MUI by default uses Roboto and as you know we installed it before. Now we import some of its weight that is needed for MUI.
  • We also import our utility for creating emotion cache and our theme options for light mode.
  • Then we use "createEmotoinCache" to create a cache which will be the default value for the "emotionCache" prop.
  • After that, we create our light theme. In the returned code, we use created cache and theme in the providers and also make sure to use the "CssBaseline" component before "Component".

We're done and the starter is ready.

I've created this repository on GitHub where you can download this starter and use it in case you want to save more time. Download and rename it and done!


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 (16)

Collapse
 
ajjack50n profile image
ajjack50n

hey, this is a great post thank you!

I have a question, though, when I follow those steps, I find my pages jump. It seems styles are not applied until about a second after load - depending on complexity of page. Does anyone else see this?

Collapse
 
hpouyanmehr profile image
Hosein Pouyanmehr

Hi again,
As an update to my previous answer, this wasn't an MUI issue. You can read in this issue about that. The problem is from Next.js (Check the issue from here), and it'll be fixed in 12.1.7.

Collapse
 
ajjack50n profile image
ajjack50n

Thank you… appreciate this update as at 12.1.6 the issue still persists. I had heard that it didn’t show in the latest builds but waiting for the stable release.

Collapse
 
hpouyanmehr profile image
Hosein Pouyanmehr

Hi,
You're welcome. I am glad if it was helpful.

Sorry for the delay. There isn't a lot of free time for me right now.
Currently, MUI isn't fully compatible with React version 18, and I think your issue is about that. So, if you're using that version of react and react-dom, downgrading it to version 17 will solve it.

If you don't use v18, providing a sandbox may help to find out the problem.

Collapse
 
adamsiekierski profile image
Adam Siekierski

I have that issue. Did you manage to resolve it?

Collapse
 
hpouyanmehr profile image
Hosein Pouyanmehr

Hi,
This is actually related to Next.js, and checking this issue may be useful.

Collapse
 
eatrejosm profile image
Esteban Trejos

great post thanks

Collapse
 
hpouyanmehr profile image
Hosein Pouyanmehr • Edited

You're welcome, I'm glad that it was useful.

Collapse
 
kasaiee profile image
Mohammadreza Kasaiee

حسین داداش دمت گرم خیلی پست خوبی بود. پرچمت بالاست

Collapse
 
hpouyanmehr profile image
Hosein Pouyanmehr

قربونت محمدرضا جان، خوشحالم که مفید بود

Collapse
 
birdeye profile image
Birdeye

Hi,
I'm migrating Material 4 to MUI 5 ( React + TS + NextJs ) and followed the steps
but facing the below error,

Unhandled Runtime Error
TypeError: (0 , react_WEBPACK_IMPORTED_MODULE_5_.createContext) is not a function

Call Stack
eval
node_modules\@emotion\react\dist\emotion-element-cbed451f.browser.esm.js (12:54)
./node_modules/@emotion/react/dist/emotion-element-cbed451f.browser.esm.js
.next/static/chunks/pages/app.js (73:1)
options.factory
/_next/static/chunks/webpack.js (633:31)
__webpack_require
_
.next/static/chunks/webpack.js (37:33)
fn
/next/static/chunks/webpack.js (288:21)
eval
webpack-internal:///./node_modules/@emotion/react/dist/emotion-react.browser.esm.js (19:98)
./node_modules/@emotion/react/dist/emotion-react.browser.esm.js
.next/static/chunks/pages/_app.js (84:1)
options.factory
/_next/static/chunks/webpack.js (633:31)
__webpack_require
_
.next/static/chunks/webpack.js (37:33)
fn
/next/static/chunks/webpack.js (288:21)
eval
webpack-internal:///./components/App/App.tsx (6:72)
./components/App/App.tsx
.next/static/chunks/pages/_app.js (9269:1)
options.factory
/_next/static/chunks/webpack.js (633:31)
__webpack_require
_
.next/static/chunks/webpack.js (37:33)
fn
/next/static/chunks/webpack.js (288:21)
eval
webpack-internal:///./pages/_app.tsx (5:76)
./pages/_app.tsx
.next/static/chunks/pages/_app.js (9280:1)
options.factory
/_next/static/chunks/webpack.js (633:31)
__webpack_require
_
.next/static/chunks/webpack.js (37:33)
fn
/_next/static/chunks/webpack.js (288:21)
eval
node_modules\next\dist\build\webpack\loaders\next-client-pages-loader.js?absolutePagePath=private-next-pages%2F_app&page=%2F_app! (5:15)
eval
node_modules\next\dist\client\route-loader.js (235:50)

Collapse
 
hpouyanmehr profile image
Hosein Pouyanmehr • Edited

Hi,
You can get this error in different situations, and most of the time reason is dependencies versions mismatch. Checking the below steps may help you.

  1. Are the react and react-dom versions the same?
  2. Are you using react-redux? How about its version if yes?

Providing a screenshot from your package.json or creating a sample sandbox may help fix the issue faster.

Also, it is worth checking out MUI migrating docs.

Collapse
 
birdeye profile image
Birdeye

Thanks for the quick response!!!

I'm sharing the list of dependencies (dev-to-uploads.s3.amazonaws.com/up...) and devDependencies ( dev-to-uploads.s3.amazonaws.com/up... ) from package.json.
I'm not using react-redux.

Thread Thread
 
hpouyanmehr profile image
Hosein Pouyanmehr

You're welcome.
Because you're migrating and not creating a new MUI project, I highly recommend you follow the steps on MUI docs if you didn't. There are fundamental changes like style engine, and the documentation covers these topics in detail.

By the way, Your dependencies and devDependencies seem ok. I haven't had this issue before, and it's hard to find the fix without looking through the code. As it may be hard to create a sandbox according to the project size, I suggest asking this question on StackOverflow or opening an issue on GitHub.

I'll try to search about it again later and let you know if I found the fix. Also, I'll be appreciated it if you share the solution in case you find it.

Thread Thread
 
birdeye profile image
Birdeye • Edited

But the application works If add styled-components package.
and add the below line to tsconfig.json,
"paths": {
"@mui/styled-engine": ["./node_modules/@mui/styled-engine-sc"]
}

and,
next.config.js -> dev-to-uploads.s3.amazonaws.com/up...

Not sure why it worked If I add styled-components.

I've a folder called server which has one more tsconfig.json dev-to-uploads.s3.amazonaws.com/up... file.

And package.json => script => { "dev": "cross-env NODE_PATH=. NODE_ENV=development NODE_OPTIONS=--max-http-header-size=80000 ts-node server"}

Thread Thread
 
hpouyanmehr profile image
Hosein Pouyanmehr

Thanks for sharing this,

So, it seems that you're still using the previous MUI version (Material-UI 4), and the style engines are different in versions 5 (emotion) and 4 (styled-components). You can keep using MUI v5 with the fix you've provided in the comment. I suggest using v5 docs for implementing new features and slightly refactoring your code to make it fully compatible with the emotion engine. Also, you can use the codemods to make this process faster in particular cases.