Next.js v14 App router + MUI v5

Next.js v14 App router + MUI v5

ยท

7 min read

๐Ÿ’ก
Next.js ์™€ MUI ์…‹ํŒ…๋ฒ•์— ๋Œ€ํ•ด ์ •๋ฆฌํ•˜์˜€์Šต๋‹ˆ๋‹ค.

Node.js ์„ค์น˜

Next.js๋ฅผ ์„ค์น˜ํ•˜๊ธฐ์œ„ํ•ด์„œ๋Š” Node.js 18.17 or later ๋ฒ„์ „์ด ํ•„์š”ํ•ฉ๋‹ˆ๋‹ค.
Node.js๋Š” Javascript๋กœ ์ž‘์„ฑ๋œ ํ”„๋กœ๊ทธ๋žจ์„ ์šด์˜์ฒด์ œ ์ƒ์—์„œ ์ผ๋ฐ˜ ์• ํ”Œ๋ฆฌ์ผ€์ด์…˜ ํ”„๋กœ๊ทธ๋žจ์ฒ˜๋Ÿผ ์‹คํ–‰์‹œ์ผœ์ฃผ๋Š” ๋Ÿฐํƒ€์ž„์ž…๋‹ˆ๋‹ค. Next.js๋ฅผ ์‚ฌ์šฉํ•ด๋ณด๊ธฐ ์œ„ํ•ด์„œ ์„ค์น˜ํ•ด ๋ด…๋‹ˆ๋‹ค.

  1. node.js ๊ณต์‹๋ฌธ์„œ https://nodejs.org/en

  2. LTS ๋‹ค์šด๋กœ๋“œ ํด๋ฆญํ•ฉ๋‹ˆ๋‹ค. LTS๋Š” long-term supprot๋กœ ์•ˆ์ •ํ™” ๋œ ๋ฒ„์ „์ด๋ผ๊ณ  ๋ณด์‹œ๋ฉด๋ฉ๋‹ˆ๋‹ค.

  3. ๋‹ค์šด๋กœ๋“œ๊ฐ€ ์™„๋ฃŒ๋˜๋ฉด ์‹คํ–‰ํ•˜์—ฌ Node.js๋ฅผ ์„ค์น˜ํ•ฉ๋‹ˆ๋‹ค. ๋ชจ๋‘ Next๋ฅผ ๋ˆŒ๋Ÿฌ์„œ ์„ค์น˜ํ•˜์‹œ๋ฉด ๋ฉ๋‹ˆ๋‹ค. (ํ˜น์‹œ ์„ค์น˜๊ฐ€ ์ œ๋Œ€๋กœ ๋˜์ง€ ์•Š๋Š”๋‹ค๋ฉด ๊ตฌ๊ธ€์— Node.js์„ค์น˜๋ฅผ ๊ฒ€์ƒ‰ํ•˜์‹œ๋ฉด ๋„์›€์ด ๋˜๋Š” ๋งŽ์€ ๊ธ€๋“ค์ด์žˆ์Šต๋‹ˆ๋‹ค!)

  4. ์„ค์น˜ ์™„๋ฃŒ ํ›„ ํ„ฐ๋ฏธ๋„์—์„œ node -v๋ฅผ ์น˜๋ฉด ํ˜„์žฌ ์„ค์น˜๋œ ๋ฒ„์ „์ด ๋‚˜ํƒ€๋‚˜๋ฉด ์„ฑ๊ณต์ž…๋‹ˆ๋‹ค.

  5. ํ˜„์žฌ ์„ค์น˜๋œ node ๋ฒ„์ „์€ 20.12.1์ž…๋‹ˆ๋‹ค. node๋Š” nvm์„ ํ†ตํ•ด ๋ฒ„์ „ ๊ด€๋ฆฌ๋ฅผ ํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค.
    ์ด ํฌ์ŠคํŠธ์—์„œ๋Š” nvm์ด์•ผ๊ธฐ๋Š” ์ƒ๋žตํ•˜๊ณ  ๊ด€๋ จ ํฌ์ŠคํŠธ ๋งํฌ ์ฒจ๋ถ€ํ•˜๊ฒ ์Šต๋‹ˆ๋‹ค. (nvm ์„ค์น˜)

  6. ์‹œ๊ฐ„์ด ๋‚˜์‹ ๋‹ค๋ฉด ๋‚˜๋ฌด์œ„ํ‚ค์—์„œ Node.js์— ๋Œ€ํ•ด์„œ๋„ ์ฝ์–ด๋ณด์„ธ์š”! (๋‚˜๋ฌด์œ„ํ‚ค Node.js)


Next.js ์„ค์น˜

  1. Next.js๋ฅผ ์„ค์น˜ํ•˜๊ธฐ ์•ž์„œ ์™œ Next.js๋ฅผ ์“ฐ๋Š”์ง€ ์•Œ์•„๋ณด๊ฒ ์Šต๋‹ˆ๋‹ค.

    React ๊ณต์‹๋ฌธ์„œ์— Strat a New React project๋ฅผ ๋ณด๋ฉด ํ”„๋ ˆ์ž„์›Œํฌ ์‚ฌ์šฉ์„ ๊ถŒ์žฅํ•˜๊ณ ์žˆ์Šต๋‹ˆ๋‹ค. ์•„๋ž˜๋Š” ๊ณต์‹๋ฌธ์„œ์—์„œ ๋งํ•˜๊ณ ์žˆ๋Š” ์žฅ์ ์„ ์ •๋ฆฌํ•˜์˜€์Šต๋‹ˆ๋‹ค.

  2. Next.js๋ฟ ๋งŒ๋‹ˆ๋ผ Remix, Gatsby ๋“ฑ ๋‹ค์–‘ํ•œ ํ”„๋ ˆ์ž„ ์›Œํฌ๊ฐ€ ์žˆ์Šต๋‹ˆ๋‹ค. ์ง„ํ–‰ํ•˜๊ณ ์žํ•˜๋Š” ํ”„๋กœ์ ํŠธ ํŠน์„ฑ์— ๋งž๊ฒŒ ์‚ฌ์šฉํ•˜๋ฉด ๋ฉ๋‹ˆ๋‹ค. (๊ฐ€๋ฒผ์šด ํ”„๋กœ์ ํŠธ๋Š” ์ˆœ์ˆ˜ ๋ฆฌ์•กํŠธ๋ฅผ ์‚ฌ์šฉํ•ด๋„ ๋ฌด๋ฐฉํ•ฉ๋‹ˆ๋‹ค.)

  3. ๋‹ค์‹œ ๋ณธ๋ก ์œผ๋กœ ๋Œ์•„์™€ Next.js ์„ค์น˜ ๋ฐฉ๋ฒ•์— ๋Œ€ํ•ด์„œ ์•Œ์•„๋ณด๊ฒ ์Šต๋‹ˆ๋‹ค. ๋‚ด๊ฐ€ ์„ค์น˜ํ•˜๊ณ ์‹ถ์€ ๊ฒฝ๋กœ์— ์ ‘๊ทผ ํ›„ ํ„ฐ๋ฏธ๋„์— ์•„๋ž˜ ๋ช…๋ น์–ด๋ฅผ ์‹คํ–‰ํ•ฉ๋‹ˆ๋‹ค.

     npx create-next-app@latest
    
  4. ๋ช…๋ น์–ด๋ฅผ ์‹คํ–‰ํ•˜๋ฉด ์•„๋ž˜์ฒ˜๋Ÿผ ์ž…๋ ฅ ๋ฐ›๋Š” ์ฐฝ์ด ๋‚˜์˜ต๋‹ˆ๋‹ค.
    - ์›ํ•˜๋Š” ํ”„๋กœ์ ํŠธ์ด๋ฆ„
    - ํƒ€์ž…์Šคํฌ๋ฆฝํŠธ ์‚ฌ์šฉ์—ฌ๋ถ€
    - ESlint ์‚ฌ์šฉ์—ฌ๋ถ€
    - tailwind ์‚ฌ์šฉ์—ฌ๋ถ€
    - src ํด๋” ์‚ฌ์šฉ์—ฌ๋ถ€
    - app router์‚ฌ์šฉ์—ฌ๋ถ€
    - import alias์‚ฌ์šฉ ์—ฌ๋ถ€
    ํ•˜๊ณ ์žํ•˜๋Š” ํ”„๋กœ์ ํŠธ ๊ธฐ์ˆ ์Šคํƒ์— ๋งž๊ฒŒ ์„ ํƒํ•˜์‹œ๋ฉด ๋ฉ๋‹ˆ๋‹ค.

  5. ์„ค์น˜๊ฐ€ ์™„๋ฃŒ ํ›„ vscode์—์„œ ํ”„๋กœ์ ํŠธ ํŒŒ์ผ์„ ์—ด์–ด๋ณด๋ฉด npm์œผ๋กœ ํŒจํ‚ค์ง€๊ฐ€ ์„ค์น˜๋˜์–ด ์žˆ์Šต๋‹ˆ๋‹ค.
    (npm์€ node.js์˜ ํŒจํ‚ค์ง€(๋…๋ฆฝ๋œ ๋ชจ๋“ˆ)์„ ๊ฐœ๋ฐœํ•˜๊ณ , ๋ฐฐํฌ, ๊ณต์œ ํ•˜๊ธฐ ์œ„ํ•œ ๊ด€๋ฆฌ ํˆด์ž…๋‹ˆ๋‹ค. ์ž์„ธํ•œ ๋‚ด์šฉ์€ npm ๊ตฌ๊ธ€๊ฒ€์ƒ‰ GoGo)

  6. ์ €๋Š” yarn ์ด๋ผ๋Š” ํŒจํ‚ค์ง€ ๋งค๋‹ˆ์ €๋ฅผ ์‚ฌ์šฉํ•  ๊ฒƒ์ด๊ธฐ ๋•Œ๋ฌธ์— yarn ์„ ์„ค์น˜ํ•ด์ค๋‹ˆ๋‹ค. ํ„ฐ๋ฏธ๋„์— ์•„๋ž˜ ๋ช…๋ น์–ด๋ฅผ ์ž…๋ ฅํ•ฉ๋‹ˆ๋‹ค.

     npm install --global yarn
    
  7. yarn์„ ์„ค์น˜ ํ›„ ํ„ฐ๋ฏธ๋„์— yarn -v๋กœ ๋ฒ„์ „์„ ํ™•์ธํ•ฉ๋‹ˆ๋‹ค. ๋ฒ„์ „์ด ๋‚˜์˜ค๋ฉด ์„ค์น˜๊ฐ€ ์™„๋ฃŒ๋œ ๊ฒƒ์ž…๋‹ˆ๋‹ค.

  8. ๋‹ค์‹œ ํ”„๋กœ์ ํŠธ์—์„œ ๊ฐ€์„œ ์•„๋ž˜ node_modules์™€ pack-lock.json์„ ์ง€์šฐ๊ณ  yarn install์„ ์‹คํ–‰ ํ•ฉ๋‹ˆ๋‹ค.

  9. yarn ์œผ๋กœ ํŒจํ‚ค์ง€ ์„ค์น˜๊ฐ€ ๋‹ค์‹œ ์™„๋ฃŒ ๋˜๋ฉด ์•„๋ž˜ ์ด๋ฏธ์ง€ ์ฒ˜๋Ÿผ node_modules์™€ yarn.lock์ด ์ƒ์„ฑ ๋ฉ๋‹ˆ๋‹ค.

  10. ์ด์ œ ํ„ฐ๋ฏธ๋„์—์„œ yarn dev๋กœ ํ”„๋กœ์ ํŠธ๋ฅผ ์‹คํ–‰ํ•˜๋ฉด ์•„๋ž˜ ํ™”๋ฉด์ด localhost:3000์— ์‹คํ–‰๋ฉ๋‹ˆ๋‹ค.
    ์„ค์น˜ ์™„๋ฃŒ!


MUI v5 ์„ค์น˜

  1. Next.js์— MUI๋ฅผ ์…‹ํŒ…ํ•ด๋ณด๋„๋ก ํ•˜๊ฒ ์Šต๋‹ˆ๋‹ค. ์…‹ํŒ…์— ์•ž์„œ MUI๋ž€? ์„ ๊ตฌ๊ธ€์— ๊ฒ€์ƒ‰ํ•˜๋ฉด..

    MUI๋Š” Material Design ์›์น™์„ ๊ธฐ๋ฐ˜์œผ๋กœ ํ•œ React ์ปดํฌ๋„ŒํŠธ ๋ผ์ด๋ธŒ๋Ÿฌ๋ฆฌ์ž…๋‹ˆ๋‹ค. ๊ฐœ๋ฐœ์ž๊ฐ€ ๊ณ ํ’ˆ์งˆ์˜ UI๋ฅผ ๋น ๋ฅด๊ฒŒ ๊ตฌ์ถ•ํ•  ์ˆ˜ ์žˆ๋„๋ก ๋‹ค์–‘ํ•œ ๋ฏธ๋ฆฌ ๋งŒ๋“ค์–ด์ง„ ์ปดํฌ๋„ŒํŠธ๋ฅผ ์ œ๊ณตํ•ฉ๋‹ˆ๋‹ค. MUI๋ฅผ ์‚ฌ์šฉํ•˜๋ฉด ์• ํ”Œ๋ฆฌ์ผ€์ด์…˜์˜ ์ผ๊ด€์„ฑ๊ณผ ์ ‘๊ทผ์„ฑ์„ ํ–ฅ์ƒ์‹œํ‚ค๋ฉด์„œ ๊ฐœ๋ฐœ ์‹œ๊ฐ„์„ ํฌ๊ฒŒ ๋‹จ์ถ•์‹œํ‚ฌ ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค.

    ๋ผ๊ณ  ํ•ฉ๋‹ˆ๋‹ค. ใ…Žใ…Ž ์ €๋Š” ์ฒซ ํšŒ์‚ฌ์—์„œ styled-compoent๋ฅผ ์“ฐ๋‹ค๊ฐ€ MUI๋ฅผ ์‚ฌ์šฉํ•˜๊ฒŒ ๋˜์—ˆ๋Š”๋ฐ, ์ฒ˜์Œ์—๋Š” ์ปค์Šคํ…€ํ•˜๋Š”๋ฐ ๋„ˆ๋ฌด ์–ด๋ ค์›Œ์„œ ์“ฐ๊ธฐ ์‹ซ๋‹ค๊ฐ€ ์ด์ œ๋Š” MUI๋งŒ ์“ฐ๊ฒŒ ๋์Šต๋‹ˆ๋‹ค. ๋ญ ๊ทธ๋ ‡์Šต๋‹ˆ๋‹ค.

  2. ๋‹ค์‹œ ๋ณธ๋ก ์œผ๋กœ ๋Œ์•„์™€์„œ MUI๋ฅผ ์„ค์น˜ํ•ฉ๋‹ˆ๋‹ค. ํ„ฐ๋ฏธ๋„์—์„œ ์•„๋ž˜ ๋ช…๋ น์–ด๋ฅผ ์ž…๋ ฅํ•ฉ๋‹ˆ๋‹ค.

     yarn add @mui/material @emotion/react @emotion/styled
    

    ์ฃผ์˜ํ•  ์ ์€ styledcompoent๋Š” server-rendered๋ฅผ ์ง€์›ํ•˜์ง€ ์•Š์Šต๋‹ˆ๋‹ค. ๊ณต์‹๋ฌธ์„œ์—์„œ SSR ํ”„๋กœ์ ํŠธ๋Š” ๊ฐ•๋ ฅํ•˜๊ฒŒ Emotion์“ฐ๋ผ๊ณ  ๊ถŒ์žฅํ•˜๊ณ ์žˆ์Šต๋‹ˆ๋‹ค. ๊ผญ emotion์œผ๋กœ ์„ค์น˜ํ•ฉ์‹œ๋‹ค.

  3. ์„ค์น˜๋ฅผ ์™„๋ฃŒํ•˜๊ณ  ๊ณต์‹๋ฌธ์„œ์—์„œ Next.js๋ฅผ ์ฐพ์•„๋ณด๋ฉด ์•„๋ž˜ ํƒญ์ด ์žˆ์Šต๋‹ˆ๋‹ค. ์—ฌ๊ธฐ๋ฅผ ๋ณด๊ณ  ์ง„ํ–‰ํ•ด๋„ ๋˜์ง€๋งŒ v13 ๋ฒ„์ „ ๊ธฐ์ค€์œผ๋กœ ์ž‘์„ฑ๋˜์—ˆ์Šต๋‹ˆ๋‹ค. Next.js๋Š” ํ˜„์žฌ v15๋ฅผ ๋ฐ”๋ผ๋ณด๊ณ ์žˆ๋Š”๋ฐ v13๊ธฐ์ค€์ธ๊ฒŒ ์•„์ฃผ ๋ง˜์— ์•ˆ๋“ญ๋‹ˆ๋‹ค. ๋‹ค๋ฅธ๋ฐฉ๋ฒ•์„ ์ฐพ๊ฒ ์Šต๋‹ˆ๋‹ค.

  4. MUI๋Š” ์นœ์ ˆํ•˜๊ฒŒ ๊ฐ ํ”„๋ ˆ์ž„์›Œํฌ๋ณ„๋กœ example project๋ฅผ ์ œ๊ณตํ•ด์ฃผ๊ณ ์žˆ์Šต๋‹ˆ๋‹ค. ์—ฌ๊ธฐ๊ฐ€ ์ œ์ผ ์ตœ์‹ ํ™”๊ฐ€ ๋น ๋ฅด๋”๋ผ๊ตฌ์š”. ์ผ๋‹จ Next.js App router + typescript๋ฅผ ํด๋ฆญํ•ฉ๋‹ˆ๋‹ค. (js๋ฅผ ์‚ฌ์šฉํ•˜๋ฉด js ํด๋ฆญ)

  5. ์•„๋ž˜ ์ฒ˜๋Ÿผ ํ”„๋กœ์ ํŠธ๊ฐ€ ๋‚˜์˜ต๋‹ˆ๋‹ค. ๋นจ๊ฐ„์ƒ‰ ๋„ค๋ชจ์— ์žˆ๋Š” ๋ช…๋ น์–ด๋ฅผ ํ„ฐ๋ฏธ๋„์— ์‹คํ–‰ํ•ด์„œ ํด๋ก ํ•ด ๋ด…์‹œ๋‹ค.

     curl https://codeload.github.com/mui/material-ui/tar.gz/master | tar -xz --strip=2  material-ui-master/examples/material-ui-nextjs-ts
     cd material-ui-nextjs-ts
    

  6. ํด๋ก ์„ ์™„๋ฃŒ ํ›„ ๋‚ด๊ฐ€ ์„ค์น˜ํ•˜๊ณ ์žˆ๋Š” ํ”„๋กœ์ ํŠธ์™€ ํ˜„์žฌ ๋ฐ›์€ MUI example ํ”„๋กœ์ ํŠธ๊ฐ€ ๋ญ๊ฐ€ ๋‹ค๋ฅธ์ง€ ๋น„๊ตํ•ด ๋ด…๋‹ˆ๋‹ค.

  7. ์‚ฌ์‹ค MUI example project๋ฅผ ๋ฐ”๋กœ ์จ๋„๋˜์ง€๋งŒ, ์–ด๋–ค ๋ถ€๋ถ„์ด ์ถ”๊ฐ€ ๋˜์—ˆ๋Š”์ง€ ์•Œ์•„๋ณด๋Š” ๊ณผ์ •๋„ ์žฌ๋ฐŒ์Šต๋‹ˆ๋‹ค. ์ข€ ๋” ์ž˜ ์ดํ•ดํ•œ๊ฒƒ๊ฐ™์€ ๋Š๋‚Œ์„ ๋ฐ›๋Š”๋‹ค๋ž„๊นŒ? ๊ธฐ๋ถ„ํƒ“์ผ ์ˆ˜๋„ ์žˆ์Šต๋‹ˆ๋‹ค.

  8. package.json๋ถ€ํ„ฐ ๋น„๊ตํ•˜๊ฒ ์Šต๋‹ˆ๋‹ค.

    • ์ง์ ‘ ์„ค์น˜

        // ์ง์—…์„ค์น˜ pack.json
      
        {
          "name": "nextv14",
          "version": "0.1.0",
          "private": true,
          "scripts": {
            "dev": "next dev",
            "build": "next build",
            "start": "next start",
            "lint": "next lint"
          },
          "dependencies": {
            "@emotion/react": "^11.11.4",
            "@emotion/styled": "^11.11.5",
            "@mui/material": "^5.15.19",
            "next": "14.2.3",
            "react": "^18",
            "react-dom": "^18"
          },
          "devDependencies": {
            "@types/node": "^20",
            "@types/react": "^18",
            "@types/react-dom": "^18",
            "eslint": "^8",
            "eslint-config-next": "14.2.3",
            "typescript": "^5"
          }
        }
      
    • MUI example

        //  mui example package.json
      
        {
          "name": "material-ui-nextjs-ts",
          "version": "5.0.0",
          "private": true,
          "scripts": {
            "dev": "next dev",
            "build": "next build",
            "start": "next start",
            "lint": "next lint",
            "post-update": "echo \"codesandbox preview only, need an update\" && pnpm update --latest"
          },
          "dependencies": {
            "@emotion/cache": "latest",
            "@emotion/react": "latest",
            "@emotion/styled": "latest",
            "@mui/icons-material": "latest",
            "@mui/material": "latest",
            "@mui/material-nextjs": "latest",
            "next": "latest",
            "react": "latest",
            "react-dom": "latest"
          },
          "devDependencies": {
            "@types/node": "latest",
            "@types/react": "latest",
            "@types/react-dom": "latest",
            "eslint": "latest",
            "eslint-config-next": "latest",
            "typescript": "latest"
          }
        }
      
  9. package.json์„ ๋น„๊ตํ•ด ๋ณด๋ฉด ๋ช‡๊ฐ€์ง€ ์ฐจ์ด์ ์ด ๋ณด์ž…๋‹ˆ๋‹ค. ์ฒซ๋ฒˆ์งธ๋กœ example package.json์—๋Š” ๋ฒ„์ „์ด ๋ชจ๋‘ latest๋กœ ์„ค์ •๋˜์–ด ์žˆ์Šต๋‹ˆ๋‹ค. clone๋ฐ›์„ ๋•Œ ๋งˆ๋‹ค ์ตœ์‹ ํ™”๋œ ๋ฒ„์ „์œผ๋กœ ๋ฐ›๊ฒŒํ•ด์ฃผ๊ฒ ๋‹ค๋Š” ์–˜๊ธฐ์ž…๋‹ˆ๋‹ค. ํ•˜์ง€๋งŒ ์‹ค์ œ ์ œํ’ˆ์— ์ €๋ ‡๊ฒŒ latest๋กœ ํ•ด๋†“์œผ๋ฉด ์–ด๋–ค ์žฌ๋ฐŒ๋Š” ์ผ์ด ์ผ์–ด๋‚ ์ง€ ๋ชจ๋ฅด๋‹ˆ ์ฃผ์˜ํ•ด์•ผํ•ฉ๋‹ˆ๋‹ค. ๋‘๋ฒˆ์งธ๋กœ๋Š” dependecies์— emotion/cache, mui/material-nextjs, mui/icons-material๊ฐ€ ๋” ์„ค์น˜๋˜์–ด์žˆ๋Š” ๊ฒƒ์„ ํ™•์ธํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค. mui/icons ์„ค์น˜์•ˆํ•ด๋„ ๋ฌด๋ฐฉํ•˜์ง€๋งŒ ์–ธ์  ๊ฐ€ ์“ธ์ผ์ด ์žˆ๊ธฐ์— ๊ฐ™์ด 3๊ฐ€์ง€ ๋ชจ๋‘ ์„ค์น˜ํ•ด ์ฃผ๊ฒ ์Šต๋‹ˆ๋‹ค. ์•„๋ž˜ ๋ช…๋ น์–ด๋ฅผ ํ„ฐ๋ฏธ๋„์— ์ž…๋ ฅํ•ฉ๋‹ˆ๋‹ค.

     yarn add @mui/material-nextjs @emotion/cache @mui/icons-material
    
  10. ์„ค์น˜๊ฐ€ ์™„๋ฃŒ๋˜์—ˆ๋‹ค๋ฉด ์ด์ œ src/app ๊ฒฝ๋กœ์—์žˆ๋Š” layout.tsx๋ฅผ ๋ด์•ผํ•ฉ๋‹ˆ๋‹ค. Next.js layout ํŒŒ์ผ๋กœ ์ƒํƒœ๋ฅผ ์œ ์ง€ํ•˜๊ฑฐ๋‚˜, ui๊ณต์œ , no-rerender๋“ฑ์„ ํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค. ์ž์„ธํ•œ ์‚ฌํ•ญ์€ ๋‚˜์ค‘์— ๋‹ค๋ฅธ ํฌ์ŠคํŠธ๋กœ ์ •๋ฆฌํ•˜๊ธฐ๋กœํ•˜๊ณ  ๊ณต์‹๋ฌธ์„œ ๋งํฌ๋‚จ๊ฒจ๋†“๊ฒ ์Šต๋‹ˆ๋‹ค. (Next.js layout)

  11. layout์„ ๋น„๊ตํ•ด๋ณด๊ฒ ์Šต๋‹ˆ๋‹ค.

    • ์ง์ ‘ ์„ค์น˜

        import type { Metadata } from "next";
        import { Inter } from "next/font/google";
        import "./globals.css";
      
        const inter = Inter({ subsets: ["latin"] });
      
        export const metadata: Metadata = {
          title: "Create Next App",
          description: "Generated by create next app",
        };
      
        export default function RootLayout({
          children,
        }: Readonly<{
          children: React.ReactNode;
        }>) {
          return (
            <html lang="en">
              <body className={inter.className}>{children}</body>
            </html>
          );
        }
      
    • MUI example

        import * as React from 'react';
        import { AppRouterCacheProvider } from '@mui/material-nextjs/v14-appRouter';
        import { ThemeProvider } from '@mui/material/styles';
        import CssBaseline from '@mui/material/CssBaseline';
        import theme from '@/theme';
      
        export default function RootLayout(props: { children: React.ReactNode }) {
          return (
            <html lang="en">
              <body>
                <AppRouterCacheProvider options={{ enableCssLayer: true }}>
                  <ThemeProvider theme={theme}>
                    {/* CssBaseline kickstart an elegant, consistent, and simple baseline to build upon. */}
                    <CssBaseline />
                    {props.children}
                  </ThemeProvider>
                </AppRouterCacheProvider>
              </body>
            </html>
          );
        }
      
  12. MUI example ํ”„๋กœ์ ํŠธ๋ฅผ ๋ณด๋ฉด bodyํƒœ๊ทธ์•„๋ž˜ AppRouterCacheProvider๊ฐ€ children์„ ๊ฐ์‹ธ๊ณ  ์žˆ๊ณ  ๊ทธ ์•„๋ž˜ ThemeProvider๊ฐ€ ๊ฐ์‹ธ์ ธ์žˆ์Šต๋‹ˆ๋‹ค. AppRouterCacheProvider ๋ฒ„์ „์€ v14๋กœ ๋˜์–ด์žˆ๋„ค์š”. AppRouterCacheProvider์˜ ์—ญํ• ์€ ๊ณต์‹๋ฌธ์„œ์— ๋‚˜์™€์žˆ์Šต๋‹ˆ๋‹ค.

    The AppRouterCacheProvider component is responsible for collecting the CSS generated by MUI System on the server, as Next.js is streaming chunks of the .html page to the client.

    While it's not required to use the AppRouterCacheProvider component, it's recommended to use it to ensure that the styles are appended to the <head> and not rendering in the <body>. See github.com/mui/material-ui/issues/26561#iss.. for why it's better.

    ์š”์•ฝํ•˜์ž๋ฉด Next.js๋Š” streaming SSR๋ฐฉ์‹์„ ์‚ฌ์šฉํ•ฉ๋‹ˆ๋‹ค. ์ด๋ฅผ ๋ณด์žฅํ•˜๊ธฐ์œ„ํ•ด AppRouterCacheProvider๋ฅผ ์‚ฌ์šฉํ•ด์•ผํ•˜๋Š”๊ฒƒ์ž…๋‹ˆ๋‹ค. ApprouterCacheProvider ์ปดํฌ๋„ŒํŠธ๋ฅผ ๋“ค์–ด๊ฐ€๋ฉด EmotionCache๋ฅผ ์‚ฌ์šฉํ•˜๊ณ ์žˆ๋Š” ๊ฒƒ์„ ํ™•์ธํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค. ์•„๊นŒ ์ถ”๊ฐ€๋กœ ์„ค์น˜ํ•œ EmotionCache๊ฐ€ ์—ฌ๊ธฐ์— ์“ฐ์ด๋Š” ๊ฒƒ์ž…๋‹ˆ๋‹ค. ApprouterCacheProvider๋ฅผ ์‚ฌ์šฉํ•˜๋ฉด headํƒœ๊ทธ์— mui style์„ ์ถ”๊ฐ€ํ•ด์ฃผ๊ธฐ ๋•Œ๋ฌธ์— ์„ฑ๋Šฅ์ƒ ์ด์ ๋„ ์žˆ๋‹ค๊ณ  ํ•ฉ๋‹ˆ๋‹ค. options์†์„ฑ์€ ๊ณต์‹๋ฌธ์„œ Go!

    
    import * as React from 'react';
    import { EmotionCache, Options as OptionsOfCreateCache } from '@emotion/cache';
    export type AppRouterCacheProviderProps = {
        /**
         * These are the options passed to createCache() from 'import createCache from "@emotion/cache"'.
         */
        options?: Partial<OptionsOfCreateCache> & {
            /**
             * If `true`, the generated styles are wrapped within `@layer mui`.
             * This is useful if you want to override the Material UI's generated styles with different styling solution, like Tailwind CSS, plain CSS etc.
             */
            enableCssLayer?: boolean;
        };
        /**
         * By default <CacheProvider /> from 'import { CacheProvider } from "@emotion/react"'.
         */
        CacheProvider?: React.ElementType<{
            value: EmotionCache;
        }>;
        children: React.ReactNode;
    };
    /**
     * Emotion works OK without this provider but it's recommended to use this provider to improve performance.
     * Without it, Emotion will generate a new <style> tag during SSR for every component.
     * See https://github.com/mui/material-ui/issues/26561#issuecomment-855286153 for why it's a problem.
     */
    export default function AppRouterCacheProvider(props: AppRouterCacheProviderProps): React.JSX.Element;
    
  13. AppRouterCacheProvider์— ๋Œ€ํ•ด ์•Œ์•„๋ดค์œผ๋‹ˆ ThemeProvider์™€ CssBaseline์— ๋Œ€ํ•ด์„œ ์•Œ์•„๋ณด๊ฒ ์Šต๋‹ˆ๋‹ค. ThemeProvider MUI ๋””์ž์ธ ์‹œ์Šคํ…œ์„ ์‚ฌ์šฉํ•˜๊ฑฐ๋‚˜ ์ปค์Šคํ…€ ๋””์ž์ธ ์‹œ์Šคํ…œ์„ ์‚ฌ์šฉํ•  ThemeํŒŒ์ผ์„ ์ฃผ์ž…ํ•˜๊ธฐ์œ„ํ•ด ์‚ฌ์šฉํ•ฉ๋‹ˆ๋‹ค. props๋กœ ์ „๋‹ฌ๋œ themeํŒŒ์ผ์ž…๋‹ˆ๋‹ค. ์ง์ ‘์„ค์น˜ํ•˜๋Š” ํ”„๋กœ์ ํŠธ์— themeํŒŒ์ผ์„ ๋งŒ๋“ค์–ด ์ค์‹œ๋‹ค. ์ €๋Š” src์— shared/provider/mui ๊ฒฝ๋กœ์— ์ƒ์„ฑํ•˜๊ฒ ์Šต๋‹ˆ๋‹ค. ํด๋” ๊ตฌ์กฐ๋Š” ์ „ํ†ต์ ์ธ ํด๋”๊ตฌ์กฐ ํ˜น์€ FSD๋ฅผ ์ฃผ๋กœ ์‚ฌ์šฉํ•˜๋Š”๋ฐ ์ด๊ฑด ์‹œ๊ฐ„๋˜๋ฉด ์ •๋ฆฌํ•˜๊ฒ ์Šต๋‹ˆ๋‹ค.

    'use client';
    import { Roboto } from 'next/font/google';
    import { createTheme } from '@mui/material/styles';
    
    const roboto = Roboto({
      weight: ['300', '400', '500', '700'],
      subsets: ['latin'],
      display: 'swap',
    });
    
    const theme = createTheme({
      palette: {
        mode: 'light',
      },
      typography: {
        fontFamily: roboto.style.fontFamily,
      },
      components: {
        MuiAlert: {
          styleOverrides: {
            root: ({ ownerState }) => ({
              ...(ownerState.severity === 'info' && {
                backgroundColor: '#60a5fa',
              }),
            }),
          },
        },
      },
    });
    
    export default theme;
    
  14. CssBaseline์€ html์— ๊ธฐ๋ณธ์œผ๋กœ ์ ์šฉ๋œ css๋ฅผ ๋ฆฌ์…‹์…‹ํŒ…ํ•ด์ฃผ๋Š” ์—ญํ• ์ด๋ผ๊ณ  ๋ณด์‹œ๋ฉด๋ฉ๋‹ˆ๋‹ค. (CssBaseline๊ณต์‹๋ฌธ์„œ)

  15. ์ด์ œ ๋‹ค ์•Œ์•„๋ดค์œผ๋‹ˆ layout.tsxํŒŒ์ผ์„ ์ง์ ‘์„ค์น˜ ํ”„๋กœ์ ํŠธ์— ๋ฐ”๊ฟ”๋ณด๊ฒ ์Šต๋‹ˆ๋‹ค.

    import * as React from "react";
    import { AppRouterCacheProvider } from "@mui/material-nextjs/v14-appRouter";
    import { ThemeProvider } from "@mui/material/styles";
    import CssBaseline from "@mui/material/CssBaseline";
    import theme from "@/shared/provider/mui/theme";
    
    export default function RootLayout(props: { children: React.ReactNode }) {
      return (
        <html lang="en">
          <body>
            <AppRouterCacheProvider options={{ enableCssLayer: true }}>
              <ThemeProvider theme={theme}>
                {/* CssBaseline kickstart an elegant, consistent, and simple baseline to build upon. */}
                <CssBaseline />
                {props.children}
              </ThemeProvider>
            </AppRouterCacheProvider>
          </body>
        </html>
      );
    }
    
  16. MUI๊ฐ€ ์ž˜ ์ ์šฉ๋๋Š”์ง€ ํ™•์ธํ•˜๊ธฐ์œ„ํ•ด app/page.tsx๋„ ๋ณ€๊ฒฝํ•ด๋ณด๊ฒ ์Šต๋‹ˆ๋‹ค.

    import * as React from "react";
    import Container from "@mui/material/Container";
    import Typography from "@mui/material/Typography";
    import Box from "@mui/material/Box";
    import Link from "@mui/material/Link";
    import NextLink from "next/link";
    // import ProTip from '@/components/ProTip';
    // import Copyright from '@/components/Copyright';
    
    export default function Home() {
      return (
        <Container maxWidth="lg">
          <Box
            sx={{
              my: 4,
              display: "flex",
              flexDirection: "column",
              justifyContent: "center",
              alignItems: "center",
            }}
          >
            <Typography variant="h4" component="h1" sx={{ mb: 2 }}>
              Material UI - Next.js App Router example in TypeScript
            </Typography>
            <Link href="/about" color="secondary" component={NextLink}>
              Go to the about page
            </Link>
            {/* <ProTip />
            <Copyright /> */}
          </Box>
        </Container>
      );
    }
    
  17. yarn dev๋กœ ์‹คํ–‰ ํ›„ ํ™ˆํŽ˜์ด์ง€์— ์•„๋ž˜ ํ™”๋ฉด์ด ๋‚˜์˜ค๋ฉด MUI ์…‹ํŒ… ์™„๋ฃŒ์ž…๋‹ˆ๋‹ค.

  18. ์—ฌ๊ธฐ๊นŒ์ง€ ์ฝ๋Š๋ผ ๊ณ ์ƒํ•˜์…จ์Šต๋‹ˆ๋‹ค ใ…Žใ…Ž ๋‹ค์Œ ํฌ์ŠคํŠธ๋Š” ํ”„๋กœ์ ํŠธ ํด๋”๊ตฌ์กฐ์— ๋Œ€ํ•ด์„œ ์ž‘์„ฑํ•ด ๋ณด๊ฒ ์Šต๋‹ˆ๋‹ค.

ย