NextJS๐Ÿ’ก

  • ๋ฆฌ์•กํŠธ ํ”„๋ ˆ์ž„์›Œํฌ
  • ์„œ๋ฒ„์‚ฌ์ด๋“œ๋ Œ๋”๋ง ์ง€์›
    • ๋ Œ๋”๋ง๋ฐฉ์‹์˜ ์ตœ์ ํ™”
    • ์‚ฌ์ „๋ Œ๋”๋ง ์ง€์›(build์‹œ์— ๋ฏธ๋ฆฌ ํŠน์ • ํŽ˜์ด์ง€์— ๋Œ€ํ•œ HTML์ƒ์„ฑ)
    • ๋น ๋ฅธ ๋กœ๋”ฉ์†๋„
  • ๋™์ ๋ผ์šฐํŒ…์„ ์‰ฝ๊ฒŒ ์‚ฌ์šฉํ•  ์ˆ˜ ์žˆ๋„๋ก ์ง€์›
    • react-router-dom์—†์ด ์ž๋™์œผ๋กœ route ๊ตฌ์„ฑ
  • api์„œ๋ฒ„ ๊ตฌ์ถ• ์ง€์›
  • ์ด๋ฏธ์ง€, ํฐํŠธ ์ตœ์ ํ™”
    • ๋ ˆ์ด์•„์›ƒ ์‰ฌํ”„ํŠธ๊ฐ€ ์ผ์–ด๋‚˜์ง€ ์•Š๋„๋ก ๊ตฌ์„ฑ
    • layout shift- ์ด๋ฏธ์ง€/์ปดํฌ๋„ŒํŠธ๊ฐ€ ๋’ค๋Šฆ๊ฒŒ ๋กœ๋“œ๋˜์–ด ๋ ˆ์ด์•„์›ƒ์ด ๋ฐ€๋ฆฌ๋Š”(๋ณ€๊ฒฝ๋˜๋Š”) ํ˜„์ƒ
  • ํƒ€์ž…์Šคํฌ๋ฆฝํŠธ ์ง€์›

๐Ÿงก๋ Œ๋”๋ง๋ฐฉ์‹

โœจCSR(Client Side Rendering)

  • ๊ธฐ๋ณธ์ ์œผ๋กœ ๋ฆฌ์•กํŠธ์™€ ๊ฐ™์€ SPA(single page application)์€ CSR(Client Side Rendering)๋ฐฉ์‹์œผ๋กœ ํŽ˜์ด์ง€ ๊ตฌํ˜„
  • ์‚ฌ์šฉ์ž์—๊ฒŒ ์ดˆ๊ธฐ์— ๋นˆ HTML์„ ๋„˜๊ฒจ์ฃผ๊ณ  ์‚ฌ์šฉ์ž์˜ ๋ธŒ๋ผ์šฐ์ €๊ฐ€ ์ž๋ฐ”์Šคํฌ๋ฆฝํŠธ๋ฅผ ์ฝ์–ด๋‚ด๋ ค๊ฐ€๋ฉฐ ์š”์†Œ๋ฅผ์„ ์ฑ„์šฐ๊ฒŒ ๋จ

[์žฅ์ ]

  • ํŽ˜์ด์ง€ ์ „ํ™˜์ด ๋น ๋ฆ„
  • ์ƒˆ๋กœ๊ณ ์นจ ์‹œ ํ™”๋ฉด์ด ๊นœ๋นก์ด๋Š” Blinking lssue๊ฐ€ ๋ฐœ์ƒํ•˜์ง€ ์•Š์Œ
  • ์„œ๋ฒ„์— ๋ถ€๋‹ด์ด ์ ์Œ

[๋‹จ์ ]

  • ์‚ฌ์šฉ์ž๊ฐ€ ์ฒซ ํ™”๋ฉด์„ ๋ณด๊ธฐ๊นŒ์ง€์˜ ์ดˆ๊ธฐ ๋กœ๋”ฉ ์‹œ๊ฐ„์ด ์˜ค๋ž˜๊ฑธ๋ฆผ
    (์ž๋ฐ”์Šคํฌ๋ฆฝํŠธ๊ฐ€ ์‹คํ–‰๋˜๊ธฐ ์ „๊นŒ์ง€ ์‚ฌ์šฉ์ž๋Š” ๋นˆํ™”๋ฉด์„ ๋ด์•ผํ•จ)
  • html์€ ๊ธฐ๋ณธ์ ์œผ๋กœ ๋น„์–ด์žˆ๋Š” ์ƒํƒœ์ด๊ธฐ๋•Œ๋ฌธ์— ๊ฒ€์ƒ‰์—”์ง„์— ํŽ˜์ด์ง€์˜ ์ •๋ณด๊ฐ€ ์›ํ™œํ•˜๊ฒŒ ๊ณต๊ธ‰๋˜์ง€ ์•Š์Œ

โœจSSR(Server Side Rendering)

  • ์‚ฌ์šฉ์ž๊ฐ€ ์‚ฌ์ดํŠธ์— ์ ‘์†ํ•˜๋Š” ์ˆœ๊ฐ„ ์„œ๋ฒ„๋Š” HTML ์š”์†Œ๋ฅผ ์ฑ„์šด ๋’ค ํ•„์š”ํ•œ ์ž๋ฐ”์Šคํฌ๋ฆฝํŠธ ์ฝ”๋“œ์™€ ํ•จ๊ป˜ ์‚ฌ์šฉ์ž์—๊ฒŒ ๊ณต๊ธ‰

[์žฅ์ ]

  • ํŽ˜์ด์ง€ ์ดˆ๊ธฐ ๋กœ๋”ฉ์ด ๋น ๋ฆ„
  • ๊ฒ€์ƒ‰์—”์ง„ ์ตœ์ ํ™”

[๋‹จ์ ]

  • ์„œ๋ฒ„๊ฐ€ ์ง์ ‘ ์š”์†Œ๋ฅผ ๊ทธ๋ ค์„œ ์‚ฌ์šฉ์ž์—๊ฒŒ ๊ณต๊ธ‰ํ•ด์•ผํ•˜๊ธฐ ๋•Œ๋ฌธ์— ์„œ๋ฒ„์— ๋ถ€ํ•˜๊ฐ€ ๊ฑธ๋ฆผ
  • ์‚ฌ์šฉ์ž๊ฐ€ ์‚ฌ์ดํŠธ๋ฅผ ๋ณผ ์ˆ˜ ์žˆ๋Š” ์ˆœ๊ฐ„๊ณผ ํ•ด๋‹น ์‚ฌ์ดํŠธ์™€ ์ธํ„ฐ๋ ‰์…˜์ด ๊ฐ€๋Šฅํ•œ ์ˆœ๊ฐ„ ์‚ฌ์ด์˜ ์‹œ๊ฐ„์ฐจ๊ฐ€ ๋ฐœ์ƒ
    (์š”์†Œ๊ฐ€ ๊ทธ๋ ค์ง„ html์„ ๋น ๋ฅด๊ฒŒ ๋ฐ›์•˜์ง€๋งŒ, ์ž๋ฐ”์Šคํฌ๋ฆฝํŠธ๊ฐ€ ์‹คํ–‰๋˜๊ธฐ ๊นŒ์ง€๋Š” ์‹œ๊ฐ„์ด ๊ฑธ๋ฆผ)
  • ์ƒˆ๋กœ๊ณ ์นจ ์‹œ ๋‹ค์‹œ ํŽ˜์ด์ง€๋ฅผ ์„œ๋ฒ„์—์„œ ๋ฐ›์•„์™€์•ผํ•จ => ํ™”๋ฉด์ด ์ž ์‹œ ๋น„์›Œ์กŒ๋‹ค๊ฐ€ ๋‹ค์‹œ ์ฑ„์›Œ์ง€๋Š” Blinking Issue๋ฐœ์ƒ(ํ™”๋ฉด ๊นœ๋นก์ž„ ๋ฐœ์ƒ)

โœจSSG(Static Site Generation): ์ •์  ์ƒ์„ฑ

  • Next.js์˜ ํ•ต์‹ฌ๊ธฐ๋Šฅ ์ค‘ ํ•˜๋‚˜
  • ์‚ฌ์šฉ์ž๊ฐ€ ์ ‘์†ํ•  ๋•Œ ๋งˆ๋‹ค ์„œ๋ฒ„๊ฐ€ ์ง์ ‘ HTML์„ ๊ทธ๋ฆฌ๋Š” ๊ฒƒ์ด ์•„๋‹Œ, build ํ•  ๋•Œ HTML์„ ๋ฏธ๋ฆฌ ๊ทธ๋ฆฐ ๋’ค ์ €์žฅํ•ด ๋‘์—ˆ๋‹ค๊ฐ€ ์‚ฌ์šฉ์ž ์ ‘์† ์‹œ ๋น ๋ฅด๊ฒŒ ๊ณต๊ธ‰
  • ์™ธ๋ถ€์—์„œ ๋ฐ์ดํ„ฐ๋ฅผ ๊ฐ€์ ธ์˜ค์ง€ ์•Š๋Š” ํ•œ ํ•ญ์ƒ(๊ธฐ๋ณธ์ ์œผ๋กœ) SSG๋ฐฉ์‹์œผ๋กœ ์‚ฌ์ „๋ Œ๋”๋ง์„ ์ง„ํ–‰

[์žฅ์ ]

  • CSR๋ฐฉ์‹๊ณผ SSR๋ฐฉ์‹์˜ ์žฅ์ ์„ ํก์ˆ˜
  • build์‹œ์— HTML๋ Œ๋”๋ง์„ ๋ฏธ๋ฆฌ ์ง„ํ–‰ํ–ˆ๊ธฐ ๋•Œ๋ฌธ์— ์ดˆ๊ธฐ ๋กœ๋”ฉ์‹œ๊ฐ„๊ณผ ํŽ˜์ด์ง€์ „ํ™˜์ด ๋ชจ๋‘ ๋น ๋ฆ„
  • build์‹œ์— ์ž‘์„ฑ๋œ HTML๋ฌธ์„œ ๋•๋ถ„์— ๊ธฐ์กด React์˜ CSR๋ฐฉ์‹๊ณผ ๋‹ฌ๋ฆฌ ๊ฒ€์ƒ‰์—”์ง„์— ์ตœ์ ํ™”๋˜์–ด์žˆ๋‹ค

[๋‹จ์ ]

  • build ์‹œ๊ฐ„์ด ์˜ค๋ž˜๊ฑธ๋ฆผ
  • build์‹œ์— ์‚ฌ์ „ ๋ Œ๋”๋ง ๋œ ๋ฐ์ดํ„ฐ๋ฅผ ์‚ฌ์šฉ์ž์—๊ฒŒ ๊ณต๊ธ‰ํ•˜๊ธฐ ๋•Œ๋ฌธ์—, ์‚ฌ์šฉ์ž๊ฐ€ ์˜ค๋ž˜ ๋œ ๋ฐ์ดํ„ฐ๋ฅผ ๋ณด๊ฒŒ ๋  ์ˆ˜ ์žˆ์Œ (์—…๋ฐ์ดํŠธ๊ฐ€ ์ž์ฃผ ๋ฐœ์ƒํ•˜๋Š” ๋ฐ์ดํ„ฐ๋ผ๋ฉด, SSG๋ณด๋‹ค๋Š” SSR๋ฐฉ์‹์œผ๋กœ ๋ Œ๋”๋ง ํ•˜๋„๋ก ํ•˜๋Š”๊ฒƒ์ด ์ข‹์Œ)

๐Ÿ’กnext.js์•ฑ์„ yarn dev (npm run start)๋กœ ์‹คํ–‰ํ•˜๋Š” ๊ฒฝ์šฐ ์•„์ง build ๋˜์ง€ ์•Š์€ ์ƒํƒœ์˜ ์•ฑ์ด๊ธฐ๋•Œ๋ฌธ์— SSG๋กœ ๊ตฌํ˜„ ๋œ ์ฝ”๋“œ๋ผ๋„ SSR๋ฐฉ์‹์œผ๋กœ ์ž‘๋™


๐Ÿงก์‚ฌ์šฉํ•ด๋ณด๊ธฐ

  • $ yarn create next-app (์•ฑ์ด๋ฆ„) --js

  • ํ”„๋กœ์ ํŠธ ์ƒ์„ฑ ์‹œ import alias๊ฐ€ ๊ธฐ๋ณธ์ ์œผ๋กœ ์„ค์ •๋˜์–ด, import ๊ฒฝ๋กœ๋กœ @(๊ธฐ๋ณธ๊ฐ’)๋ฅผ ์‚ฌ์šฉํ•  ์ˆ˜ ์žˆ๋‹ค. ( @๋Š” src/ ํด๋”๋ฅผ ๋œปํ•จ )

  • yarn dev๋กœ ํ”„๋กœ์ ํŠธ๋ฅผ ์‹คํ–‰์‹œํ‚ค๋ฉด src/pagesํด๋” ๋‚ด๋ถ€์˜ index.js๊ฐ€ ์‹คํ–‰ ๋จ

  • index.js ๋Š” ์‹คํ–‰ ์‹œ _app.js์™€ _document.js๋ฅผ ์ฐจ๋ก€๋กœ ๊ฑฐ์นœ ๋’ค ์‹คํ–‰

    • _app.js
      • ๊ธ€๋กœ๋ฒŒ ์Šคํƒ€์ผ ํ˜น์€ ์•ฑ์— ๊ณตํ†ต์ ์œผ๋กœ ์‚ฌ์šฉํ•˜๊ณ ์ž ํ•˜๋Š” ๋กœ์ง ๋“ฑ์„ ์ž‘์„ฑ
    • _document.js
      • ์ „์ฒด ํŽ˜์ด์ง€์— ๊ฑธ์ณ์„œ ์ ์šฉํ•˜๊ณ ์ž ํ•˜๋Š” Head ํƒœ๊ทธ (meta ํƒœ๊ทธ, title ํƒœ๊ทธ) ๋ฅผ ์ž‘์„ฑํ•˜๊ฑฐ๋‚˜, ์›น์‚ฌ์ดํŠธ์˜ ๋งˆํฌ์—… ๊ตฌ์กฐ๋ฅผ ์„ค์ •

๐Ÿงกpages ํด๋”๋ฅผ ํ†ตํ•œ ๋ผ์šฐํŒ…

  • pages ํด๋”์•ˆ์— ํŒŒ์ผ์ด๋‚˜ ํด๋”๋ฅผ ๋งŒ๋“ค๋ฉด, ํ•ด๋‹น ํŒŒ์ผ/ํด๋”์˜ ์ด๋ฆ„์„ ๋”ฐ๋ผ route๊ฐ€ ์ž๋™์ƒ์„ฑ
ํด๋”๊ฒฝ๋กœRoute๋น„๊ณ 
pages/post/index.jsx/postindexํŒŒ์ผ์€ ํ•ด๋‹นํŒŒ์ผ์˜ ํด๋”๋ช…์œผ๋กœ ๊ฒฝ๋กœ๊ฐ€ ์—ฐ๊ฒฐ๋จ
pages/post/detail.jsx/post/detailindex.jsx๋ฅผ ์ œ์™ธํ•œ ํŒŒ์ผ๋ช…์€ ํด๋”๋ช…์˜ ๋ฃจํŠธ ๋’ค์— ์—ฐ๊ฒฐ ๋จ
pages/post/[id].jsx/post/${id}- ๋™์ ์œผ๋กœ ๊ฒฝ๋กœ ์ƒ์„ฑ
- useRouter() ๋ฅผ ์‚ฌ์šฉํ•ด์„œ ๊ฒฝ๋กœ์˜ id๊ฐ’์„ ๊ฐ€์ ธ์˜ฌ ์ˆ˜ ์žˆ์Œ
pages/post/[โ€ฆparams].jsx/post/*/*/*- ๋™์ ์œผ๋กœ ๊ฒฝ๋กœ ์ƒ์„ฑ
- ๊ฒฝ๋กœ์˜ ๊นŠ์ด์™€ ์ƒ๊ด€์—†์ด ๋ชจ๋“  ๊ฒฝ๋กœ๋กœ ์ ‘๊ทผ ๊ฐ€๋Šฅ
- useRouter()๋ฅผ ํ†ตํ•ด ๊ฐ ๊ฒฝ๋กœ๋ฅผ ๋ฐฐ์—ดํ˜•ํƒœ๋กœ ๋ฐ›์•„์˜ฌ ์ˆ˜ ์žˆ์Œ

๊ฒฝ๋กœ ์šฐ์„ ์ˆœ์œ„
detail(๊ฒฝ๋กœ๋ช… ์ง์ ‘๋ช…์‹œ) > [id](ํ•˜๋‚˜์˜ ์†์„ฑ ๋ช…์‹œ) > [...params](๋‚˜๋จธ์ง€ ๊ฒฝ๋กœ)

๐ŸงกuseRouter()

  • ๊ฒฝ๋กœ์™€ ๊ด€๋ จํ•œ ๋ฐ์ดํ„ฐ ๋˜๋Š” ํ•จ์ˆ˜๋ฅผ ๋‹ด๊ณ ์žˆ๋Š” ๊ฐ์ฒด๋ฅผ ๋ฐ˜ํ™˜
  import { useRouter } from 'next/router'

  const router = useRouter()

โœจrouter.query

  • ๊ฒฝ๋กœ์— ๋‹ด๊ธด ๋ฐ์ดํ„ฐ๋Š” ์•„๋ž˜์™€ ๊ฐ™์ด ๊บผ๋‚ด ์‚ฌ์šฉ
// src/pages/post/[id].jsx
// ์ ‘๊ทผ๊ฒฝ๋กœ '/post/1?name=hello'
import { useRouter } from 'next/router'

const router = useRouter()
const {id, name} = router.query

console.log(id) //'1'
console.log(name) //'hello'
  • [...params].jsx ๋ฅผ ํ™œ์šฉํ•ด ๋งŒ๋“  ๊นŠ์ด๊ฐ€ ์žˆ๋Š” route์— ์ ‘๊ทผํ•˜๋Š” ๊ฒฝ์šฐ ํ•ด๋‹น ๊ฒฝ๋กœ๋ฅผ ๋ฐฐ์—ดํ˜•์‹์œผ๋กœ ๋ฐ˜ํ™˜
// src/pages/user/[...params].jsx
// ์ ‘๊ทผ๊ฒฝ๋กœ '/user/123/abc'
import { useRouter } from 'next/router'

const router = useRouter()
const { params } = router.query

console.log(params) //['123', 'abc']

โœจrouter.push()

  • <Link>ํƒœ๊ทธ๋ฅผ ์‚ฌ์šฉํ•˜์ง€ ์•Š๊ณ  ํ•จ์ˆ˜๋‚ด์—์„œ ํŽ˜์ด์ง€ ์ „ํ™˜์„ ํ•˜๊ณ  ์‹ถ๋‹ค๋ฉด router.push()์‚ฌ์šฉ
router.push(url)
  • ๊ฒฝ๋กœ๋ฅผ ์ž…๋ ฅํ•˜๊ฑฐ๋‚˜, url๊ฐ์ฒด๋ฅผ ์‚ฌ์šฉํ•  ์ˆ˜๋„ ์žˆ์Œ
<button onClick={() => router.push('/post/1')}>1๋ฒˆ ๊ธ€๋กœ๊ฐ€๊ธฐ</button>

<button onClick={() => router.push({ pathname: '/post/[id]', query: { id: 1 } })}>1๋ฒˆ ๊ธ€๋กœ๊ฐ€๊ธฐ</button>

โœจrouter.isReady

  • ์ •์  ์ตœ์ ํ™”๊ฐ€ ์ด๋ฃจ์–ด์ง„ ํŽ˜์ด์ง€(์™ธ๋ถ€๋ฐ์ดํ„ฐ๋ฅผ ๋ฐ›์•„ getStaticProps() ๋ฅผ ์‚ฌ์šฉํ•œ ํŽ˜์ด์ง€)์˜ ๊ฒฝ์šฐ query๊ฐ€ ๋นˆ ๊ฐ์ฒด๋กœ ์ „๋‹ฌ ๋œ ๋’ค์— ์ฑ„์›Œ์ง
  • ๊ทธ๋Ÿฐ ๊ฒฝ์šฐ router.isReady๋ฅผ ์‚ฌ์šฉํ•˜์—ฌ, query๊ฐ€ ์ค€๋น„ ๋˜๋ฉด ๊ทธ๋•Œ query ๋ฐ์ดํ„ฐ์— ์ ‘๊ทผ ํ•˜๋„๋ก ํ•จ
  • boolean๊ฐ’ ๋ฐ˜ํ™˜
useEffect(() => {
  if (router.isReady) console.log(router.query)
}, [router.isReady])

๐ŸงกLinkํƒœ๊ทธ

  • ํŽ˜์ด์ง€ ๊ฐ„ ์ด๋™์‹œ์—๋Š” <a>ํƒœ๊ทธ ๋Œ€์‹  <Link>ํƒœ๊ทธ๋ฅผ ์‚ฌ์šฉํ•ด์•ผ ํ•จ
import Link from "next/link";
// ๊ธฐ๋ณธ๋ฐฉ์‹
<Link href='/post'>post๋กœ ์ด๋™</Link>

// ์ฟผ๋ฆฌํŒŒ๋ผ๋ฏธํ„ฐ
<Link href='/post?name=hello'>post๋กœ ์ด๋™</Link>

// url๊ฐ์ฒด
<Link href={{ pathname: '/post', query: { name: 'hello' } }}>post๋กœ ์ด๋™</Link>
  • Linkํƒœ๊ทธ๋Š” ํ•ด๋‹น ํƒœ๊ทธ๊ฐ€ Viewport์•ˆ์— ๋“ค์–ด์˜ค๋ฉด ์ž๋™์œผ๋กœ ํ•ด๋‹น ํŽ˜์ด์ง€๋ฅผ ๋ฏธ๋ฆฌ ๋กœ๋“œํ•จ(prefetch)
    • ์ •์ ์ƒ์„ฑ๋ฐฉ์‹(SSG)์˜ ๊ฒฝ์šฐ์— ํ•œํ•จ
    • SSR๋ฐฉ์‹์˜ ๊ฒฝ์šฐ์—” Linkํƒœ๊ทธ๋ฅผ ํด๋ฆญํ•ด์•ผ๋งŒ ํŽ˜์ด์ง€๊ฐ€ ๋กœ๋“œ ๋จ

๐ŸงกImageํƒœ๊ทธ

  • ์„ฑ๋Šฅ์ตœ์ ํ™”๋ฅผ ์œ„ํ•ด ๊ธฐ์กด์˜ <img />ํƒœ๊ทธ๊ฐ€ ์•„๋‹Œ <Image /> ํƒœ๊ทธ๋ฅผ ์‚ฌ์šฉ

  • Imageํƒœ๊ทธ ํŠน์ง•

    • ์ž๋™์œผ๋กœ ํฌ๊ธฐ๋ฅผ viewport์— ๋งž์ถฐ์คŒ
    • ์‚ฌ์šฉ์ž์—๊ฒŒ ๋ณด์—ฌ์งˆ ๋•Œ(viewport์— ์ง„์ž…ํ–ˆ์„ ๋•Œ)์—๋งŒ ์ด๋ฏธ์ง€๋ฅผ ๋กœ๋“œํ•˜์—ฌ ์ดˆ๊ธฐ ๋กœ๋”ฉ์‹œ๊ฐ„์„ ๋‹จ์ถ•
    • ๋ ˆ์ด์•„์›ƒ ์ด๋™ ์ตœ์†Œํ™” (Cumulative Layout Shift ๋ฐฉ์ง€)
    • ๋””๋ฐ”์ด์Šค์˜ ์ข…๋ฅ˜์— ๋”ฐ๋ผ ํŒŒ์ผ ํฌ๊ธฐ๋ฅผ ์กฐ์ ˆํ•˜์—ฌ ๊ณต๊ธ‰
import Image from 'next/image';

<Image
  src="/images/profile.jpg" // ์ด๋ฏธ์ง€ ์ฃผ์†Œ, ๊ฒฝ๋กœ
  height={200} // CLS ๋ฐฉ์ง€๋ฅผ ์œ„ํ•ด ์‚ฌ์šฉ๋จ
  width={200} // CLS ๋ฐฉ์ง€๋ฅผ ์œ„ํ•ด ์‚ฌ์šฉ๋จ
  alt="profile"
/>
  • width์™€ height๋ฅผ ์ง์ ‘ ๋ช…์‹œํ•ด์ฃผ์–ด์•ผ ํ•จ
    (์ด๋ฏธ์ง€๊ฐ€ ๋กœ๋“œ๋˜๊ธฐ ์ „ ํ•ด๋‹น ์ด๋ฏธ์ง€์˜ ํฌ๊ธฐ๋ฅผ ๋จผ์ € ๊ณ„์‚ฐํ•˜์—ฌ ์ž๋ฆฌ๋ฅผ ๋งˆ๋ จ => Layout Shift ๋ฐฉ์ง€)

โœจfill

  • ์ด๋ฏธ์ง€ ํฌ๊ธฐ๋ฅผ ๋ชจ๋ฅด๋Š” ๊ฒฝ์šฐ fill์†์„ฑ์„ ์‚ฌ์šฉํ•˜๋ฉด ๋ถ€๋ชจ์š”์†Œ์˜ ํฌ๊ธฐ์— ๋งž์ถฐ์ง
  • fill ์†์„ฑ์„ ์‚ฌ์šฉํ•˜๋ ค๋ฉด ๋ถ€๋ชจ์š”์†Œ๋Š” position: relative๋ฐ display: block์†์„ฑ์„ ๊ฐ€์ง€๊ณ  ์žˆ์–ด์•ผ ํ•จ
<Image src="/images/profile.jpg" alt="profile" fill />

โœจsizes

  • ๊ธฐ๋ณธ์ ์œผ๋กœ Imageํƒœ๊ทธ๋ฅผ ์‚ฌ์šฉํ•˜๋ฉด ํŽ˜์ด์ง€์— ์ ‘์†ํ•˜๋Š” ๋””๋ฐ”์ด์Šค์˜ ํ™”๋ฉด ๋„ˆ๋น„์— ๋”ฐ๋ผ ์ด๋ฏธ์ง€์˜ ํฌ๊ธฐ๋ฅผ ๋‹ค๋ฅด๊ฒŒ ์กฐ์ ˆํ•˜์—ฌ ๊ณต๊ธ‰
  • sizes์†์„ฑ์„ ์‚ฌ์šฉํ•˜๋ฉด ๋””๋ฐ”์ด์Šค ํฌ๊ธฐ ๋ณ„ ์ด๋ฏธ์ง€์˜ ์‚ฌ์ด์ฆˆ๋ฅผ ์ง์ ‘ ์ง€์ •ํ•ด ์ค„ ์ˆ˜ ์žˆ์Œ
<Image src={dogImage} alt="dog" fill sizes="(max-width: 768px) 33vw, (max-width: 1200px) 50vw, 100vw"/>7

/* ํ™”๋ฉด ํฌ๊ธฐ๊ฐ€ 768px ๋ณด๋‹ค ์ž‘๋‹ค๋ฉด ๋ทฐํฌํŠธ ๊ฐ€๋กœ๊ธธ์ด์˜ 33% ์ •๋„์˜ ํฌ๊ธฐ๋ฅผ ๊ฐ€์ง„ ์ด๋ฏธ์ง€๋ฅผ ๊ณต๊ธ‰ํ•ด์ฃผ๊ณ , 
1200px ๋ณด๋‹ค ์ž‘๋‹ค๋ฉด ๋ทฐํฌํŠธ ๊ฐ€๋กœ๊ธธ์ด์˜ 50% ์ •๋„์˜ ํฌ๊ธฐ๋ฅผ ๊ฐ€์ง„ ์ด๋ฏธ์ง€๋ฅผ ๊ณต๊ธ‰ํ•ด์ฃผ๊ณ , 
๊ทธ๊ฒƒ๋ณด๋‹ค ํฌ๋‹ค๋ฉด 100% ํฌ๊ธฐ๋ฅผ ๊ฐ€์ง„ ์ด๋ฏธ์ง€๋ฅผ ๊ณต๊ธ‰ํ•˜๋„๋ก ์„ค์ •*/
  • ์ด๋ฏธ์ง€๋ฅผ ์™ธ๋ถ€์—์„œ ๊ฐ€์ ธ์˜ค๋Š” ๊ฒฝ์šฐ next.config.js์„ค์ •์„ ์ˆ˜์ •ํ•ด์ฃผ์–ด์•ผ ํ•จ
  • ์ด๋ฏธ์ง€๋ฅผ ๋ถˆ๋Ÿฌ ์˜ฌ ๋„๋ฉ”์ธ์„ ์ถ”๊ฐ€ํ•˜์—ฌ ํ—ˆ์šฉ
//next.config.js
images: {
    remotePatterns: [
      {
        protocol: 'https',
        hostname: 'via.placeholder.com', // ์ „๋ถ€ ํ—ˆ์šฉํ•˜๊ณ ์ž ํ•  ๊ฒฝ์šฐ์—๋Š” **
        port: '',
        pathname: '**', // ์ „๋ถ€ ํ—ˆ์šฉํ•˜๊ณ ์ž ํ•  ๊ฒฝ์šฐ์—๋Š” **
      },
    ],
  },

๐Ÿงกstyled-components๋ฅผ ์‚ฌ์šฉํ•œ ์Šคํƒ€์ผ ์ ์šฉ

  • ๊ธฐ์กด๊ณผ ๊ฐ™์ด styled-component๋ฅผ ์ ์šฉํ•˜๋Š” ๊ฒฝ์šฐ Prop โ€˜classNameโ€™ did not match๋ผ๋Š” ๊ฒฝ๊ณ ๊ฐ€ ๋ฐœ์ƒํ•˜๋ฉฐ ์Šคํƒ€์ผ์ด ์ ์šฉ๋˜์ง€ ์•Š์Œ.
import styled from 'styled-components'

export const Container = styled.div`
    display: flex;
    flex-direction: column;
`

function Post() {
  return (
    <Container>
      <h1>Post</h1>
    </Container>
  )
}

export default Post

// ERROR! Prop โ€˜classNameโ€™ did not match
  • styled-components์˜ ๊ฒฝ์šฐ ์‚ฌ์šฉ์ž์—๊ฒŒ ํŽ˜์ด์ง€๋ฅผ ๊ณต๊ธ‰ ํ•  ๋•Œ ๋งˆ๋‹ค ์ƒˆ๋กœ ๊ณ ์œ ํ•œ classname์„ ์„ค์ •ํ•˜๋Š”๋ฐ, next.js์—์„œ build์‹œ์— ๋ฏธ๋ฆฌ ๋ Œ๋”๋ง ํ•œ ์ •์  ํŽ˜์ด์ง€์˜ classname๊ณผ ์„œ๋ฒ„์—์„œ ์‚ฌ์šฉ์ž์—๊ฒŒ ๊ณต๊ธ‰ํ•˜๋ฉฐ ๋ Œ๋”๋ง ๋œ ํŽ˜์ด์ง€๊ฐ„์— classname์ด ์ผ์น˜ํ•˜์ง€ ์•Š๊ธฐ๋•Œ๋ฌธ

  • next.config.js์— compiler์ถ”๊ฐ€

// next.config.js

/** @type {import('next').NextConfig} */
const nextConfig = {
  reactStrictMode: true,
  compiler: {
    styledComponents: true
  }
}

module.exports = nextConfig
  • styledComponents: true ์„ค์ • ์‹œ ์ตœ์ดˆ ๋นŒ๋“œํ•  ๋•Œ์™€ ์„œ๋ฒ„/ํด๋ผ์ด์–ธํŠธ์—์„œ ๋ Œ๋”๋งํ•  ๋•Œ์˜ className ์„ ์ผ์น˜์‹œํ‚ด

๐Ÿงก์ •์ ์‚ฌ์ดํŠธ ์ƒ์„ฑ (SSG)

  • ์™ธ๋ถ€์—์„œ ๋ฐ์ดํ„ฐ๋ฅผ ๊ฐ€์ ธ์˜ค์ง€ ์•Š๋Š” ๊ฒฝ์šฐ Next.js๋Š” ๊ธฐ๋ณธ์ ์œผ๋กœ SSG๋ฐฉ์‹์œผ๋กœ HTML์„ ๊ทธ๋ฆผ
  • ์™ธ๋ถ€์—์„œ ๋ฐ์ดํ„ฐ๋ฅผ ๊ฐ€์ ธ์™€์•ผํ•˜๋Š” ๊ฒฝ์šฐ SSG๋ฐฉ์‹์œผ๋กœ ๋ Œ๋”๋ง์„ ํ•˜๊ธฐ ์œ„ํ•ด์„œ๋Š” ๋ณ„๋„์˜ ์„ค์ •์„ ํ•ด์ฃผ์–ด์•ผ ํ•จ

โœจgetStaticProps()

  1. getStaticProps()ํ•จ์ˆ˜๋ฅผ ๋งŒ๋“ค์–ด ํ•„์š”ํ•œ ๋ฐ์ดํ„ฐ๋ฅผ ํ˜ธ์ถœํ•œ ๋’ค, props์— ๊ฐ์ฒดํ˜•ํƒœ๋กœ ๋‹ด์•„ return
  2. getStaticProps()์—์„œ returnํ•œ ๋ฐ์ดํ„ฐ๋Š” ์ปดํฌ๋„ŒํŠธ์—์„œ props๋กœ ๋ฐ›์„ ์ˆ˜ ์žˆ๋‹ค
//๊ธฐ๋ณธํ˜•ํƒœ
export async function getStaticProps() {
  // ๋ฐ์ดํ„ฐ ํ˜ธ์ถœ
  return { props: { ๋ฐ์ดํ„ฐ } }
}

function ํŽ˜์ด์ง€์ปดํฌ๋„ŒํŠธ({ ๋ฐ์ดํ„ฐ }) {
  return <div></div>
}
  • ์˜ˆ์‹œ์ฝ”๋“œ
import axios from 'axios'

export async function getStaticProps() {
    const response = await axios.get('https://jsonplaceholder.typicode.com/posts')
    const posts = response.data

    return { props: { posts } }
}

function Post({ posts }) {
  return (
    <div>
        {posts.map((post) => (
          <div key={post.id}>
              <h1>{post.title}</h1>
              <p>{post.body}</p>
          </div>
        ))}
      </div>
  )
}

export default Post

revalidate์˜ต์…˜

revalidate: 10 // ๋‹จ์œ„: ์ดˆ(s)
  • ์ •์ ํŽ˜์ด์ง€์˜ ๊ฒฝ์šฐ ๋นŒ๋“œ ์‹œ ๋ถˆ๋Ÿฌ์˜จ ๋ฐ์ดํ„ฐ๋ฅผ ์‚ฌ์šฉํ•ด ์‚ฌ์šฉ์ž๊ฐ€ ์˜ค๋ž˜ ๋œ ๋ฐ์ดํ„ฐ๋ฅผ ์‚ฌ์šฉํ•˜๊ฒŒ ๋˜๋Š” ๋‹จ์ ์ด ์žˆ์Œ
  • revalidate์˜ต์…˜์„ ์‚ฌ์šฉํ•˜๋ฉด ์„ค์ •ํ•œ ์‹œ๊ฐ„์ด ์ง€๋‚  ๋•Œ ๋งˆ๋‹ค ์ฃผ๊ธฐ์ ์œผ๋กœ ๋‹ค์‹œ buildํ•˜์—ฌ ๋ฐ์ดํ„ฐ๋ฅผ ์ƒˆ๋กœ ๋ถˆ๋Ÿฌ์˜ด
  • ISR(Incremental Static Regeneration) ๋ฐฉ์‹์ด๋ผ ํ•จ
// ์˜ˆ์‹œ
export async function getStaticProps() {
  const response = await axios.get(
    "https://jsonplaceholder.typicode.com/posts"
  );
  const posts = response.data;

  return { props: { posts }, revalidate: 10 }; //10์ดˆ์— ํ•œ๋ฒˆ์”ฉ ๋‹ค์‹œ build
}

โœจgetStaticPaths()

  • ๋™์ ์ธ ๊ฒฝ๋กœ๋ฅผ ์‚ฌ์šฉํ•˜๋Š” ํŽ˜์ด์ง€์—์„œ, next.js๋Š” ์‚ฌ์ „์— ์–ด๋–ค ํŽ˜์ด์ง€ ๊ฒฝ๋กœ๋ฅผ ๋ฏธ๋ฆฌ ๋ Œ๋”๋ง ํ•ด๋‘์–ด์•ผ ํ•˜๋Š”์ง€ ์•Œ ์ˆ˜ ์—†์Œ

  • getStaticPaths()๋Š” ๋™์ ์ธ ๊ฒฝ๋กœ๋ฅผ ์‚ฌ์šฉํ•˜๋Š” ํŽ˜์ด์ง€์—์„œ ํŠน์ •ํ•œ ๊ฒฝ๋กœ๋ฅผ ๋ฏธ๋ฆฌ ๋ Œ๋”๋ง ํ•ด๋‘˜ ์ˆ˜ ์žˆ๋„๋ก ์ง€์ •

  • getStaticPaths()์˜ return ๊ฐ’์„ ํ†ตํ•ด ์ง€์ •์ด ๊ฐ€๋Šฅํ•˜๋ฉฐ, ๋ฐฐ์—ดํ˜•์‹์œผ๋กœ ๊ฒฝ๋กœ๋ฅผ ๋‹ด์•„ ์„ค์ •

  • ์˜ˆ๋ฅผ๋“ค์–ด /posts/[id]์˜ ๋™์ ๊ฒฝ๋กœ๋กœ ์ ‘๊ทผํ•˜๋Š” ํŽ˜์ด์ง€๊ฐ€ ์žˆ์„ ๋•Œ, ์•„๋ž˜์™€ ๊ฐ™์ด ์ฝ”๋“œ๋ฅผ ์ž‘์„ฑํ•˜๋ฉด, posts/1, posts/2, posts/3 ํŽ˜์ด์ง€๋ฅผ build์‹œ์— ์ •์ ์œผ๋กœ ์ƒ์„ฑ

    export async function getStaticPaths() {
      return { 
        paths: [
          {params: {id: '1'}}, {params: {id: '2'}}, {params: {id: '3'}}...
        ]
      }
    }

fallback์˜ต์…˜

  • ์ฝ”๋“œ์—์„œ ๋ช…์‹œํ•˜์ง€ ์•Š์€ ๊ฒฝ๋กœ๋กœ ์‚ฌ์šฉ์ž๊ฐ€ ์ ‘๊ทผํ•˜๋Š” ๊ฒฝ์šฐ ์ฒ˜๋ฆฌ
export async function getStaticPaths() {
  const response = await axios.get(
    "https://jsonplaceholder.typicode.com/posts"
  );
  const posts = response.data;
  const paths = posts.map((post) => ({ params: { id: `${post.id}` } }));

  return { paths, fallback: false };
}
  • 1~100๋ฒˆ๊ธ€๋งŒ ์žˆ๋Š” ์ƒํ™ฉ์— ์‚ฌ์šฉ์ž๊ฐ€ 101๋ฒˆ ๊ฒŒ์‹œ๊ธ€์— ์ ‘๊ทผํ•˜๋Š” ๊ฒฝ์šฐ

    • fallback: false => 404ํŽ˜์ด์ง€๋กœ ์ด๋™
    • fallback: true => ๊ทธ์ œ์„œ์•ผ ํ•ด๋‹น id๋กœ ์š”์ฒญ์„ ๋ณด๋‚ด๊ณ  ์‘๋‹ต์„ ๊ฐ€์ ธ์˜ด (๋กœ๋”ฉํ™”๋ฉด ์„ค์ •๊ฐ€๋Šฅ)
      • ๋ฐ์ดํ„ฐ๊ฐ€ ๋„ˆ๋ฌด ๋งŽ์€๊ฒฝ์šฐ ํ•ด๋‹น๋ฐฉ์‹์„ ์‚ฌ์šฉ
    • fallback: blocking => ๊ทธ์ œ์„œ์•ผ ํ•ด๋‹น id๋กœ ์š”์ฒญ์„ ๋ณด๋‚ด๊ณ  ์‘๋‹ต์„ ๊ฐ€์ ธ์˜ด (๋กœ๋”ฉํ™”๋ฉด ์—†์ด, ๋กœ๋”ฉ์ด ์™„๋ฃŒ๋˜์–ด์•ผ ํŽ˜์ด์ง€๋ฅผ ๋ณด์—ฌ์คŒ)
  • true๋‚˜ blocking๋กœ ์˜ต์…˜๊ฐ’์„ ์„ค์ •ํ•˜๋Š” ๊ฒฝ์šฐ ์‚ฌ์šฉ์ž๊ฐ€ ํ•œ๋ฒˆ ์ ‘๊ทผํ•œ ํŽ˜์ด์ง€๋Š” ์บ์‹ฑ๋˜์–ด ์žฌ์‚ฌ์šฉ

  • true์„ค์ •์‹œ ๋ณด์—ฌ ์ค„ ๋กœ๋”ฉํŽ˜์ด์ง€๋Š” router.isFallback์œผ๋กœ boolean๊ฐ’์„ ๋ฐ›์•„์™€ ์•„๋ž˜์ฒ˜๋Ÿผ ์ž‘์„ฑ

const router = useRouter()

if (router.isFallback) {
    return <div>๋กœ๋”ฉ ์ค‘..</div>
}

โœจgetStaticProps()์™€ getStaticPaths() ํ•จ๊ป˜ ์‚ฌ์šฉ

  • getStaticPaths()๋กœ ์ƒ์„ฑํ•˜๊ฒŒ ๋˜๋Š” ํŽ˜์ด์ง€์—์„œ ์™ธ๋ถ€๋ฐ์ดํ„ฐ๋ฅผ ์‚ฌ์šฉํ•ด์•ผ ํ•˜๋Š” ๊ฒฝ์šฐ ๋‘ ๋ฉ”์†Œ๋“œ๋ฅผ ํ•จ๊ป˜ ์‚ฌ์šฉํ•œ๋‹ค
  • getStaticPaths()์—์„œ returnํ•œ ๊ฒฝ๋กœ๋ฐ์ดํ„ฐ๋ฅผ getStaticProps()๋กœ ๋ฐ›์•„์„œ ์‚ฌ์šฉ ํ•  ์ˆ˜ ์žˆ๋‹ค
  • getStaticPaths()์—์„œ returnํ•œ paths๋ฐฐ์—ด์˜ ๊ธธ์ด๋งŒํผ getStaticProps()๊ฐ€ ์‹คํ–‰

์ฝ”๋“œ ํ๋ฆ„:

getStaticPaths() > paths์ „๋‹ฌ > getStaticProps() > props์ „๋‹ฌ => ํŽ˜์ด์ง€์ปดํฌ๋„ŒํŠธ()

  • ์˜ˆ์‹œ์ฝ”๋“œ
import axios from "axios";

// getStaticPaths => ์ง€๊ธˆ ์ ‘๊ทผ ํ•  ์•„์ด๋”” ๋ฒˆํ˜ธ ๋ช…์‹œ
export async function getStaticPaths() {
  const response = await axios.get(
    "https://jsonplaceholder.typicode.com/posts"
  );
  const posts = response.data;
  const paths = posts.map((post) => ({ params: { id: `${post.id}` } })); 
  // paths = [{params: {id: 1}}, {params: {id: 2}}, {params: {id: 3}} ...]

  return { paths };
}

// getStaticProps => getStaticPaths๊ฐ€ ๋ฆฌํ„ดํ•œ ๋ฒˆํ˜ธ๋ฅผ ํ† ๋Œ€๋กœ ์š”์ฒญ์„ ๋ณด๋‚ด๊ณ , ๋ฐ์ดํ„ฐ๋ฅผ ์ €์žฅ
export async function getStaticProps({ params }) {
  const response = await axios.get(
    `https://jsonplaceholder.typicode.com/posts/${params.id}`
  );
  const post = response.data;

  return { props: { post } };
}

// ํŽ˜์ด์ง€์ปดํฌ๋„ŒํŠธ => getStaticProps๊ฐ€ ๋ฆฌํ„ดํ•œ ๋ฐ์ดํ„ฐ๋ฅผ props๋กœ ๋ฐ›์•„์„œ ์‚ฌ์šฉ
function PostDetail({ post }) {
  return (
    <div>
      <span>{post.id}</span>
      <h1>{post.title}</h1>
      <p>{post.body}</p>
    </div>
  );
}

export default PostDetail;

๐Ÿงก์„œ๋ฒ„์‚ฌ์ด๋“œ๋ Œ๋”๋ง ๊ตฌํ˜„ (SSR)

  • getServerSideProps()ํ•จ์ˆ˜๋ฅผ ์‚ฌ์šฉํ•˜๋ฉฐ ์‚ฌ์šฉ ๋ฐฉ์‹์€ getStaticProps()์™€ ๊ฐ™์Œ
import axios from 'axios'

export async function getServerSideProps() {
    const response = await axios.get('https://jsonplaceholder.typicode.com/posts')
    const posts = response.data
    return { props: { posts } }
}

function Post({ posts }) {
  return (
    <div>
      {posts.map((post) => (
        <div key={post.id}>
          <h1>{post.title}</h1>
          <p>{post.body}</p>
        </div>
      ))}
    </div>
  )
}

export default Post

๐Ÿ’ก์ •๋ง ์—…๋ฐ์ดํŠธ๊ฐ€ ์žฆ์€ ๊ฒฝ์šฐ๊ฐ€ ์•„๋‹ˆ๋ผ๋ฉด, SSR ๋ฐฉ์‹ ๋ณด๋‹ค๋Š” SSG ๋ฐฉ์‹์— revalidate ์˜ต์…˜์„ ๋ช…์‹œํ•˜์—ฌ ์‚ฌ์šฉํ•˜๋Š”๊ฒƒ์„ ์ถ”์ฒœ

profile
ํ”„๋ก ํŠธ์—”๋“œ ๊ฐœ๋ฐœ์ž ์„ฑ์žฅ์ผ๊ธฐ ๐Ÿ’ญ

0๊ฐœ์˜ ๋Œ“๊ธ€