Jul 11th, 2022
Step 1 : Create Next.js project (สำหรับ typescript projectสามารถเพิ่ม flag --typescript ในคำสั่งได้เลยค่ะ) npx create-next-app@latest --typescript
เมื่อ run คำสั่งนี้เสร็จแล้ว ลอง run project ด้วยคำสั่ง npm run dev
Step 2 : Install MUI npm install @mui/material @emotion/react @emotion/styled
Step 3 : ในกรณีที่เราใช้ MUI กับ Next.js เราต้อง install emotion package เพิ่มเติมnpm i @emotion/cache @emotion/react @emotion/server @emotion/styled
Step 4 : สร้าง MUI theme
โดยการสร้าง folder 'theme' ใน styles folderจากนั้นสร้างไฟล์ index.ts, pallette.ts, และ typography.js
index.ts เป็นไฟล์สำหรับแก้ไข MUI theme ไฟล์หลัก
import { createTheme, Theme as _Theme } from "@mui/material/styles";
import { createBreakpoints } from "@mui/system";
import palette from "./palette";
import typography from "./typography";
interface Theme extends _Theme {
status: any;
}
const breakpoints = createBreakpoints({});
// Create a theme instance.
export const theme: Theme = createTheme({
status: {
appState: "loading",
},
typography,
palette,
shape: {
borderRadius: 6,
},
spacing: 8,
breakpoints: {
values: {
xs: 0,
sm: 576,
md: 768,
lg: 992,
xl: 1200,
},
},
}) as any;
export default theme;
pallete.ts เป็นไฟล์ที่ใช้จัดการธีมสีต่างๆ เช่น Primary, Secondary, Success
import { colors } from '@mui/material';
const white = '#FFFFFF';
const black = '#222';
export default {
primary: {
contrastText: white,
dark: black,
main: black,
light: '#212121',
},
secondary: {
contrastText: white,
dark: '#111827',
main: '#1f2937',
light: '#4b5563',
},
success: {
contrastText: white,
dark: "#54cb7b",
main: "#4EC274",
light: colors.green[400],
},
info: {
contrastText: white,
dark: '#737373',
main: '#949494',
light: '#949494',
},
warning: {
contrastText: white,
dark: colors.orange[900],
main: colors.orange[600],
light: colors.orange[400],
},
error: {
contrastText: white,
dark: '#960010',
main: '#F72220',
light: '#ff6d61',
},
text: {
primary: black,
secondary: '#949494',
link: black,
},
background: {
default: '#f9f9f9',
paper: white,
},
icon: colors.blueGrey[600],
divider: '#E0E0E0',
border: '#cecece',
};
typography.ts เป็นไฟล์ที่ใช้จัดการเกี่ยวกับ ตัวหนังสือ ซึ่ง reference จาก variant ของ Component typography ของ MUI เช่น h1, h2, body1, subtitle1
import { createBreakpoints } from '@mui/system';
import palette from './palette';
const breakpoints = createBreakpoints({});
export default {
fontFamily: 'Nunito, sans-serif',
h1: {
color: palette.text.primary,
fontWeight: 700,
fontSize: '32px',
lineHeight: '1.2',
[breakpoints.up('sm')]: {
fontSize: '42px',
},
},
h2: {
color: palette.text.primary,
fontWeight: 700,
fontSize: '26px',
letterSpacing: '0',
lineHeight: '32px',
marginBottom: '20px',
},
h3: {
color: palette.text.primary,
fontWeight: 600,
fontSize: '22px',
letterSpacing: '0',
lineHeight: '28px',
},
h4: {
color: palette.text.primary,
fontWeight: 700,
fontSize: '18px',
letterSpacing: '0',
lineHeight: '24px',
},
h5: {
color: palette.text.primary,
fontWeight: 500,
fontSize: '16px',
letterSpacing: '-0.05px',
lineHeight: '20px',
},
h6: {
color: palette.text.primary,
fontWeight: 500,
fontSize: '14px',
letterSpacing: '-0.05px',
lineHeight: '20px',
},
subtitle1: {
color: palette.text.primary,
fontSize: '14px',
fontWeight: '500',
letterSpacing: '0',
lineHeight: '20px',
[breakpoints.up('sm')]: {
fontSize: '18px',
},
},
subtitle2: {
color: palette.black,
fontSize: '16px',
fontWeight: '600',
letterSpacing: '0',
lineHeight: '20px',
},
body1: {
color: palette.text.primary,
fontSize: '16px',
letterSpacing: '0',
lineHeight: '20px',
},
body2: {
color: palette.black,
fontSize: '18px',
letterSpacing: '0',
lineHeight: '22px',
// whiteSpace: 'pre-line',
'&.MuiTypography-gutterBottom': {
marginBottom: '16px',
}
},
button: {
color: palette.text.primary,
fontSize: '16px',
fontWeight: '400',
},
caption: {
color: palette.text.secondary,
fontSize: '11px',
letterSpacing: '0.33px',
lineHeight: '13px',
},
overline: {
color: palette.text.secondary,
fontSize: '11px',
fontWeight: 500,
letterSpacing: '0.33px',
lineHeight: '13px',
textTransform: 'uppercase',
},
a: {
color: palette.text.primary,
},
label: {
fontSize: '16px',
fontWeight: 700,
color: palette.text.primary,
},
};
Step 5 : Create an emotion cache utils/createEmotionCache.tsไฟล์ emotionCache นี้จะช่วยให้เราสามารถ override MUI styles ได้ เช่น ในกรณีที่เราต้องเขียน CSS Modules
import createCache from '@emotion/cache';
const createEmotionCache = () => {
return createCache({ key: 'css', prepend: true });
};
export default createEmotionCache;Step 6 : Edit _app.tsx
import { CacheProvider, EmotionCache } from "@emotion/react";
import CssBaseline from "@mui/material/CssBaseline";
import { StyledEngineProvider, ThemeProvider } from "@mui/material/styles";
import { AppProps } from "next/app";
import Head from "next/head";
import ModalProvider from "../src/provider/ModalProvider";
import createEmotionCache from "../src/styles/createEmotionCache";
import "../src/styles/globals.scss";
import theme from "../src/styles/theme";
// Client-side cache, shared for the whole session of the user in the browser.
const clientSideEmotionCache = createEmotionCache();
interface MyAppProps extends AppProps {
emotionCache?: EmotionCache;
}
export default function MyApp(props: MyAppProps) {
const {
Component,
emotionCache = clientSideEmotionCache,
pageProps,
} = props;
return (
<CacheProvider value={emotionCache}>
<Head>
<meta
name="viewport"
content="initial-scale=1, width=device-width"
/>
</Head>
<StyledEngineProvider injectFirst>
<ThemeProvider theme={theme}>
{/* CssBaseline kickstart an elegant, consistent, and simple baseline to build upon. */}
<CssBaseline />
<ModalProvider>
<Component {...pageProps} />
</ModalProvider>
</ThemeProvider>
</StyledEngineProvider>
</CacheProvider>
);
}
ในไฟล์นี้เราจะเพิ่ม CacheProvider เข้าไป
เพิ่ม ThemeProvider เพื่อเรียกใช้ theme MUI ที่เรา custom ไว้
เรียกใช้ CssBaseline
ซึ่งหน้าที่ของ CssBaseline ก็จะเหมือนกับการใช้ normalize.css ซึ่งเป็นตัวช่วยปรับ style ที่บราวเซอร์แต่ละบราวเซอร์แสดงผลไม่เหมือนกัน ให้มีความสอดคล้องกัน และแสดงผลคล้ายกันมากขึ้น
Step 7 : Create custom _document.tsx
import createEmotionServer from "@emotion/server/create-instance";
import Document, { Head, Html, Main, NextScript } from "next/document";
import createEmotionCache from "../src/styles/createEmotionCache";
import theme from "../src/styles/theme";
export default class MyDocument extends Document {
render() {
return (
<Html lang="en">
<Head>
{/* PWA primary color */}
<meta
name="theme-color"
content={theme.palette.primary.main}
/>
<link rel="shortcut icon" href="/static/favicon.ico" />
{/* Inject MUI styles first to match with the prepend: true configuration. */}
{(this.props as any).emotionStyleTags}
</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);
ctx.renderPage = () =>
originalRenderPage({
enhanceApp: (App: any) =>
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,
};
};