Adding Material UI to Next.js TypeScript

Mui is a popular UI component library for React that provides pre-made components such as buttons, forms, and modals. In this article, we will be discussing how to add Mui v5 to Next.js using TypeScript.

Install Dependencies

First, you need to install the necessary dependencies. You can do this by running the following command in your VS Code terminal:

yarn add @mui/material @mui/icons-material @emotion/react @emotion/server @emotion/styled

Create a createEmotionCache.ts File

The next step is to create a createEmotionCache.ts file in your src folder. This is a utility function provided by Material-UI (mui) to create a cache for Emotion, which is a CSS-in-JS library used by Material-UI (mui) for styling. The cache is used to store generated class names and prevent duplicate style declarations.

Here's a required implementation of createEmotionCache.ts:

import createCache from '@emotion/cache';

const isBrowser = typeof document !== 'undefined';

// On the client side, Create a meta tag at the top of the <head> and set it as insertionPoint.
// This assures that MUI styles are loaded first.
// It allows developers to easily override MUI styles with other styling solutions, like CSS modules.
export default function createEmotionCache() {
  let insertionPoint;

  if (isBrowser) {
    const emotionInsertionPoint = document.querySelector<HTMLMetaElement>(
      'meta[name="emotion-insertion-point"]',
    );
    insertionPoint = emotionInsertionPoint ?? undefined;
  }

  return createCache({ key: 'mui-style', insertionPoint });
}

The const isBrowser = typeof document !== 'undefined'; statement checks if the script is running in a browser environment by checking if the document variable exists. It sets the isBrowser constant to a boolean value indicating if the script is running in a browser or not.

If isBrowser is true, the function retrieves the HTML meta element with the name attribute set to emotion-insertion-point. This meta element is used as an insertion point for MUI styles to ensure that they are loaded first and can be easily overridden with other styling solutions like CSS modules. If the meta element cannot be found, the insertionPoint variable is set to undefined.

Finally, the function returns an instance of the Emotion cache created using the createCache function from the @emotion/cache library. The key property of the configuration object passed to createCache is set to 'mui-style' and the insertionPoint property is set to the value retrieved from the HTML meta element or undefined if it was not found.

Create a theme.ts File

The next step is to create a theme.ts file in your src folder. This file contains the main theme configuration for your app.

Here's a required implementation of theme.ts:

import { Roboto } from 'next/font/google';
import { createTheme } from '@mui/material/styles';
import { red } from '@mui/material/colors';

export const roboto = Roboto({
  weight: ['300', '400', '500', '700'],
  subsets: ['latin'],
  display: 'swap',
  fallback: ['Helvetica', 'Arial', 'sans-serif'],
});

// Create a theme instance.
const theme = createTheme({
  palette: {
    primary: {
      main: '#556cd6',
    },
    secondary: {
      main: '#19857b',
    },
    error: {
      main: red.A400,
    },
  },
  typography: {
    fontFamily: roboto.style.fontFamily,
  },
});

export default theme;

The theme defines the color palette and typography settings for MUI components in an application.

The import statements at the beginning of the file import required modules to be used to create the theme.

The export const roboto = Roboto(...) statement creates an instance of the Roboto font family from the Google Fonts API using the Roboto function imported from the next/font/google library. It sets various options like weight, subsets, display, and fallback for the font family. This instance can be used in the typography section of the MUI theme to set the default font family for all components.

The const theme = createTheme(...) statement creates an instance of the MUI theme by calling the createTheme function and passing an object with parameters that define the theme. The palette parameter is an object that defines the primary, secondary, and error colors for the theme. The primary color is set to #556cd6, the secondary color is set to #19857b, and the error color is set to the red.A400 value from the @mui/material/colors library.

The typography parameter is another object that defines the typography settings for the theme. It sets the default font family for all components to the fontFamily property of the roboto.style object which was created earlier.

Finally, the export default theme; statement exports the created MUI theme object as the default export of this module to be used throughout the application.

Edit _app.tsx File

Next, you need to edit _app.tsx file in the projects src/pages folder. This file is used to wrap your app in a higher-order component (HOC) that provides global styles and theme settings.

Here's a required implementation of _app.tsx:

import * as React from 'react';
import Head from 'next/head';
import { AppProps } from 'next/app';
import { ThemeProvider } from '@mui/material/styles';
import CssBaseline from '@mui/material/CssBaseline';
import { CacheProvider, EmotionCache } from '@emotion/react';
import createEmotionCache from '@/createEmotionCache';
import theme from '@/theme';

// Client-side cache, shared for the whole session of the user in the browser.
const clientSideEmotionCache = createEmotionCache();

export interface EmployeeLeaveManagerProps extends AppProps {
  emotionCache?: EmotionCache;
}

export default function EmployeeLeaveManagerApp(props: EmployeeLeaveManagerProps) {
  const { Component, emotionCache = clientSideEmotionCache, pageProps } = props;
  return (
    <CacheProvider value={emotionCache}>
      <Head>
        <meta name="viewport" content="initial-scale=1, width=device-width" />
      </Head>
      <ThemeProvider theme={theme}>
        {/* CssBaseline kickstart an elegant, consistent, and simple baseline to build upon. */}
        <CssBaseline />
        <Component {...pageProps} />
      </ThemeProvider>
    </CacheProvider>
  );
}

Edit _document.tsx File

In Next.js, the _document.tsx file is a special file used for server-side rendering (SSR) and to override the default Document component provided by Next.js.

The Document component in Next.js is responsible for rendering the initial HTML document for the app, including the <html>, <head>, and <body> tags. It is used during server-side rendering to generate the HTML that is sent to the client.

By default, Next.js provides a Document component that renders a minimal HTML document with some Next.js specific scripts and styles. However, in some cases, you may want to customize the HTML document, for example, by adding custom scripts, styles, or meta tags.

Here's a required implementation of _document.tsx:

import * as React from 'react';
import Document, {
  Html,
  Head,
  Main,
  NextScript,
  DocumentProps,
  DocumentContext,
} from 'next/document';
import createEmotionServer from '@emotion/server/create-instance';
import { AppType } from 'next/app';
import { EmployeeLeaveManagerProps } from './_app';
import theme, { roboto } from '@/theme';
import createEmotionCache from '@/createEmotionCache';

interface EmployeeLeaveManagerDocumentProps extends DocumentProps {
  emotionStyleTags: JSX.Element[];
}

export default function EmployeeLeaveManagerDocument({ emotionStyleTags }: EmployeeLeaveManagerDocumentProps) {
  return (
    <Html lang="en" className={roboto.className}>
      <Head>
        {/* PWA primary color */}
        <meta name="theme-color" content={theme.palette.primary.main} />
        <link rel="shortcut icon" href="/favicon.ico" />
        <meta name="emotion-insertion-point" content="" />
        {emotionStyleTags}
      </Head>
      <body>
        <Main />
        <NextScript />
      </body>
    </Html>
  );
}

// `getInitialProps` belongs to `_document` (instead of `_app`),
// it's compatible with static-site generation (SSG).
EmployeeLeaveManagerDocument.getInitialProps = async (ctx: DocumentContext) => {
  // 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);

  ctx.renderPage = () =>
    originalRenderPage({
      enhanceApp: (App: React.ComponentType<React.ComponentProps<AppType> & EmployeeLeaveManagerProps>) =>
        function EnhanceApp(props) {
          return <App emotionCache={cache} {...props} />;
        },
    });

  const initialProps = await Document.getInitialProps(ctx);
  // This is important. It prevents Emotion to render invalid HTML.
  // See https://github.com/mui/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,
    emotionStyleTags,
  };
};

Import Mui Components

Now that you have set up your custom required files, you can start importing Mui components into your pages or other components. For example mui Button:

import Head from 'next/head'
import Box from '@mui/material/Box'
import Button from '@mui/material/Button'

export default function Home() {
  return (
    <>
      <Head>
        <title>Employee Leave Manager App</title>
        <meta name="description" content="Generated by create next app" />
        <meta name="viewport" content="width=device-width, initial-scale=1" />
        <link rel="icon" href="/favicon.ico" />
      </Head>
      <Box>
        <Button variant="contained">
          MUI Button
        </Button>
      </Box>
    </>
  )
}

In this example, we import the Button component from Mui and use it in our Home component. We also use Next.js's Head component to create title and meta of our page.

Conclusion

Adding MUI to your Next.js TypeScript project can be a powerful way to quickly add pre-made UI components and styles. By following the steps in this article, you should now have a good foundation for integrating MUI into your app.

For reference visit https://github.com/mui/material-ui/tree/master/examples/material-next-ts

11
Subscribe to my newsletter

Read articles from Arshad Ali Soomro directly inside your inbox. Subscribe to the newsletter, and don't miss out.

Written by

Arshad Ali Soomro
Arshad Ali Soomro

Hi there, I'm a tech enthusiast with a passion for programming and a keen eye for developing high-quality software solutions. With my skills in Java, Spring Boot RESTful API development, and Android app development, I've gained a wealth of experience in building robust and scalable applications that meet the needs of businesses and end-users alike. Recently, I've ventured into the world of Next.js, and I'm excited to expand my knowledge and skills in this area. As an enthusiastic learner and problem-solver, I thrive in challenging environments and am constantly pushing myself to grow and evolve. When I'm not coding, you can find me exploring new technologies, tinkering with new gadgets, or playing video games. I'm also passionate about giving back to the tech community, and I regularly contribute to open-source projects and share my knowledge and experiences with others. If you're interested in collaborating on a project or just want to connect, feel free to drop me a message!