๐Ÿšค ์„ฑ์žฅ์ผ์ง€ 7.0

์ฑ… ํ–‰๋ณตํ•œ ์ด๊ธฐ์ฃผ์˜์ž(์›จ์ธ ๋‹ค์ด์–ด)์˜ ๋‚ด์šฉ์— ์ž๊ทน๋ฐ›์•„ ์‹œ์ž‘ํ•˜๋Š” ์†Œ๋ฐ•ํ•œ ์„ฑ์žฅ๊ธฐ๋ก

์‚ด์•„์žˆ๋Š” ๊ฝƒ๊ณผ ์ฃฝ์€ ๊ฝƒ์€ ์–ด๋–ป๊ฒŒ ๊ตฌ๋ณ„ํ•˜๋Š”๊ฐ€?
์„ฑ์žฅํ•˜๊ณ  ์žˆ๋Š” ๊ฒƒ์ด ์‚ด์•„ ์žˆ๋Š” ๊ฒƒ์ด๋‹ค.
์ƒ๋ช…์˜ ์œ ์ผํ•œ ์ฆ๊ฑฐ๋Š” ์„ฑ์žฅ์ด๋‹ค!

โš› (7.0)<์™„์ „ ๊ฐœํŽธ> ํŒŒ์ธ๋งŒ ํ•™์Šต๋ฒ•์„ ์•Œ๊ฒŒ ๋œ๋งŒํผ, ์„ฑ์žฅ์ผ์ง€๋Š” ์ •๋ง ๊ทธ ๋‚ ์˜ ํ‚ค์›Œ๋“œ ์ค‘์‹ฌ์œผ๋กœ ๊ฐ„๋‹จํ•˜๊ฒŒ ์ •๋ฆฌํ•˜๋„๋ก ํ•œ๋‹ค.

next ์ปดํฌ๋„ŒํŠธ ๊ตฌ์„ฑํ•˜๊ธฐ

์–ด๋Š์ •๋„ ํ”„๋กœ์ ํŠธ ์‹œ์ž‘ ์ „ ์—ฌ๋Ÿฌ ์„ธํŒ…์€ ๋งˆ๋ฌด๋ฆฌํ•˜๊ณ  ์ปดํฌ๋„ŒํŠธ ๊ตฌ์„ฑ์„ ํ•˜๋ ค๋‹ˆ ์ƒ๊ฐ๋ณด๋‹ค ๋งŽ์€ ๊ณ ๋ฏผ์ด ํ•„์š”ํ–ˆ๋‹ค. 13๋ฒ„์ „๋ถ€ํ„ฐ๋Š” ์ปดํฌ๋„ŒํŠธ ๋ณ„๋กœ ํด๋ผ์ด์–ธํŠธ, ์„œ๋ฒ„ ์ปดํฌ๋„ŒํŠธ๋ฅผ ๋ถ„๋ฆฌํ•ด์„œ ์‚ฌ์šฉํ•  ์ˆ˜ ์žˆ๊ฒŒ ๋˜์—ˆ๋‹ค. ๊ทธ๋ž˜์„œ ์ปดํฌ๋„ŒํŠธ๋ฅผ ๊ตฌ์„ฑํ•  ๋•Œ ์–ด๋–ค ์ปดํฌ๋„ŒํŠธ๋ฅผ ์‚ฌ์šฉํ• ์ง€ ๊ณ ๋ฏผ์ด ํ•„์š”ํ–ˆ๋‹ค. ๊ธฐ๋ณธ์ ์œผ๋กœ๋Š” ์ตœ๋Œ€ํ•œ SSG๋กœ ๊ตฌ์„ฑํ•˜๊ณ  ๊ทธ ๋’ค์— ์œ ์ €์™€์˜ ์ธํ„ฐ๋ ‰์…˜์ด ์žˆ๋Š” ๊ฒฝ์šฐ์—๋Š” ํด๋ผ์ด์–ธํŠธ ์ปดํฌ๋„ŒํŠธ, ๋ฐ์ดํ„ฐ๊ฐ€ ์ž์ฃผ ๋ณ€๊ฒฝ๋˜๋Š” ๊ฒฝ์šฐ์—๋Š” SSR๋กœ ๊ตฌ์„ฑํ•˜๊ธฐ๋กœ ํ–ˆ๋‹ค. ๋ฌผ๋ก  ๊ทธ๋Ÿผ์—๋„ ํ—ท๊ฐˆ๋ฆฌ๋Š” ๋ถ€๋ถ„๋“ค์ด ์กด์žฌํ•˜๊ธฐ๋Š” ํ•œ๋‹ค. ์˜ˆ๋ฅผ ๋“ค์–ด, ์„œ๋ฒ„์—๊ฒŒ ์ด๋ฏธ์ง€๋“ค์„ ๊ฐ€์ ธ์™€์„œ ๊ทธ ์ด๋ฏธ์ง€๋ฅผ ๋‚˜์—ดํ•˜๊ณ  ๊ทธ ์ด๋ฏธ์ง€๋ฅผ ์œ ์ €๊ฐ€ ์„ ํƒํ•ด์„œ ์–ด๋–ค UI ๋ณ€๊ฒฝ์„ ์ค€๋‹ค๊ณ  ํ•ด๋ณด์ž. ์ด ๊ฒฝ์šฐ์—๋Š” ์ด๋ฏธ์ง€๋“ค์„ ๊ฐ€์ ธ์˜ค๋Š” ๋ถ€๋ถ„์€ ์„œ๋ฒ„ ์ปดํฌ๋„ŒํŠธ๋กœ ๋‘๊ณ , ์œ ์ €๊ฐ€ ์„ ํƒํ•˜๋Š” ๋ถ€๋ถ„์€ ํด๋ผ์ด์–ธํŠธ ์ปดํฌ๋„ŒํŠธ๋กœ ๋‘๋Š” ๊ฒƒ์ด ๋งž๋Š” ๊ฒƒ ๊ฐ™๋‹ค.(์•„์ง ์ด ๋ถ€๋ถ„์€ ์ œ๋Œ€๋กœ ๊ตฌํ˜„ํ•ด๋ณด์ง€ ์•Š์•„์„œ ํ™•์‹คํ•˜์ง€๋Š” ์•Š๋‹ค. ํ•ด๋ด์•ผ์•Œ๋“ฏ..!)

next ์œ ์ €์˜ ๋ธŒ๋ผ์šฐ์ € ์„ค์ •์— ๋”ฐ๋ผ ๋‹คํฌ๋ชจ๋“œ ์ ์šฉํ•˜๊ธฐ

ํ˜„์žฌ ๋‹คํฌ๋ชจ๋“œ ์œ ๋ฌด์™€ ๋‹คํฌ/๋ผ์ดํŠธ๋ฅผ ๋ณ€๊ฒฝํ•˜๋Š” ํ† ๊ธ€ํ•จ์ˆ˜๋ฅผ context API๋กœ ๊ด€๋ฆฌํ•˜๊ณ  ์žˆ๋‹ค. ์ฝ”๋“œ๋Š” ์•„๋ž˜์™€ ๊ฐ™๋‹ค.

'use client';
import { createContext, useContext, useState } from 'react';

export const ThemeContext = createContext({});
export const ToggleThemeContext = createContext(() => {
  console.error('Forgot to wrap component in `ThemeProvider`');
});
export const useTheme = () => {
  return useContext(ThemeContext);
};
export const useToggleTheme = () => {
  return useContext(ToggleThemeContext);
};

type Props = {
  children: React.ReactNode;
};

export default function ThemeProvider({ children }: Props) {
  // NOTE: ํ˜„์žฌ ์œ ์ € ๋ธŒ๋ผ์šฐ์ € ์„ค์ • ํ…Œ๋งˆ๋ฅผ ๊ฐ€์ ธ์˜จ๋‹ค.
  const isBrowser = typeof window !== 'undefined';
  const initialTheme =
    isBrowser && window.matchMedia && window.matchMedia('(prefers-color-scheme: dark)').matches
      ? 'dark'
      : 'light';

  const [theme, setTheme] = useState(initialTheme);
  const toggleTheme = () => {
    setTheme(theme === 'light' ? 'dark' : 'light');
  };

  return (
    <ThemeContext.Provider value={theme}>
      <ToggleThemeContext.Provider value={toggleTheme}>
        <div className={theme}>{children}</div>
      </ToggleThemeContext.Provider>
    </ThemeContext.Provider>
  );
}

๊ทธ๋Ÿฐ๋ฐ ๋ฌธ์ œ๋Š” ํด๋ผ์ด์–ธํŠธ ์ปดํฌ๋„ŒํŠธ์—ฌ๋„ ์„œ๋ฒ„์—์„œ ํ”„๋ฆฌ๋ Œ๋”๋ง์„ ํ•˜๊ธฐ ๋•Œ๋ฌธ์— ๊ทธ ๋•Œ๋Š” window ๊ฐ์ฒด๊ฐ€ undefined์ธ ๋ฌธ์ œ๊ฐ€ ๋ฐœ์ƒํ–ˆ๋‹ค. ๊ทธ๋Ÿฌ๋˜ ์ค‘ next.js์—์„œ ์ œ๊ณตํ•˜๋Š” dynamic์„ ์ด์šฉํ•˜๋ฉด ํ•ด๊ฒฐํ•  ์ˆ˜ ์žˆ๋‹ค๋Š” ๊ฒƒ์„ ์•Œ๊ฒŒ ๋˜์—ˆ๋‹ค. ์•„๋ž˜์™€ ๊ฐ™์ด ์‚ฌ์šฉํ•˜๋ฉด ๋œ๋‹ค. ์œ„์˜ ์ฝ”๋“œ์—์„œ ThemeProvider๋ฅผ importํ•  ๋•Œ dynamic์œผ๋กœ ๊ฐ์‹ธ์ฃผ๋ฉด ๋œ๋‹ค.

import dynamic from 'next/dynamic';
const ThemeProvider = dynamic(() => import('components/ThemeProvider'), { ssr: false });

์ด๋ ‡๊ฒŒ ๋˜๋ฉด ์„œ๋ฒ„์—์„œ ํ”„๋ฆฌ๋ Œ๋”๋ง์ด ๋˜์ง€ ์•Š๊ณ  ํด๋ผ์ด์–ธํŠธ์—์„œ๋งŒ ๋ Œ๋”๋ง์ด ๋˜๊ธฐ ๋•Œ๋ฌธ์— window ๊ฐ์ฒด๊ฐ€ undefined๊ฐ€ ๋˜์ง€ ์•Š๋Š”๋‹ค.

๊ทธ๋Ÿฐ๋ฐโ€ฆ ๋˜๋‹ค๋ฅธ ๋ฌธ์ œ ๋ฐœ์ƒ

์ฒ˜์Œ์—” ํ•ด๊ฒฐํ•œ ์ค„ ์•Œ์•˜๋‹ค. ํ•˜โ€ฆ ๊ทธ๋Ÿฐ๋ฐ ๋ฌธ์ œ๋Š” ์–ดํ”Œ๋ฆฌ์ผ€์ด์…˜ ๊ฐ€์žฅ ์ƒ์œ„์— ThemeProvider๋ฅผ ๊ฐ์‹ธ๋‘์—ˆ๊ธฐ ๋•Œ๋ฌธ์—, ๊ทธ ํ•˜์œ„ ๋ชจ๋“  ์ปดํฌ๋„ŒํŠธ๊ฐ€ dynamic์œผ๋กœ ์ธํ•œ lazy loading ์ฒ˜๋ฆฌ๊ฐ€ ๋˜์–ด๋ฒ„๋ฆฐ๋‹ค๋Š” ๊ฒƒ์ด๋‹ค. ์ฆ‰, ์›น ํŽ˜์ด์ง€ ์ฒ˜์Œ ์ ‘์† ์‹œ ๋„คํŠธ์›Œํฌ ํƒญ์—์„œ ์ฒซ๋ฒˆ์งธ html์„ Preview๋กœ ๋ณด์•„๋„ ๊ทธ๋ƒฅ ๋นˆ ํ™”๋ฉด๋งŒ ๋ณด์ธ๋‹ค. ์ด๋Ÿฌ๋ฉด Next์—์„œ์˜ SSR(ํ˜น์€ SSG)์˜ ์˜๋ฏธ๊ฐ€ ์‚ฌ๋ผ์ ธ๋ฒ„๋ฆฐ๋‹คโ€ฆใ…  ํ›„โ€ฆ useEffect๋ฅผ ์‚ฌ์šฉํ•ด๋ดค์ง€๋งŒ, ๊ทธ๋Ÿฌ๋ฉด ์ฒ˜์Œ์— ๋ฐ”๋กœ ๋ผ์ดํŠธ ๋ชจ๋“œ๋กœ ๋ Œ๋”๋ง๋˜์—ˆ๋‹ค๊ฐ€ ๊ทธ ์ดํ›„์— ๋‹คํฌ ๋ชจ๋“œ๋กœ ๋ณ€๊ฒฝ๋œ๋‹ค. ์ผ๋‹จ์€ initialTheme์„ light๋กœ ๊ณ ์ •ํ•ด๋‘๊ณ , ๋‚˜์ค‘์— ๋‹ค์‹œ ๊ณ ๋ฏผํ•ด๋ด์•ผ๊ฒ ๋‹ค.(์• ์ดˆ์— ์„œ๋ฒ„ ๋ Œ๋”๋ง์—์„œ ์œ ์ €์˜ ์„ค์ •์„ ์•Œ๊ณ  ๋ Œ๋”๋งํ•˜๋Š” ๋ฐฉ๋ฒ•์ด ๋ง์ด ๋˜๋‚˜ ์‹ถ๊ธฐ๋„ ํ•˜๋‹ค.)

react icon with tailwind

์ •๋ง ๋ณ„ ๊ฑฐ ์•„๋‹ˆ์ง€๋งŒ, react icon์˜ className์— tailwindcss๊ฐ€ ์ ์šฉ๋œ๋‹ค๋Š” ์‚ฌ์‹คโ€ฆ!!!!! ์‹ฌ์ง€์–ด text- prefix๋ฅผ ๋ถ™์ด๋ฉด icon์˜ content์˜ ์ƒ‰์ƒ์ด ๋ณ€๊ฒฝ๋˜๊ณ  bg- prefix๋ฅผ ๋ถ™์ด๋ฉด icon์˜ ๋ฐฐ๊ฒฝ์ƒ‰์ด ๋ณ€๊ฒฝ๋œ๋‹ค. ์‚ฌ์‹ค ์•„์ด์ฝ˜๋“ค ํ•˜๋‚˜ํ•˜๋‚˜ ์ฐพ์•„๊ฐ€๋ฉด์„œ ์“ฐ๋Š”๊ฒŒ ๋„ˆ๋ฌด ๋ถˆํŽธํ•˜๋‹ค๊ณ  ํˆฌ๋œ๊ฑฐ๋ ธ๋Š”๋ฐ ์ด๊ฑด ์ซŒ ์ธ์ •

๐Ÿ“ ํšŒ๊ณ 

์ƒ๊ฐ๋ณด๋‹ค ๋น ๋ฅด์ง„ ์•Š์ง€๋งŒ ์ƒ๊ฐ๋ณด๋‹ค ์žฌ๋ฏธ์žˆ๋„น

์ฐธ๊ณ