Abstract
Introducing how to Material UI 5 apply to Next.js 12 framework.
SSR
Framework 에는 Next.js
, Nuxt
, Sveltekit
등 여러가지가 있지만 가장 대표적인 framework는 react
기반의 Next.js
일 것이다.
본 포스팅에서는 Next.js
에 best react component library 중 하나인 MUI
를 적용하는 가장 최신 방법을 소개하고자 한다.
기존의 @mui/styles
패키지를 통해 적용하는 방법은 react 18
의 호환성 문제로 deprecated 되었으며 관련 내용은 하기 링크를 참조
The legacy styling solution of MUI: https://mui.com/system/styles/basics/
Getting Started
원하는 프로젝트 폴더에 Next.js TypeScript
프로젝트를 생성
Terminal
pnpm create next-app . --typescript
MUI
관련 패키지 설치
Terminal
pnpm add -S @emotion/cache @emotion/react @emotion/server @emotion/styled @mui/icons-material @mui/material
createEmotionCache
함수 모듈인 emotionCache.ts
를 lib
폴더에 생성
Note
클라이언트에서
<head/>
의 가장 최상단에<meta>
태그를 생성하고 이를insertionPoint
로 지정한다.이는 페이지 로딩시 MUI style 을 가장 먼저 로딩하는것을 보장
이렇게 먼저 로딩될 경우 다른 Style solution들 보다 높은 우선순위를 가지게 되어
MUI
로 개발하는데 이점을 가지게 된다.
lib/emotionCache.ts
import createCache from '@emotion/cache';
const isBrowser = typeof document !== 'undefined';
const createEmotionCache = () => {
let insertionPoint;
if (isBrowser) {
const emotionInsertionPoint = document.querySelector(
'meta[name="emotion-insertion-point"]'
) as HTMLElement;
insertionPoint = emotionInsertionPoint ?? undefined;
}
return createCache({ key: 'mui-style', insertionPoint });
};
export default createEmotionCache;
lib
폴더에 theme.ts
도 생성
Note
- 나중에 global style을 적용하기 위해 theme.ts 생성
lib/theme.ts
import { createTheme } from '@mui/material/styles';
import { indigo } from '@mui/material/colors';
const theme = createTheme({
palette: {
primary: {
main: indigo.A400,
},
},
});
export default theme;
_app.tsx
파일을 다음과 같이 수정
pages/_app.tsx
import * as React from 'react';
import Head from 'next/head';
import { ThemeProvider } from '@mui/material/styles';
import { CacheProvider } from '@emotion/react';
import CssBaseline from '@mui/material/CssBaseline';
import theme from '../lib/theme';
import createEmotionCache from '../lib/emotionCache';
import type { NextPage } from 'next';
import type { AppProps } from 'next/app';
import type { EmotionCache } from '@emotion/react';
type AppPropsWithCache = AppProps & {
Component: NextPage;
emotionCache?: EmotionCache;
};
const clientSideEmotionCache = createEmotionCache();
const MyApp = ({
Component,
emotionCache = clientSideEmotionCache,
pageProps,
}: AppPropsWithCache) => {
return (
<CacheProvider value={emotionCache}>
<Head>
<meta name='viewport' content='initial-scale=1, width=device-width' />
</Head>
<ThemeProvider theme={theme}>
<CssBaseline />
<Component {...pageProps} />
</ThemeProvider>
</CacheProvider>
);
};
export default MyApp;
_document.tsx
파일을 생성한 뒤 다음과 같이 작성
Note
_Document.getInitialProps
는_app
이 아닌_document
에 상속되며 static 으로 생성됨
_Document.getInitialProps = async (ctx: DocumentContext): Promise<DocumentInitialProps> => {...}
cache를 새로 생성하기보단 동일한 Emotion cache를 SSR Request에 사용한다면 퍼포먼스를 개선하는 효과를 얻을 수 있다. (하지만 global side effect가 발생할 수 있는 단점이 있다는 것도 인지해야한다.)
여기서는 SSR Request 마다 cache를 생성하기로 한다.
const cache = createEmotionCache();
-
styles fragment
는app
과page
의 렌더링이 끝난뒤에 렌더링된다.
styles: [...React.Children.toArray(initialProps.styles), ...emotionStyleTags],
- 잘못된 HTML 생성 방지를 위해 다음 구문은 중요.
참고링크: https://github.com/mui/material-ui/issues/26561#issuecomment-855286153
const emotionStyles = extractCriticalToChunks(initialProps.html);
Generating Order
서버
app.getInitialProps
page.getInitialProps
document.getInitialProps
app.render
page.render
document.render
서버에러
document.getInitialProps
app.render
page.render
document.render
클라이언트
app.getInitialProps
page.getInitialProps
app.render
page.render
pages/_document.tsx
import * as React from 'react';
import Document, { Html, Head, Main, NextScript } from 'next/document';
import createEmotionServer from '@emotion/server/create-instance';
import theme from '../lib/theme';
import createEmotionCache from '../lib/emotionCache';
import type { DocumentContext, DocumentInitialProps } from 'next/document';
export default class _Document extends Document {
render() {
return (
<Html lang='ko'>
<Head>
<meta name='theme-color' content={theme.palette.primary.main} />
<link rel='shortcut icon' href='/static/favicon.ico' />
<link
rel='stylesheet'
href='https://fonts.googleapis.com/css?family=Roboto:300,400,500,700&display=swap'
/>
<meta name='emotion-insertion-point' content='' />
{this.props.styles}
</Head>
<body>
<Main />
<NextScript />
</body>
</Html>
);
}
}
_Document.getInitialProps = async (ctx: DocumentContext): Promise<DocumentInitialProps> => {
const originalRenderPage = ctx.renderPage;
const cache = createEmotionCache();
const { extractCriticalToChunks } = createEmotionServer(cache);
ctx.renderPage = () =>
originalRenderPage({
enhanceApp: (App) =>
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,
styles: [...React.Children.toArray(initialProps.styles), ...emotionStyleTags],
};
};
index.tsx
를 다음과 같이 작성
pages/index.tsx
import { Button } from '@mui/material';
import type { NextPage } from 'next';
const Home: NextPage = () => {
return (
<main
style={{
width: '100vw',
height: '100vh',
display: 'flex',
justifyContent: 'center',
alignItems: 'center',
}}
>
<div>
<header>
<h1>MUI5 + Next.js 12</h1>
</header>
<section>
<Button variant={'contained'}>Hello MUI</Button>
</section>
</div>
</main>
);
};
export default Home;
Result
👉 CodeSandBox Sample Link
Conclusion
본 포스팅에서는 MUI 5
를 Next.Js 12
에 적용하는 최신 방법을 소개하였다. (2022년 8월 기준)
react 18
이 등장하면서 기존 방법보다 고려해야할 부분이 늘어난건 사실이나 여전히 reference가 많은 조합을 선호하는 트렌트 안에서는 Best 라고 할수있는 SSR Framework + UI Library 조합인 Next.Js
+ MUI
는 꾸준히 이용될 것이다.
이외에도 추천하는 React UI Framework 중 하나인 Mantine을 소개하면서 글을 마무리 짓고자 한다.
Mantine: https://mantine.dev/
Top comments (0)