๐Ÿ’ป์šฉ๋‡ฝ ๊ฐœ๋ฐœ ๋…ธํŠธ๐Ÿ’ป
article thumbnail
๋ฐ˜์‘ํ˜•

[Next.js] ๊ฐœ์ธ ํ”„๋กœ์ ํŠธ 11์—์„œ 14๋กœ ๋งˆ์ด๊ทธ๋ ˆ์ด์…˜ ๊ณผ์ •

๐Ÿ“–๋“ค์–ด๊ฐ€๋ฉฐ

์•ฝ 2๋…„ ์ „ Next.js(SSG) + TypeScript๋กœ ์ œ์ž‘ํ•œ ๋กค MBTI ํ”„๋กœ์ ํŠธ๋ฅผ 14 ๋ฒ„์ „์œผ๋กœ ๋งˆ์ด๊ทธ๋ ˆ์ด์…˜ ํ•œ ๊ณผ์ • ๋ฐ ํ›„๊ธฐ๋ฅผ ์ž‘์„ฑํ•ด๋ณด๋ ค ํ•œ๋‹ค.


ํ•ด๋‹น ๊ธ€์€ ๋งˆ์ด๊ทธ๋ ˆ์ด์…˜์„ ์–ด๋–ป๊ฒŒ ํ•˜๋Š” ๊ฐ€์— ๋Œ€ํ•œ ๊ธ€์ด ์•„๋‹™๋‹ˆ๋‹ค.
์ œ๋ชฉ ๊ทธ๋Œ€๋กœ ๋งˆ์ด๊ทธ๋ ˆ์ด์…˜์„ ํ•˜๋Š” ๊ณผ์ •์„ ๊ธฐ๋กํ•˜๋Š” ๊ธ€์ž…๋‹ˆ๋‹ค.

๋งˆ์ด๊ทธ๋ ˆ์ด์…˜์„ ์™œ ํ•ด์•ผ ํ–ˆ๋Š”๊ฐ€?

์‚ฌ์‹ค ์„œ๋น„์Šค ์ž์ฒด๋กœ๋Š” ๋ฌธ์ œ์—†์ด ์ž˜ ๋Œ์•„๊ฐ€๊ณ  ์žˆ์—ˆ๋‹ค.

๋Œ€์‹ ์— Next.js๋กœ ์ง์ ‘ A - Z๊นŒ์ง€ ํ•ด๋ณธ ํ”„๋กœ์ ํŠธ๋Š” ๋กค MBTI ํ”„๋กœ์ ํŠธ๋ฐ–์— ์—†์—ˆ๋‹ค.

 

Next.js๊ฐ€ 11๋ฒ„์ „์— ๋น„ํ•ด 14 ๋ฒ„์ „๊นŒ์ง€(ํŠนํžˆ 13 ๋ฒ„์ „์—์„œ) ๋งŽ์€ ๋ถ€๋ถ„์ด ๋ณ€๊ฒฝ์ด ๋˜๊ณ , ์–ด๋–ป๊ฒŒ ํ™œ์šฉํ•  ์ˆ˜ ์žˆ๋Š”์ง€ ์ง์ ‘ ๋ถ€๋”ชํžˆ๋ฉฐ ๊นจ๋‹ฌ์œผ๋ฉฐ ํ•™์Šตํ•˜๋Š” ๋ฐฉ์‹์œผ๋กœ ์ง€์‹์„ ํก์ˆ˜ํ•˜๋Š” ๋‚˜์˜ ์„ฑํ–ฅ์ธ ์ด์œ ์™€ ์ตœ์‹  ๋ฒ„์ „์œผ๋กœ ๋งˆ์ด๊ทธ๋ ˆ์ด์…˜ ํ•˜๋ฉด์„œ ๊ธฐ์กด ์ฝ”๋“œ์™€๋Š” ์–ด๋–ป๊ฒŒ ๋‹ฌ๋ผ์ง€๊ณ  ์–ด๋–ค ๊ฐœ์„ ์ ์ด ์ƒ๊ธธ๊นŒ ํ•˜๋Š” ๋ถ€๋ถ„์—์„œ ๊ถ๊ธˆํ–ˆ๋‹ค.

 

๊ทธ๋ฆฌ๊ณ  ์ถ”ํ›„์— ๊ธฐ๋Šฅ ์ถ”๊ฐ€ ์˜ˆ์ •๋„ ์žˆ๊ธฐ๋„ ํ•˜๊ณ  2๋…„ ์ „ ์ž‘์„ฑ๋œ ์ฝ”๋“œ๋ผ ๋ฒŒ์จ ๋ ˆ๊ฑฐ์‹œ ์ฝ”๋“œ๊ฐ€ ๋˜์–ด๋ฒ„๋ ธ๋‹ค..

๊ทธ๋ž˜์„œ ๊ธฐ์กด์— ์žˆ๋˜ Next.js 11 ๋ฒ„์ „์˜ ํ”„๋กœ์ ํŠธ๋ฅผ ์ตœ์‹  ๋ฒ„์ „(14 ๋ฒ„์ „)์œผ๋กœ ๋งˆ์ด๊ทธ๋ ˆ์ด์…˜ ํ•ด๋ณด๊ธฐ๋กœ ๋งˆ์Œ๋จน๊ฒŒ ๋˜์—ˆ๋‹ค.

 

๊ธฐ์กด ํ”„๋กœ์ ํŠธ์—์„œ ์–ด๋–ค ๋ถ€๋ถ„๋“ค์ด ๋ณ€๊ฒฝ์ด ๋˜์—ˆ๋Š”๊ฐ€?

์•„๋ž˜๋Š” ๋งˆ์ด๊ทธ๋ ˆ์ด์…˜์„ ํ•˜๋ฉด์„œ ํ”„๋กœ์ ํŠธ ์ค‘ ๋ณ€๊ฒฝ๋œ ๋‚ด์šฉ์ด๋‹ค.

1. ๊ด€๋ จ ์ข…์†์„ฑ ์—…๋ฐ์ดํŠธ

  • Next.js 11.0.1 -> 14.0.4
  • React 17.0.2 -> 18.2.0
  • styled-components 5.3.0 -> 6.1.1
  • Typescript 4.3.5 -> 4.5.2

2. page router -> app router

์•„๋ž˜๋Š” app router๋กœ ๊ตฌ์กฐ๋ฅผ ๋ณ€๊ฒฝํ•˜๋ฉด์„œ ๋ณ€๊ฒฝ๋œ ๋ถ€๋ถ„๋“ค์ด๋‹ค.

  • ๊ตฌ๊ธ€ ์• ๋„๋ฆฌํ‹ฑ์Šค ์ฝ”๋“œ ๋ณ€๊ฒฝ
  • styled-components ๊ด€๋ จ ์ฝ”๋“œ ๋ณ€๊ฒฝ
  • ์นด์นด์˜คํ†ก ๊ณต์œ ํ•˜๊ธฐ ๋กœ์ง ๋ณ€๊ฒฝ
  • ์ „์ฒด์ ์ธ ํด๋”๊ตฌ์กฐ ๋ณ€๊ฒฝ
  • ํด๋ผ์ด์–ธํŠธ ์ปดํฌ๋„ŒํŠธ ๋ถ„๋ฆฌ

3. getStaticPaths -> generateStaticParams

4. ํฐํŠธ ์ ์šฉ ๋ณ€๊ฒฝ ๋ฐ ์ตœ์ ํ™”

  • @fontsource -> next/font

5. metadata ์ ์šฉ ๋ฐฉ๋ฒ• ๋ณ€๊ฒฝ

 

๋งˆ์ด๊ทธ๋ ˆ์ด์…˜ ๊ณผ์ •

๊ด€๋ จ ์ข…์†์„ฑ ์—…๋ฐ์ดํŠธ

๋จผ์ € next.js ๊ณต์‹๋ฌธ์„œ๋ฅผ ์ฐธ๊ณ ํ•ด์„œ ์ตœ์‹  ๋ฒ„์ „์œผ๋กœ ์—…๋ฐ์ดํŠธ ์‹œ์ผฐ๋‹ค.

yarn add next@latest react@latest react-dom@latest eslint-config-next@latest

 

์ดํ›„์— yarn dev๋ฅผ ์ด์šฉํ•ด์„œ ์ž˜ ์‹คํ–‰์ด ๋˜๋Š”์ง€ ํ™•์ธ์„ ํ•ด๋ณด์•˜์ง€๋งŒ,

ํƒ€์ž…์Šคํฌ๋ฆฝํŠธ๋„ 4.5.2 ๋ฒ„์ „ ์ด์ƒ์ด ๋˜์–ด์•ผ ๋œ๋‹ค๋Š” ์˜ค๋ฅ˜๊ฐ€ ๋ฐœ์ƒํ•ด์„œ ์ตœ์†Œ ๊ตฌ๋™ ๋ฒ„์ „์ธ 4.5.2 ๋ฒ„์ „์œผ๋กœ ๊ธ€๋กœ๋ฒŒ๋กœ ์—…๋ฐ์ดํŠธํ–ˆ๋‹ค.

npm install -g typescript@4.5.2

 

๊ทธ๋ฆฌ๊ณ , ๊ธฐ์กด styled-components๋„ ๋ฒ„์ „(5.3.0)์ด ๋งค์šฐ ์˜ค๋ž˜๋˜์—ˆ๊ธฐ์—, ์ด ์ฐธ์— ์ตœ์‹  ๋ฒ„์ „(6.1.1)์œผ๋กœ ์—…๋ฐ์ดํŠธํ–ˆ๋‹ค.

yarn add styled-components@latest

๊ทธ๋ฆฌ๊ณ  ๋‹น์‹œ์—๋Š” "@types/styled-components": "^5.1.11"๋ฅผ ๋”ฐ๋กœ ์„ค์น˜ํ•ด ์คฌ์—ˆ๋Š”๋ฐ ์ด๋ฒˆ์— ์—…๋ฐ์ดํŠธํ•˜๋ฉด์„œ @types๋Š” ๋”ฐ๋กœ ํ•„์š”ํ•˜์ง€ ์•Š๊ฒŒ ๋œ ๊ฒƒ ๊ฐ™์•„ ์ œ๊ฑฐํ•ด ์คฌ๋‹ค.

 

์ถ”๊ฐ€๋กœ node ๋ฒ„์ „๋„ ์ตœ์†Œ 18.17 ์ด์ƒ์—์„œ๋งŒ ๊ตฌ๋™์ด ๋˜๊ธฐ ๋•Œ๋ฌธ์— node ๋ฒ„์ „๋„ lts ๋ฒ„์ „์œผ๋กœ ๋งž์ท„๋‹ค.(20.10.0 ๋ฒ„์ „)

app router ์ ์šฉ

๋Œ€๋ง์˜ app router ์ ์šฉ์ด๋‹ค..

13 ๋ฒ„์ „์ด ๋‚˜์˜จ ํ›„ page router ์™ธ์— app router๊ฐ€ ์ƒ๊ฒผ๋‹ค.

app directory ๋‚ด๋ถ€์—์„œ๋Š” ๋ชจ๋“  ์ปดํฌ๋„ŒํŠธ๊ฐ€ ๊ธฐ๋ณธ์ ์œผ๋กœ ์„œ๋ฒ„์ปดํฌ๋„ŒํŠธ๋กœ ๋™์ž‘ํ•œ๋‹ค.

React์˜ ํด๋ผ์ด์–ธํŠธ ์ปดํฌ๋„ŒํŠธ์™€ ์„œ๋ฒ„์ปดํฌ๋„ŒํŠธ

 

ํ˜„์žฌ page router์™€ app router๋ฅผ ์„ ํƒํ•  ์ˆ˜ ์žˆ๋‹ค.

page router๋Š” ๊ธฐ์กด์˜ ๋ฐฉ์‹๊ณผ ๋™์ผํ•œ ๊ตฌ์กฐ๋ฅผ ๊ฐ€์ ธ๊ฐ€์ง€๋งŒ app router๋กœ ๋ฐ”๋€Œ๋ฉด์„œ ๊ตฌ์กฐ๊ฐ€ ์ƒ๋‹นํžˆ ๋ฐ”๋€Œ์—ˆ๋‹ค.

 

ํ™•์‹คํžˆ app router๊ฐ€ ๋ถˆํŽธํ•˜๋‹ค๋Š” ๊ธ€ ๋“ค์ด ๋งŽ์ด ๋ณด์˜€๋Š”๋ฐ ์ด๋ฒˆ์— app rotuer๋ฅผ ์ ์šฉํ•˜๋ฉด์„œ ์™œ ๋ถˆํŽธํ•˜๋‹ค๋Š” ๊ฑด์ง€ ๋ชธ์†Œ ๋Š๋ผ๊ฒŒ ๋˜์—ˆ๋‹ค.

๊ทธ๋ž˜์„œ app router๋ฅผ ์ ์šฉํ•˜๊ธฐ ์ „์— ์™œ app router๋ฅผ ์‚ฌ์šฉํ•ด์•ผ ํ•˜๋Š”์ง€ app router๋กœ์จ ์–ด๋–ค ์ด์ ์„ ๊ฐ€์ ธ๊ฐˆ ์ˆ˜ ์žˆ๋Š”์ง€ ์ž˜ ์ƒ๊ฐํ•ด ๋ณด๊ธธ ๋ฐ”๋ž€๋‹ค.

 

์•„๋ฌด ์ด์œ  ์—†์ด app router๋ฅผ ์ ์šฉํ–ˆ๋‹ค๊ฐ€ ์˜คํžˆ๋ ค ์„œ๋ฒ„ ์ปดํฌ๋„ŒํŠธ์™€ ํด๋ผ์ด์–ธํŠธ ์ปดํฌ๋„ŒํŠธ ๋•Œ๋ฌธ์— ์ƒ์‚ฐ์„ฑ์ด ์ €ํ•˜๋  ์ˆ˜ ์žˆ๋‹ค๊ณ  ๋Š๊ผˆ๋‹ค.

 

๋‚ด๊ฐ€ app router๋ฅผ ์ ์šฉํ•œ ์ด์œ ๋Š” ํ”„๋กœ์ ํŠธ ์ž์ฒด๊ฐ€ ํฌ์ง€ ์•Š๊ณ  ํ† ์ด ํ”„๋กœ์ ํŠธ ์ •๋„์˜ ๊ทœ๋ชจ์ด๊ธฐ ๋•Œ๋ฌธ์— app router๋ฅผ ์ ์šฉํ•œ๋‹ค๊ณ  ํ•ด์„œ ๋Œ€๊ทœ๋ชจ ์—…๋ฐ์ดํŠธ(?)๊ฐ€ ์ผ์–ด๋‚˜์ง€ ์•Š์„ ๊ฒƒ์ด๋ผ ์ƒ๊ฐํ•˜๊ณ  ๊ธฐ์กด page router์™€ ๋ฌด์Šจ ์ฐจ์ด๊ฐ€ ์žˆ๋Š”์ง€, ์™œ ์‚ฌ๋žŒ๋“ค์ด ๋ถˆํŽธํ•˜๋‹ค๊ณ  ํ•˜๋Š” ๋ชฉ์†Œ๋ฆฌ๊ฐ€ ๋“ค๋ฆฌ๋Š”์ง€ ๋Š๊ปด๋ณด๊ณ  ์‹ถ์—ˆ๋‹ค.

์™œ๋ƒ๋ฉด ๋ถˆํŽธํ•œ ๊ฒƒ์€ ์ฃผ๊ด€์ ์ธ ๊ฒƒ์ด๊ธฐ ๋•Œ๋ฌธ์— ๋‚ด๊ฐ€ ์‚ฌ์šฉํ–ˆ์„ ๋•Œ๋Š” ์˜คํžˆ๋ ค app router๊ฐ€ ๋” ๋งž์„ ์ˆ˜ ์žˆ๋‹ค๋Š” ์ƒ๊ฐ๋„ ๋“ค์—ˆ๊ธฐ ๋•Œ๋ฌธ์ด๋‹ค.

app router ๊ตฌ์กฐ

์œ„ ์‚ฌ์ง„์€ app router์˜ ๊ตฌ์กฐ๋‹ค.

page ํŒŒ์ผ ์ด๋ฆ„์ด ๊ณง ํ•ด๋‹น ๋ผ์šฐํŠธ๋ฅผ ๋ณด์—ฌ์ค„ ์ปดํฌ๋„ŒํŠธ๊ฐ€ ๋˜๋Š” ๊ฒƒ์ด๋‹ค.

app ํด๋” ๋‚ด์— ํด๋” ์ด๋ฆ„์ด route๊ฐ€ ๋œ๋‹ค.

ํ•ด๋‹น ๊ทœ์น™์„ ๊ผญ ์ง€์ผœ์•ผ ํ•œ๋‹ค.

page ํŒŒ์ผ ์ด๋ฆ„ ์™ธ์— ๋‹ค๋ฅธ ํŒŒ์ผ์ด๋ฆ„์ด ์žˆ์–ด๋„ ๋‹ค๋ฅธ ํŒŒ์ผ์€ ๋”ฐ๋กœ route๋กœ ์ธ์‹๋˜์ง€ ์•Š๋Š”๋‹ค.

ํŒŒ์ผ ๊ตฌ์กฐ ๋ณ€๊ฒฝ

๋จผ์ € ์•„๋ž˜๋Š” ๋‚ด ํ”„๋กœ์ ํŠธ์—์„œ app router๋ฅผ ์ ์šฉํ•œ app ํด๋”์˜ ๊ตฌ์กฐ๋‹ค

app ํด๋” ๊ตฌ์กฐ

app ํด๋” ๋‚ด์—์„œ ์›ฌ๋งŒํ•œ ์ปดํฌ๋„ŒํŠธ๋ฅผ ๊ด€๋ฆฌํ•˜๋Š” ์ฝ”๋“œ๋“ค๋„ ๋ดค๊ณ  ๊ธฐ์กด์ฒ˜๋Ÿผ components ํด๋”๋ฅผ ์ƒ์„ฑํ•˜๊ณ  ๊ทธ๊ณณ์—์„œ ๊ด€๋ฆฌํ•˜๋Š” ์ฝ”๋“œ๋„ ๋ดค๊ณ  ์—ฌ๋Ÿฌ ํด๋” ๊ด€๋ฆฌ ์ปจ๋ฒค์…˜์ด ์žˆ๋Š” ๊ฒƒ ๊ฐ™๋‹ค.

๋‚˜๋Š” ๊ธฐ์กด๊ณผ ๋งŽ์€ ๋ณ€ํ™”๊ฐ€ ์—†๋„๋ก ํ•˜๊ณ  ํ›„์ž๊ฐ€ ๋” ๊ฐ€๋…์„ฑ์ด ์ข‹๊ฒŒ ๋Š๊ปด์„œ ํ›„์ž๋ฅผ ํƒํ–ˆ๋‹ค.

 

๊ธฐ์กด์— page rotuer์™€ ๋‹ค๋ฅด๊ฒŒ _document.tsx์™€ _app.tsx๊ฐ€ ์‚ฌ๋ผ์ง€๊ณ  layout.tsx์—์„œ ๋ชจ๋‘ ๊ด€๋ฆฌํ•˜๊ฒŒ ๋ณ€๊ฒฝ๋˜์—ˆ๋‹ค.

๊ธฐ์กด Layout์œผ๋กœ ๊ณตํ†ต ์ปดํฌ๋„ŒํŠธ ์Šคํƒ€์ผ๋ง์„ ํ•ด์คฌ์—ˆ๋Š”๋ฐ layout.tsx์—์„œ ๊ด€๋ฆฌํ•˜๋ฉด ๋˜๋„๋ก ๋ณ€๊ฒฝํ–ˆ๋‹ค.

// app router ์ ์šฉ ์ „ Layout.tsx
import GlobalStyle from 'styles/GlobalStyle';
import styled from 'styled-components';
import { size } from 'styles/theme';
import Footer from './Footer';
import DarkModeBtn from '../Buttons/DarkModeBtn';
import { useEffect } from 'react';
import { useRouter } from 'next/router';
import * as gtag from 'utils/gtag';

...

const Layout = ({ children }: ILayoutProps) => {

 ...

  return (
    <Wrapper>
      <GlobalStyle />
      <DarkModeBtn />
      {children}
      <Footer />
    </Wrapper>
  );
};

export default Layout;

์œ„ ์ฝ”๋“œ๋Š” ์ด์ „ ๋ ˆ์ด์•„์›ƒ์„ ๋‹ด๋‹นํ•˜๋˜ ์ฝ”๋“œ๋‹ค.

๊ธฐ์กด Layout.tsx์— ์žˆ๋˜ ์ฝ”๋“œ app/layout.tsx ํŒŒ์ผ์„ ์ƒ์„ฑ ํ›„ ๊ทธ๋Œ€๋กœ ๊ฐ–๋‹ค๊ฐ€ ์˜ฎ๊ฒผ๋Š”๋ฐ

  1. 'use clinet'๋ฅผ ์‚ฌ์šฉํ•  ์ˆ˜ ์—†๋Š” ๊ด€๋ จ ์—๋Ÿฌ๊ฐ€ ๋ฐœ์ƒ
  2. Uncaught Error: invariant expected app router to be mounted ์—๋Ÿฌ ๋ฐœ์ƒ

๋จผ์ € 1๋ฒˆ์— ๊ด€ํ•œ ์—๋Ÿฌ์— ๋Œ€ํ•œ ํ•ด๊ฒฐ์ฑ…์€ ํด๋ผ์ด์–ธํŠธ ์ปดํฌ๋„ŒํŠธ ๊ด€๋ จ ์ปดํฌ๋„ŒํŠธ๋ฅผ ์ƒ์„ฑํ•œ ํ›„ RootLayout์—์„œ ๊ฐ์‹ธ๋Š” ํ˜•์‹์œผ๋กœ ์‚ฌ์šฉ์€ ๊ฐ€๋Šฅํ•˜๋‹ค๋Š” ํ•ด๊ฒฐ์ฑ…์„ ๋ฐœ๊ฒฌํ–ˆ๋‹ค.

2๋ฒˆ์— ๊ด€ํ•ด์„œ๋Š” layout.tsx๋Š” body๋ฅผ ์ž๋™์œผ๋กœ ์ƒ์„ฑํ•˜์ง€ ์•Š๊ธฐ ๋•Œ๋ฌธ์— ๊ผญ body๋ฅผ ๊ฐ™์ด ์ž‘์„ฑํ•ด์ค˜์•ผ ํ•œ๋‹ค. (์ฐธ๊ณ )

ClientComponentContainer.tsx
layout.tsx

์œ„์™€ ๊ฐ™์ด ๊ธฐ์กด์— Layout.tsx์—์„œ ์‚ฌ์šฉํ–ˆ๋˜ ์ฝ”๋“œ๋“ค์„ 'use clinet' ๋ฌธ๋ฒ•๊ณผ ํ•จ๊ป˜ ์ž‘์„ฑ ํ›„ layout.tsx์— ์ ์šฉ์‹œ์ผœ ์ฃผ์—ˆ๋‹ค.

(ClientComponentContainer.tsx๋ฅผ ๋ณด๋ฉด StyledComponentsResistry ์ปดํฌ๋„ŒํŠธ๊ฐ€ ์žˆ๋Š”๋ฐ ์ด์— ๊ด€ํ•œ ๋‚ด์šฉ์€ ๋’ค์—์„œ ๋‹ค๋ฃฌ๋‹ค.)

 

๊ทธ๋ฆฌ๊ณ  ๊ธฐ์กด์—๋Š” next/head๋ฅผ ์ด์šฉํ•ด์„œ meta tag๋ฅผ ์ž‘์„ฑํ•ด ์คฌ์ง€๋งŒ Next 14 ์ดํ›„์—์„œ๋Š” nextjs์˜ metadata๋ฅผ ํ†ตํ•ด ์ž‘์„ฑํ•  ์ˆ˜ ์žˆ๋‹ค.


Next.js์—๋Š” ์• ํ”Œ๋ฆฌ์ผ€์ด์…˜ ๋ฉ”ํƒ€๋ฐ์ดํ„ฐ(์˜ˆ: HTML ๋‚ด์˜ meta ๋ฐ link ํƒœ๊ทธ )๋ฅผ ์ •์˜ํ•˜๋Š” ๋ฐ ์‚ฌ์šฉํ•  ์ˆ˜ ์žˆ๋Š” ๋ฉ”ํƒ€๋ฐ์ดํ„ฐ API๊ฐ€ ์žˆ์Šต๋‹ˆ๋‹ค. head ์š”์†Œ) ๊ฐœ์„ ๋œ SEO ๋ฐ ์›น ๊ณต์œ  ๊ฐ€๋Šฅ์„ฑ.
ํ•ด๋‹น ๊ณต์‹๋ฌธ์„œ

ํ•ด๋‹น ๊ณต์‹๋ฌธ์„œ ์ค‘ ์ผ๋ถ€ ๋ฐœ์ทŒ

์ฃผ์˜ ์‚ฌํ•ญ์œผ๋กœ metadata๊ฐ€ ์ž‘์„ฑ๋œ ๊ณณ์€ ๊ผญ ์„œ๋ฒ„ ์ปดํฌ๋„ŒํŠธ์—์„œ ์ž‘์„ฑ๋˜์–ด์•ผ ํ•œ๋‹ค!

ํด๋ผ์ด์–ธํŠธ ์ปดํฌ๋„ŒํŠธ์—์„œ ์ž‘์„ฑํ•˜๊ฒŒ ๋˜๋ฉด ์˜ค๋ฅ˜๋ฅผ ๋งž์ดํ•  ์ˆ˜ ์žˆ์„ ๊ฒƒ์ด๋‹ค.

๊ทธ๋ž˜์„œ Mbti ํŽ˜์ด์ง€๋„ Mbti ์ปดํฌ๋„ŒํŠธ๋Š” ํด๋ผ์ด์–ธํŠธ ์ปดํฌ๋„ŒํŠธ๋กœ ์ž‘์„ฑ๋˜์–ด์•ผ ํ•˜๊ธฐ ๋•Œ๋ฌธ์— import ํ•˜๋Š” ๋ฐฉ์‹์œผ๋กœ ์ปดํฌ๋„ŒํŠธ๋ฅผ ์ž‘์„ฑํ–ˆ๋‹ค.

app/Mbti/page.tsx

app/Mbti/page.tsx

 

SSG

์ด์ œ ๊ธฐ์กด mbti ํƒ€์ž…๋“ค์— ๋Œ€ํ•œ ํŽ˜์ด์ง€์— ๋Œ€ํ•ด์„œ SSG๋ฅผ ์ ์šฉํ–ˆ๋˜ getStaticPaths๋ฅผ ์ ์šฉํ–ˆ๋˜ ๊ฒƒ๋“ค์„ ๋ณ€๊ฒฝํ•  ์ฐจ๋ก€๋‹ค.

 

๋จผ์ € Next 13 ์ดํ›„ app router์— ๋Œ€ํ•ด์„œ๋Š” generateStaticParams๋ฅผ ํ†ตํ•ด SSG๋ฅผ ๊ตฌํ˜„ํ•  ์ˆ˜ ์žˆ๋‹ค.

๊ทธ๋ฆฌ๊ณ  ์ด์ „์—๋„ ๋™์ ์œผ๋กœ metadata๋ฅผ ์„ค์ •ํ•ด ์คฌ๊ธฐ ๋•Œ๋ฌธ์— ์ด๋ฒˆ์—๋Š” generateMetadata๋ฅผ ํ†ตํ•ด ๋™์ ์œผ๋กœ metadata๋ฅผ ์ ์šฉํ•  ์ˆ˜ ์žˆ๋‹ค.

app/mbti/[type]/page.tsx

app/mbti/[type]/page.tsx

์—ฌ๊ธฐ์„œ ๋จผ์ € ์ฃผ์˜ํ•  ์ ์€ ํด๋” ๋ช…์„ [type]์œผ๋กœ ์ง€์ •ํ–ˆ๊ธฐ ๋•Œ๋ฌธ์— generateStaticParams์˜ return ๊ฐ์ฒด ์ด๋ฆ„๋„ type์ด๋ผ๋Š” ์ด๋ฆ„์œผ๋กœ ์ง€์ •ํ•ด์•ผ ํ•œ๋‹ค.

๊ธฐ์กด์— View๋ฅผ ๋‹ด๋‹นํ•˜๋˜ ์ฝ”๋“œ๋“ค์€ ํด๋ผ์ด์–ธํŠธ ์ปดํฌ๋„ŒํŠธ์ด๊ธฐ ๋•Œ๋ฌธ์— Type ์ปดํฌ๋„ŒํŠธ๋กœ ๋”ฐ๋กœ ๋ถ„๋ฆฌํ•ด์„œ ๋ถˆ๋Ÿฌ์™€์„œ ์‚ฌ์šฉํ–ˆ๋‹ค.

styled-components ์ถ”๊ฐ€ ๋กœ์ง

๊ธฐ์กด์— Next์—์„œ styled-components๋ฅผ ์ ์šฉํ•˜๊ธฐ ์œ„ํ•ด์„œ _document.tsx์— ์ถ”๊ฐ€ ์ฝ”๋“œ๋ฅผ ์ž‘์„ฑํ•ด์ค˜์•ผ ํ–ˆ๋‹ค.

ํ•˜์ง€๋งŒ app router์—์„œ _document.tsx ํŒŒ์ผ์€ ์กด์žฌํ•˜์ง€ ์•Š๊ธฐ ๋•Œ๋ฌธ์— ์ถ”๊ฐ€์ ์ธ ๋กœ์ง์ด ํ•„์š”ํ•˜๋‹ค.

ํ•ด๋‹น ๊ณต์‹๋ฌธ์„œ ์ผ๋ถ€ ๋ฐœ์ทŒ

ํ•ด๋‹น ๊ณต์‹๋ฌธ์„œ ๊ทธ๋Œ€๋กœ ๋”ฐ๋ผ ํ•˜๋ฉด ์ž˜ ์ ์šฉ์ด ๋˜์—ˆ๊ธฐ ๋•Œ๋ฌธ์— ๋”ฑํžˆ ์–ด๋ ค์šด ์ ์€ ์—†์—ˆ๋‹ค.

 

๋จผ์ € libํด๋”๋ฅผ ์ƒ์„ฑ ํ›„ registry.tsx ํŒŒ์ผ์„ ์ƒ์„ฑํ–ˆ๋‹ค.

'use client';

import React, { useState } from 'react';
import { useServerInsertedHTML } from 'next/navigation';
import { ServerStyleSheet, StyleSheetManager } from 'styled-components';

export default function StyledComponentsRegistry({
  children,
}: {
  children: React.ReactNode;
}) {
  // Only create stylesheet once with lazy initial state
  // x-ref: https://reactjs.org/docs/hooks-reference.html#lazy-initial-state
  const [styledComponentsStyleSheet] = useState(() => new ServerStyleSheet());

  useServerInsertedHTML(() => {
    const styles = styledComponentsStyleSheet.getStyleElement();
    styledComponentsStyleSheet.instance.clearTag();
    return <>{styles}</>;
  });

  if (typeof window !== 'undefined') return <>{children}</>;

  return (
    <StyleSheetManager sheet={styledComponentsStyleSheet.instance}>
      {children}
    </StyleSheetManager>
  );
}

์œ„ ์ฝ”๋“œ๋„ ํด๋ผ์ด์–ธํŠธ ์ปดํฌ๋„ŒํŠธ์ด๊ธฐ ๋•Œ๋ฌธ์— ์ตœ์ƒ์œ„(layout.tsx)์— ๋ฐฐ์น˜ํ•˜๊ธฐ ์ „์— ์•„๋ž˜์™€ ๊ฐ™์ด ClientComponentContainer.tsx์— ๋„ฃ์–ด์คฌ๋‹ค.

'use client';
import styled from 'styled-components';
import ThemeProvider from 'context/ThemeProvider';
import GlobalStyle from 'styles/GlobalStyle';
import Footer from 'components/common/Footer';
import DarkModeBtn from 'components/Buttons/DarkModeBtn';
import StyledComponentsRegistry from 'lib/registry';
import { size } from 'styles/theme';
const Wrapper = styled.div`
  width: 100%;
  max-width: ${size.mobile};
  min-height: 100vh;
  margin: auto;
  padding: ${({ theme }) => theme.padding.base};
  display: flex;
  flex-direction: column;
  justify-content: center;
`;

interface IClientComponentContainerProps {
  children: React.ReactNode;
}

function ClientComponentContainer({
  children,
}: IClientComponentContainerProps) {
  return (
    <StyledComponentsRegistry>
      <ThemeProvider>
        <Wrapper>
          <GlobalStyle />
          <DarkModeBtn />
          {children}
          <Footer />
        </Wrapper>
      </ThemeProvider>
    </StyledComponentsRegistry>
  );
}

export default ClientComponentContainer;

 

๊ทธ๋ฆฌ๊ณ  ์•„๋ž˜์™€ ๊ฐ™์ด next.config.ts์— ๊ธฐ์กด์— ์žˆ๋˜ ์ฝ”๋“œ๋ฅผ ์‚ญ์ œํ–ˆ๋‹ค๊ฐ€ classname did not match ์˜ค๋ฅ˜๊ฐ€ ๋ฐœ์ƒํ–ˆ๋‹ค.

 compiler: {
    // ssr and displayName are configured by default
    styledComponents: true,
  },

์œ„ ์ฝ”๋“œ๋Š” ๊ธฐ์กด๊ณผ ๋™์ผํ•˜๊ฒŒ ํ•„์š”ํ•˜๋ฏ€๋กœ ๊ผญ ์ž‘์„ฑํ•ด ์ฃผ๋„๋ก ํ•œ๋‹ค.

๊ตฌ๊ธ€ ์• ๋„๋ฆฌํ‹ฑ์Šค ์ฝ”๋“œ ๋ณ€๊ฒฝ

app router๋กœ ๋ณ€๊ฒฝ๋˜๋ฉด์„œ ๊ตฌ๊ธ€ ์• ๋„๋ฆฌํ‹ฑ์Šค์˜ ์ฝ”๋“œ์˜ ์ˆ˜์ •๋„ ํ•„์š”ํ–ˆ๋‹ค. (์ฐธ๊ณ ํ•œ ๋ธ”๋กœ๊ทธ)

๊ธฐ์กด์—๋Š” _document.tsx์— ๊ด€๋ จ ์ฝ”๋“œ๋ฅผ ์ž‘์„ฑํ–ˆ์ง€๋งŒ ์ง€๊ธˆ ์ƒํ™ฉ์—์„œ๋Š” Script ์ปดํฌ๋„ŒํŠธ๋ฅผ ๋”ฐ๋กœ ์ž‘์„ฑ ํ›„ ๋ถˆ๋Ÿฌ์˜ค๋Š” ๋ฐฉ์‹์œผ๋กœ ์‚ฌ์šฉํ–ˆ๋‹ค.

app/GoogleAnalytics.tsx

ํ•ด๋‹น ์ปดํฌ๋„ŒํŠธ๋ฅผ ์ž‘์„ฑ ํ›„ layout.tsx์˜ body ์‚ฌ์ด ์ƒ๋‹จ์— ๋„ฃ์–ด์คŒ์œผ๋กœ์จ ์‰ฝ๊ฒŒ ์ ์šฉํ•  ์ˆ˜ ์žˆ์—ˆ๋‹ค.

ํ•˜์ง€๋งŒ ๋ฌธ์ œ์ ์ด ์žˆ์—ˆ๋‹ค. 

์ด์ „์—๋Š” ์ด๋ฒคํŠธ ์ถ”์ ์„ ํ•˜๋Š” ๊ด€๋ จ ์ฝ”๋“œ๊ฐ€ ์กด์žฌํ–ˆ๋‹ค.

useEffect(() => {
    if (process.env.NODE_ENV === 'production') {
      const handleRouteChange = (url: URL) => {
        gtag.pageview(url);
      };
      router.events.on('routeChangeComplete', handleRouteChange);
      router.events.on('hashChangeComplete', handleRouteChange);
      return () => {
        router.events.off('routeChangeComplete', handleRouteChange);
        router.events.off('hashChangeComplete', handleRouteChange);
      };
    }
  }, [router.events]);

์ด์ „์—๋Š” ์œ„์™€ ๊ฐ™์ด userRouter๋ฅผ ์ด์šฉํ•ด์„œ router.evnets๋ฅผ ํ†ตํ•ด ์ด๋ฒคํŠธ๋ฅผ ์ถ”์ ํ•  ์ˆ˜ ์žˆ์—ˆ๋‹ค.

 

ํ•˜์ง€๋งŒ Next 13์—์„œ๋Š” useRouter์˜ events๋Š” ์‚ฌ๋ผ์กŒ๋‹ค...?

 

https://github.com/vercel/next.js/discussions/41934

https://github.com/vercel/next.js/discussions/42016

์œ„์˜ ๋งํฌ(issue)๋“ค์—์„œ ์•„์ฃผ ๋งŽ์€ ์‚ฌ๋žŒ๋“ค์˜ ๋Œ๋ ค๋†“์œผ๋ผ๋Š” ํ˜„์žฅ์„ ๊ณก์†Œ๋ฆฌ(?)๋ฅผ ๋“ค์„ ์ˆ˜ ์žˆ๋‹ค... (๋‹ค๋“ค ์—„์ฒญ ํ™”๊ฐ€ ๋‚˜์žˆ๋Š” ๋“ฏํ•œ ๋Š๋‚Œ..)

์ •๋ง ๋งŽ์€ ์‚ฌ๋žŒ๋“ค์ด ์›ํ•˜๊ณ  ์žˆ๋Š” ๊ธฐ๋Šฅ์ธ ๊ฒƒ ๊ฐ™๋‹ค.

 

์—ด์‹ฌํžˆ ์ฐพ์•„๋ณธ ๊ฒฐ๊ณผ router.events๋ฅผ ๋Œ€์ฒดํ•˜๋Š” ๊ธฐ๋Šฅ์€ ์—†๋‹ค.

๋Œ€์‹ ์— ๋Œ€์•ˆ์€ ์žˆ์—ˆ๋‹ค.

  1. page router ์‚ฌ์šฉ
  2. router.evnets์™€ ์œ ์‚ฌํ•˜๊ฒŒ ๋™์ž‘ํ•˜๋Š” ์ฝ”๋“œ ์ง์ ‘ ๊ตฌํ˜„ ๋˜๋Š” ์—ฌ๋Ÿฌ ์‚ฌ๋žŒ์ด ์œ ์‚ฌํ•˜๊ฒŒ ๋งŒ๋“ค์–ด ๋†“์€ ์ฝ”๋“œ ๊ฐ€์ ธ๋‹ค ์“ฐ๊ธฐ

๊ณ ๋ฏผ์„ ํ–ˆ๋‹ค.

1๋ฒˆ์— ๋Œ€ํ•ด์„œ๋Š” app router๋ฅผ ์‚ฌ์šฉํ•˜๋Š” ๊ฒƒ์ด ๋ชฉ์ ์ด์—ˆ๊ธฐ ๋•Œ๋ฌธ์— PASS..

2๋ฒˆ์— ๋Œ€ํ•ด์„œ๋Š” issue๋ฅผ ํ™•์ธํ•ด ๋ณด๋ฉด ์œ ์‚ฌํ•˜๊ฒŒ ๋™์ž‘ํ•˜๋„๋ก ๊ตฌํ˜„ํ•ด์„œ ๊ณต์œ ํ•œ ์ฝ”๋“œ๊ฐ€ ์žˆ๊ธด ํ•˜๋‹ค.

ํ•˜์ง€๋งŒ ๋„ˆ๋ฌด ๋งŽ์€ ๋ถ€๋ฅ˜(?)์˜ ์ฝ”๋“œ๊ฐ€ ์žˆ๊ณ  ์ฝ”๋“œ ์–‘๋„ ์ ์ง€ ์•Š๋‹ค๊ณ  ํŒ๋‹จ๋˜์—ˆ๋‹ค.

 

๊ฒฐ๋ก ์€ ๋‚ด ํ”„๋กœ์ ํŠธ์— ๊ตฌ๊ธ€ ์• ๋„๋ฆฌํ‹ฑ์Šค๋Š” ์ด ํŽ˜์ด์ง€ ๋ทฐ์ˆ˜๋งŒ ํ™•์ธํ•ด๋„ ๊ดœ์ฐฎ๊ธฐ ๋•Œ๋ฌธ์— ์ด๋ฒคํŠธ ๊ด€๋ จ ์ฝ”๋“œ๋Š” ์‚ญ์ œํ•˜๊ธฐ๋กœ ๊ฒฐ์ •ํ–ˆ๋‹ค.

 

๊ทธ๋ฆฌ๊ณ  ๊ธฐ์กด useRouter๋ฅผ ์‚ฌ์šฉํ•˜๋˜ ๊ณณ์—์„œ NextRouter was not mounted ์—๋Ÿฌ๊ฐ€ ๋ฐœ์ƒํ–ˆ๋‹ค.

์ด ์—๋Ÿฌ๊ฐ€ ๋ฐœ์ƒํ•œ ์ด์œ ๋Š” 'use client'๋ฅผ ๋ช…์‹œํ•œ ๊ณณ. ์ฆ‰, ํด๋ผ์ด์–ธํŠธ ์ปดํฌ๋„ŒํŠธ์—์„œ useRouter๋ฅผ ๋ถˆ๋Ÿฌ์˜ฌ ๋•Œ๋Š” ์•„๋ž˜์™€ ๊ฐ™์ด ๋ถˆ๋Ÿฌ์™€์•ผ ํ•œ๋‹ค.

'use client';

// ๋ณ€๊ฒฝ ์ „
import { useRouter } from 'next/router';
// ๋ณ€๊ฒฝ ํ›„
import { useRouter } from 'next/navigation';

Link ๋ณ€๊ฒฝ

Link  ๊ด€๋ จํ•ด์„œ๋Š” Link์˜ ์ž์‹์œผ๋กœ ๊ผญ ๊ฐ์‹ธ์ค˜์•ผ ํ–ˆ๋˜ aํƒœ๊ทธ๋Š” ์ž‘์„ฑํ•˜์ง€ ์•Š๋„๋ก ๋ณ€๊ฒฝ๋˜์—ˆ๋‹ค.

// ๋ณ€๊ฒฝ ์ „
return (
    <SButton fontColor="laime" borderColor="laime">
      <Link href="/mbti">
        <a>๋ชจ๋“  ์œ ํ˜• ๋ณด๋Ÿฌ๊ฐ€๊ธฐ</a>
       ๋ชจ๋“  ์œ ํ˜• ๋ณด๋Ÿฌ๊ฐ€๊ธฐ
      </Link>
    </SButton>
  );

// ๋ณ€๊ฒฝ ํ›„
return (
    <SButton fontColor="laime" borderColor="laime">
      <Link href="/mbti">
       	๋ชจ๋“  ์œ ํ˜• ๋ณด๋Ÿฌ๊ฐ€๊ธฐ
      </Link>
    </SButton>
  );

 

defaultProps will be removed ๊ฒฝ๊ณ  ๋ฐœ์ƒ

์ด ๋ฌธ์ œ๋Š” React๋ฅผ ์—…๋ฐ์ดํŠธํ•˜๋ฉด์„œ ๋ฐœ์ƒํ•œ ๊ฒฝ๊ณ ์ด๋‹ค.

 

"Warning: App: Support for defaultProps will be removed from function components in a future major release. Use JavaScript default parameters instead."


์•„์ง defaultProps์˜ Deprecated๋Š” RFC ๋‹จ๊ณ„์ด๊ธด ํ•˜๋‚˜, ๋ฉ”์‹œ์ง€์˜ ๋‚ด์šฉ์œผ๋กœ ๋ณด์•„ ๋‹ค์Œ ๋ฉ”์ด์ € ๋ฒ„์ „(React 19)๋ถ€ํ„ฐ ์‚ฌ์šฉ์ด ๋ถˆ๊ฐ€๋Šฅํ•  ๊ฒƒ์œผ๋กœ ๋ณด์ธ๋‹ค.

defaultProps๋Š” class ์ปดํฌ๋„ŒํŠธ์—์„œ ์œ ์šฉํ–ˆ์ง€๋งŒ, ํ•จ์ˆ˜ํ˜• ์ปดํฌ๋„ŒํŠธ์—์„œ๋Š” ES6์˜ default parameters๋กœ ๋Œ€์ฒด๊ฐ€ ๊ฐ€๋Šฅํ•˜๊ฒŒ ๋˜์—ˆ๋‹ค.

 

์ด ๊ฒฝ๊ณ ์ฐฝ์„ ํ•ด๊ฒฐํ•˜๊ธฐ ์œ„ํ•ด Button ์ปดํฌ๋„ŒํŠธ์˜ ๊ธฐ์กด์— defaultProps๋กœ ์ž‘์„ฑ๋˜์–ด ์žˆ๋˜ ๋ถ€๋ถ„์„ default parameters๋กœ ๋ณ€๊ฒฝํ•ด ์ฃผ์—ˆ๋‹ค.

๋ณ€๊ฒฝ๋œ Button ์ปดํฌ๋„ŒํŠธ ์ฝ”๋“œ

 

next/font ์ ์šฉ

๊ธฐ์กด์— ํฐํŠธ๋Š” @fontsource/noto-sans-kr๋กœ ํฐํŠธ๋ฅผ ์ ์šฉํ•ด ์คฌ๋‹ค.

ํ•ด๋‹น ๊ณต์‹๋ฌธ์„œ ์ผ๋ถ€ ๋ฐœ์ทŒ

์œ„ ๋‚ด์šฉ์€ ํ•ด๋‹น ๊ณต์‹๋ฌธ์„œ์˜ ์ผ๋ถ€์ด๋‹ค.

 

๊ฐ„๋‹จํ•˜๊ฒŒ ๋งํ•˜๋ฉด next/font๋ฅผ ์‚ฌ์šฉํ•˜๋ฉด ์ตœ์ ํ™”๋„ ๊ฐ€๋Šฅํ•˜๊ณ  ๋ณ„๋‹ค๋ฅธ ๋„คํŠธ์›Œํฌ ์š”์ฒญ๋„ ํ•„์š”ํ•˜์ง€ ์•Š์•„์„œ ๋น ๋ฅด๊ฒŒ ๋กœ๋“œ๋ฅผ ํ•  ์ˆ˜ ์žˆ๋‹ค๋Š” ๋‚ด์šฉ์ด๋‹ค.

 

๊ทธ๋ž˜์„œ ๊ธฐ์กด์— ์„ค์น˜ํ–ˆ๋˜  "@fontsource/noto-sans-kr": "^4.5.12"๋Š” ์ด์ œ ํ•„์š”ํ•˜์ง€ ์•Š๊ธฐ ๋•Œ๋ฌธ ๋ฐ”๋กœ ์ œ๊ฑฐํ•ด ์ฃผ๊ณ  ์ง„ํ–‰ํ–ˆ๋‹ค.

 

๋‚ด ํ”„๋กœ์ ํŠธ์—์„œ๋Š” GlobalStyles.ts์—์„œ ํฐํŠธ๋ฅผ ์ ์šฉํ–ˆ๊ธฐ ๋•Œ๋ฌธ์— ํ•ด๋‹น ํŒŒ์ผ์—์„œ ์ง„ํ–‰ํ–ˆ๋‹ค.

ํฐํŠธ ์ ์šฉ

์นด์นด์˜คํ†ก ๊ณต์œ ํ•˜๊ธฐ ๊ด€๋ จ ์ฝ”๋“œ ๋ณ€๊ฒฝ

์€๊ทผํžˆ ์• ๋ฅผ ๋จน์—ˆ๋˜ ๋ถ€๋ถ„์ด๋‹ค.

๋จผ์ € ๊ธฐ์กด์—๋Š” _document.tsx์— kakao ๊ด€๋ จ ์Šคํฌ๋ฆฝํŠธ๋ฅผ ์ถ”๊ฐ€ํ•ด ์ฃผ๊ณ  Layout.tsx์—์„œ useEffect๋ฅผ ํ†ตํ•ด Kakao.init ๋ฉ”์„œ๋“œ๋ฅผ ์‚ฌ์šฉํ•ด์„œ ์ ์šฉํ•ด ์ฃผ์—ˆ๋‹ค. (๋‹น์‹œ ๊ด€๋ จ ๋ธ”๋กœ๊ทธ ํฌ์ŠคํŒ…)

 

๊ทธ๋ž˜์„œ app router์— ๋งž๊ฒŒ ํ•ด๋‹น ๋ถ€๋ถ„์€ layout.tsx์— ๊ด€๋ จ ์Šคํฌ๋ฆฝํŠธ๋ฅผ ์ถ”๊ฐ€ํ•˜๊ณ  ClientComponentContainer.tsx์— init ์ ์šฉํ•˜๋ฉด ์‰ฝ๊ฒŒ ๋๋‚  ์ค„ ์•Œ์•˜๋‹ค.

 

ํ•˜์ง€๋งŒ kakao.init์ด ์กด์žฌํ•˜์ง€ ์•Š๋Š”๋‹ค๋Š” ์˜ค๋ฅ˜๋งŒ ๊ณ„์† ๋ฐœ์ƒ๋˜์—ˆ๋‹ค.

 

kakao cannot read properties of undefined (reading 'init')

 

๋‚˜์™€ ๋งค์šฐ ๊ฐ™์€ ์ƒํ™ฉ์„ ๋งˆ์ฃผํ•œ ๋ถ„์ด ๊ณ„์…จ๋‹ค. ์—๋Ÿฌ ๋‚ด์šฉ ๋ฐ ๊ณผ์ • 100% ๋™์ผ! (๋ธ”๋กœ๊ทธ ๋งํฌ)

์•ˆํƒ€๊น๊ฒŒ๋„ ํ•ด๋‹น ๋‚ด์šฉ์˜ ํ•ด๊ฒฐ ๋ฐฉ๋ฒ•์œผ๋กœ ํ•ด๊ฒฐ๋˜์ง€๋Š” ์•Š๊ณ  ๊ฐ™์€ ์—๋Ÿฌ๋งŒ ๋งž์ดํ•  ๋ฟ์ด์—ˆ๋‹ค.

 

์ฐจ๊ทผ์ฐจ๊ทผ ์นด์นด์˜ค ๊ด€๋ จ ์Šคํฌ๋ฆฝํŠธ๊ฐ€ ์ž˜ ๋ถˆ๋Ÿฌ์™€์กŒ๋Š”์ง€ ํ™•์ธํ–ˆ๋‹ค.

๊ทธ๋ž˜์„œ useEffect ํ•  ๋•Œ(init ์ ์šฉํ•  ๋•Œ) window ๊ฐ์ฒด๋ฅผ ์ถœ๋ ฅํ•ด์„œ window์— Kakao ๊ฐ์ฒด๊ฐ€ ๋‹ด๊ฒผ๋Š”์ง€ ํ™•์ธํ•ด ๋ดค๋‹ค.

ํ•˜์ง€๋งŒ ๊ฒฐ๊ณผ๋Š” undefinend..

๊ด€๋ จ ์ฝ”๋“œ๋Š” ์Šคํฌ๋ฆฝํŠธ ์ ์šฉ ์ฝ”๋“œ๋Š” ์•„๋ž˜์™€ ๊ฐ™๋‹ค.

<script src="https://developers.kakao.com/sdk/js/kakao.min.js" ></script>

์Šคํฌ๋ฆฝํŠธ๊ฐ€ ๋กœ๋“œ๊ฐ€ ๋˜๊ธฐ ์ „์— init์„ ์ง„ํ–‰ํ•˜๋ ค ํ•ด์„œ ๊ทธ๋Ÿฐ ๊ฑด ๊ฐ€ ์‹ถ์—ˆ๋‹ค. (์‚ฌ์‹ค ์ด๊ฒŒ ๋งž๋‹ค.)

 

๊ทธ๋ž˜์„œ defer ์˜ต์…˜๋„ ์ฃผ๊ณ  async ์˜ต์…˜๋„ ์ฃผ๊ณ  ๋‹ค ํ•ด๋ดค๋Š”๋ฐ ๊ฒฐ๊ณผ๋Š” ๋™์ผํ–ˆ๋‹ค.

๋‚˜์ค‘์— ์•Œ๊ณ  ๋ณด๋‹ˆ๊นŒ ์นด์นด์˜ค ๊ด€๋ จ ์Šคํฌ๋ฆฝํŠธ๋Š” async๋กœ ์•Œ์•„์„œ ์ ์šฉ๋œ๋‹ค๊ณ  ํ•œ๋‹ค.

 

๋„๋Œ€์ฒด ์•ˆ ๋  ์ด์œ ๊ฐ€ ์—†๋Š”๋ฐ ์™œ ์ž๊พธ ๋ชป ์ฐพ๋Š” ๊ฒƒ์ธ์ง€ ์ง„์งœ ๋„ˆ๋ฌด ๋‹ต๋‹ตํ–ˆ๋‹ค.

์ด๋•Œ๊นŒ์ง„ ์ด์ „์ด๋ž‘ ํฌ๊ฒŒ ๋‹ฌ๋ผ์ง„ ์ฝ”๋“œ ์ž์ฒด๊ฐ€ ์—†์—ˆ๊ธฐ ๋•Œ๋ฌธ์ด๋‹ค.

 

์ง€ํ‘ธ๋ผ๊ธฐ๋ผ๋„ ์žก๋Š” ์‹ฌ์ •์œผ๋กœ ์ •๋ง ํ˜น์‹œ๋‚˜ ํ•ด์„œ ๋ธŒ๋ผ์šฐ์ € ์ฝ˜์†”์ฐฝ์— window.Kakao๋ฅผ ์ฐ์–ด๋ณด๋‹ˆ๊นŒ ๊ฐ์ฒด๊ฐ€ ์ถœ๋ ฅ์ด ๋๋‹ค.

์ด ๊ฒฐ๊ณผ๋ฅผ ๋ณด๊ณ  ๋ญ”๊ฐ€ ๋” ์‹ฌ๋ž€ํ•ด์กŒ๋‹ค.

๋‚ด๊ฐ€ ์˜ˆ์ƒํ•˜๋˜ ๋ฌธ์ œ๊ฐ€ ๋งž๊ธด ํ•˜๋Š”๋ฐ ๋„๋Œ€์ฒด ์–ด๋–ป๊ฒŒ ํ•ด๊ฒฐํ•ด์•ผ ํ•˜๋‚˜ ์‹ถ์—ˆ๋‹ค.

useEffect์˜ ๋‘ ๋ฒˆ์งธ ๋ฐฐ์—ด ์ธ์ž๋กœ window๋„ ๋„ฃ์–ด๋ณด๊ณ  window.Kakao๋„ ๋„ฃ์–ด๋ณด๊ณ  ๋ชจ๋‘ ์‹œ๋„ํ•ด ๋ณด์•˜์ง€๋งŒ init์ด ์ ์šฉ์ด ์•ˆ๋˜์—ˆ๋‹ค.

 

๊ฒฐ๊ตญ GPT์—๊ฒŒ ๋ฌผ์–ด๋ดค๋‹ค.

์ด์ „๊นŒ์ง€ GPT์—๊ฒŒ ๋ฌผ์–ด๋ณด์ง€ ์•Š์€ ์ด์œ ๊ฐ€ Next 13 ์ดํ›„ ๋‚ด์šฉ์€ GPT๊ฐ€ ์•Œ์ง€ ๋ชปํ•ด์„œ ์‚ฌ์šฉํ•˜๊ณ  ์‹ถ์–ด๋„ ์‚ฌ์šฉํ•˜์ง€ ๋ชปํ–ˆ์ง€๋งŒ

์ด ๋ถ€๋ถ„์€ Next 13๊ณผ ๋ณ„๊ฐœ์ธ ๋ถ€๋ถ„์ด์–ด์„œ ๋ฌผ์–ด๋ดค๋‹ค.

 

์›์ธ์€ ๋‚ด๊ฐ€ ์ƒ๊ฐํ•˜๋˜ ์Šคํฌ๋ฆฝํŠธ๊ฐ€ ์ ์šฉ์ด ๋˜๊ธฐ ์ „์— init์„ ํ˜ธ์ถœํ•ด์„œ ์ผ์–ด๋‚œ ๋ฌธ์ œ๊ฐ€ ๋งž์•˜๊ณ ,

setInterval์„ ์ ์šฉํ•ด ๋ณด๋ผ๊ณ  ํ–ˆ๋‹ค.

useEffect(() => {
    const interval = setInterval(() => {
      if (window.Kakao) {
        window.Kakao.init(process.env.NEXT_PUBLIC_KAKAO_API_KEY);
        clearInterval(interval);
      }
    }, 100);
}, []);

์ฒ˜์Œ GPT๊ฐ€ ์ œ์•ˆํ•œ ์ฝ”๋“œ์˜€๋‹ค.

์ด๋ ‡๊ฒŒ ํ•˜๋‹ˆ๊นŒ ์ง„์งœ ์ œ๋Œ€๋กœ ๋™์ž‘ํ•˜๊ธด ํ–ˆ๋‹ค.

 

ํ•˜์ง€๋งŒ ์•„๋ฌด๋ฆฌ ์ฐพ์•„๋ด๋„ ์ด๋Ÿฐ ์‹์œผ๋กœ ์ฝ”๋“œ๋ฅผ ์ ์šฉํ•œ ์ƒํ™ฉ์€ ๋ณด์ง€ ๋ชปํ–ˆ๊ณ  ์ด๊ฒŒ setinterval์„ ์‚ฌ์šฉํ•œ ์ฝ”๋“œ๋ณด๋‹ค ๋” ๋‚˜์€ ํ•ด๊ฒฐ์ฑ…์ด ์žˆ์„ ๊ฒƒ์ด๋ผ๊ณ  ์ƒ๊ฐํ–ˆ๋‹ค.

 

๋‹ค์‹œ ๋ฌผ์–ด๋ดค๋‹ค. ๋‹ค๋ฅธ ๋ฐฉ๋ฒ•์€ ์—†๋Š” ๊ฑฐ๋ƒ๊ณ . ์•„๋ž˜์™€ ๊ฐ™์€ ์ฝ”๋“œ๋ฅผ ์ œ์‹œํ–ˆ๋‹ค.

const loadKakaoSDK = () => {
  return new Promise((resolve) => {
    const script = document.createElement('script');
    script.src = 'https://developers.kakao.com/sdk/js/kakao.js';
    script.onload = () => resolve();
    document.body.appendChild(script);
  });
};

useEffect(() => {
  const initKakao = async () => {
    await loadKakaoSDK();
    if (window.Kakao.isInitialized() === false) {
      window.Kakao.init(process.env.NEXT_PUBLIC_KAKAO_API_KEY);
    }
  };
  initKakao();
}, []);

์œ„ ์ฝ”๋“œ๋ฅผ ํ™œ์šฉํ•˜์ง€๋Š” ์•Š์•˜์ง€๋งŒ ์—ฌ๊ธฐ์„œ ๋„ˆ๋ฌด ํฐ ํžŒํŠธ๋ฅผ ์–ป์–ด๋ฒ„๋ ธ๋‹ค.

๋ฐ”๋กœ onLoad ์†์„ฑ์ด ์žˆ์—ˆ๋˜ ๊ฒƒ์ด์—ˆ๋‹ค.

๊ผญ useEffect๋ฅผ ์‚ฌ์šฉํ•˜์ง€ ์•Š์•„๋„ ์Šคํฌ๋ฆฝํŠธ๊ฐ€ ๋ถˆ๋Ÿฌ์™€์ง€๋ฉด ๋ฐ”๋กœ init์„ ์ ์šฉ์‹œ์ผœ ๋ฒ„๋ฆฌ๋ฉด ๋˜๋Š” ๊ฒƒ์ด์—ˆ๋‹ค....

'use client';
import Script from 'next/script';

function KakaoScript() {
  const onLoad = () => {
    window.Kakao.init(process.env.NEXT_PUBLIC_KAKAO_API_KEY);
  };

  return (
    <Script
      src="https://developers.kakao.com/sdk/js/kakao.js"
      async
      onLoad={onLoad}
    />
  );
}

export default KakaoScript;

์ด๋ ‡๊ฒŒ ํ•˜๊ณ  ํ•ด๋‹น ์ปดํฌ๋„ŒํŠธ๋ฅผ layout.tsx์˜ body ํ•˜๋‹จ์— ๋ฐฐ์น˜ํ–ˆ๋‹ค.

 

์•„์ฃผ ๊น”๋”ํ•˜๊ฒŒ ์ž˜ ๋™์ž‘ํ–ˆ๋‹ค.

 

์ด๋ ‡๊ฒŒ ์ ์šฉํ•œ ์‚ฌ๋ก€๋Š” ๋ณด์ง€ ๋ชปํ•˜๊ณ  Next 13 ์ดํ›„ ์ž๋ฃŒ๊ฐ€ ๋งŽ์ด ์—†๊ธฐ ๋•Œ๋ฌธ์— ๋„ˆ๋ฌด ํž˜๋“ค๊ฒŒ ํ•ด๊ฒฐํ–ˆ๋‹ค.

onLoad๋กœ ๊ฐ€๋Šฅํ•  ๊ฑฐ๋ผ๊ณ  ์ƒ๊ฐ์ง€๋„ ๋ชปํ•˜๊ณ  ์žˆ์—ˆ๋‹ค.

๊ทธ๋ž˜์„œ ์ด์ „์— ๋‚ด๊ฐ€ Next.js ์นด์นด์˜คํ†ก ๊ณต์œ ํ•˜๊ธฐ ๊ด€๋ จ ํฌ์ŠคํŒ…์„ ๋ฐ”๋กœ ๋ณด์™„ํ•  ์˜ˆ์ •์ด๋‹ค.

next deploy

๋งˆ์ง€๋ง‰์œผ๋กœ ๋งˆ์ด๊ทธ๋ ˆ์ด์…˜์„ ์™„๋ฃŒํ•˜๊ณ  vercel์„ ํ†ตํ•ด ๋ฐฐํฌ๋ฅผ ์ง„ํ–‰ํ•˜๋˜ ๋„์ค‘


The "next export" command has been removed in favor of "output: … …export" in next.config.js

 

์œ„์™€ ๊ฐ™์€ ์—๋Ÿฌ๊ฐ€ ๋ฐœ์ƒํ–ˆ๋‹ค.

๊ธฐ์กด package.json์— "deploy": "next build && next export"๊ฐ€ ์ถ”๊ฐ€๋˜์–ด ์žˆ์—ˆ๋Š”๋ฐ Next 14 ๋ฒ„์ „๋ถ€ํ„ฐ๋Š” next export๊ฐ€ ์‚ญ์ œ๋˜์—ˆ๋‹ค๊ณ  ํ•œ๋‹ค.

ํ•ด๋‹น ๊ณต์‹๋ฌธ์„œ ์ผ๋ถ€ ๋ฐœ์ทŒ

๋Œ€์‹ ์— next.config.js์— output: 'export'๋ฅผ ์ถ”๊ฐ€ํ•ด ์ฃผ๋ฉด ๋œ๋‹ค๊ณ  ํ•œ๋‹ค.

deploy ๋ช…๋ น์–ด ์‚ญ์ œ
output: export ์ถ”๊ฐ€

์œ„์™€ ๊ฐ™์ด deploy ๋ช…๋ น์–ด๋Š” ์ด์ œ ํ•„์š”๊ฐ€ ์—†์–ด์กŒ๊ธฐ ๋•Œ๋ฌธ์— ๊ณผ๊ฐํžˆ ์‚ญ์ œํ•ด์ฃผ๊ณ , next.config.js์— output:'export๋ฅผ ์ถ”๊ฐ€ํ•ด์คฌ๋‹ค.

vercel์—์„œ ๋ฐฐํฌ ๋ช…๋ น์–ด๋ฅผ yarn build๋กœ ์ˆ˜์ •ํ•ด ์ฃผ์—ˆ๋‹ค.

ํ•ด๋‹น ๊ณต์‹๋ฌธ์„œ

๐Ÿ“•๋งˆ๋ฌด๋ฆฌ

SSG๊ฐ€ ์ž˜ ์ ์šฉ๋œ ์„ฑ๊ณต์ ์ธ build

์‚ฌ์‹ค ๋งˆ์ด๊ทธ๋ ˆ์ด์…˜์„ ์ง„ํ–‰ํ•˜๊ธฐ ์ „์—๋Š” ํ”„๋กœ์ ํŠธ๊ฐ€ ๋งค์šฐ ์ž‘์€ ํŽธ์ด๋ผ๊ณ  ์ƒ๊ฐํ–ˆ๊ธฐ ๋•Œ๋ฌธ์— ๋งŽ์€ ๋ฌธ์ œ๋ฅผ ๋งž์ดํ•  ๊ฑฐ๋ผ๊ณ  ์ƒ๊ฐ์€ ํ•˜์ง€ ๋ชปํ–ˆ๋‹ค.

 

๋งˆ์ด๊ทธ๋ ˆ์ด์…˜์„ ํ•˜๋ฉด์„œ ๋Œ€ํ‘œ์ ์œผ๋กœ ๋Š๊ผˆ๋˜ ๋ถˆํŽธํ•œ ์ ์€ ์•„๋ž˜์™€ ๊ฐ™๋‹ค.

  • GPT ์‚ฌ์šฉ ๋ชป ํ•จ(GPT๋Š” 21๋…„ 9์›”๊นŒ์ง€์˜ ์ •๋ณด๋งŒ ๊ฐ–๊ณ  ์žˆ๊ธฐ ๋•Œ๋ฌธ์— ์ตœ๊ทผ ์ถœ์‹œ ๋œ Next 13 ๋ฒ„์ „์€ ์•Œ์ง€ ๋ชปํ•œ๋‹ค. ๋ฌผ์–ด๋ณด๋ฉด ์ด์ „ ๋ฒ„์ „์— ๊ด€๋ จํ•ด์„œ ๋‹ต๋ณ€์ด ๋Œ์•„์˜จ๋‹ค.)
  • ์ฐธ๊ณ  ์ž๋ฃŒ๊ฐ€ ๋„ˆ๋ฌด ์—†๋‹ค.
  • ๊ธฐ์กด page router์—์„œ๋Š” ์ง€์›๋˜๊ณ  app router์—์„œ๋Š” ์ง€์›์ด ์•ˆ ๋˜๋Š” ๊ฒƒ๋“ค์ด ๋งŽ๋‹ค.

๊ฐœ์„ ํ•ด์•ผ ํ•  ์ ์€ ์•„๋ž˜์™€ ๊ฐ™๋‹ค.

  • styled-components ๊ฑท์–ด๋‚ด๊ธฐ
    • ์„œ๋ฒ„์ปดํฌ๋„ŒํŠธ๋ฅผ ์ง€์›ํ•˜์ง€ ์•Š์•„์„œ ์‚ฌ์šฉํ•˜๋Š” ๋ชจ๋“  ๋ถ€๋ถ„์— 'use client'๋ฅผ ๋ช…์‹œํ•ด์•ผํ•จ์œผ๋กœ์จ, ์„œ๋ฒ„์ปดํฌ๋„ŒํŠธ์˜ ์ด์ ์„ ํ™œ์šฉํ•˜์ง€ ๋ชปํ•œ๋‹ค.
    • ๊ณต์‹๋ฌธ์„œ์—์„œ ์ž์ฒด์ ์œผ๋กœ ์ถ”์ฒœํ•˜๋Š” tailwind-css๋ฅผ ๊ณ ๋ คํ•˜๋Š” ์ค‘์—์žˆ๋‹ค.

์‚ฌ์‹ค app router์˜ ๊ตฌ์กฐ๋Š” ์ฒ˜์Œ์ด๊ณ  ์•„์ง ์ดˆ๊ธฐ ๋‹จ๊ณ„์—ฌ์„œ ์ ์‘๋˜์ง€ ์•Š์•„ ๋ถˆํŽธํ•œ ์ ์ด๋ผ๊ณ  ์ƒ๊ฐํ•œ๋‹ค.

์‹œ๊ฐ„์ด ์ง€๋‚ ์ˆ˜๋ก ๋ณด์ด ๋˜๋ฉด์„œ ์ฐธ๊ณ  ์ž๋ฃŒ๋„ ๋งŽ์•„์ง€๋ฉด์„œ ํ˜„์žฌ page router๋ฅผ ์‚ฌ๋žŒ๋“ค์ด ํŽธํ•˜๊ฒŒ ์“ฐ๋Š” ๊ฒƒ์ฒ˜๋Ÿผ app router๋„ ๊ทธ๋ ‡๊ฒŒ ๋˜์ง€ ์•Š์„๊นŒ ์ƒ๊ฐํ•œ๋‹ค.

 

๊ตฌ๊ธ€๋ง์„ ํ•˜๋ฉด์„œ๋Š” ๋Œ€๋ถ€๋ถ„ "Next.js 13 ~~" ์ด๋Ÿฐ ์‹์œผ๋กœ ๊ฒ€์ƒ‰์„ ํ–ˆ๋‹ค.

์™œ๋ƒํ•˜๋ฉด Next์—์„œ ํฐ ๋ณ€ํ™”๋ฅผ ์ผ์œผํ‚จ ๋ถ€๋ถ„์€ Next 13 ๋ฒ„์ „์ด ์ถœ์‹œ๋˜์—ˆ์„ ๋•Œ ๋ณ€ํ™”๊ฐ€ ๊ฐ€์žฅ ์ปธ๊ธฐ ๋•Œ๋ฌธ์— ๋น„์ค‘์€ Next 13์ด ๊ฐ€์žฅ ํฌ๊ณ , 14 ๋ฒ„์ „์œผ๋กœ ๋งˆ์ด๊ทธ๋ ˆ์ด์…˜์„ ์ง„ํ–‰ํ–ˆ์ง€๋งŒ 13 ๋ฒ„์ „ ๊ด€๋ จํ•ด์„œ ๊ตฌ๊ธ€๋ง์„ ์ง„ํ–‰ํ•˜๋Š” ๊ฒƒ์ด ๋งž๋‹ค๊ณ  ์ƒ๊ฐํ–ˆ๋‹ค.

 

๊ทธ๋ฆฌ๊ณ  app router๋กœ ๋ณ€๊ฒฝํ•˜๋ฉด์„œ ์„ฑ๋Šฅ ์ƒ์˜ ์ด์ ์€ ๋ณด์ด์ง€ ์•Š๋Š” ๊ฒƒ ๊ฐ™๋‹ค.

์ œ์ผ ํฐ ์ด์œ ๋Š” ํ”„๋กœ์ ํŠธ ๊ทœ๋ชจ๊ฐ€ ์ž‘๊ธฐ ๋•Œ๋ฌธ์— ๋ˆˆ์— ๋„๋Š” ๋ณ€ํ™”๋Š” ์—†๋˜ ๊ฒƒ ๊ฐ™๋‹ค.

 

ํ•˜์ง€๋งŒ ์ด๋ ‡๊ฒŒ ๋งˆ์ด๊ทธ๋ ˆ์ด์…˜์„ ํ•˜๋ฉด์„œ ๊ฒช์€ ๊ฒƒ๋“ค์€ ๊ฐ’์ง€๋‹ค๊ณ  ์ƒ๊ฐํ•œ๋‹ค.

์–ด๋–ป๊ฒŒ ๋ณด๋ฉด ๋‚˜์˜ Nextjs ๊ด€๋ จํ•œ ์ง€์‹์€ 11 ๋ฒ„์ „์—์„œ ๋ฉˆ์ถฐ์žˆ๋‹ค๊ณ  ํ•ด๋„ ๋ฌด๋ฐฉํ–ˆ์—ˆ๊ณ , 11 ๋ฒ„์ „ ์ดํ›„์— ๋งŽ์€ ๋ณ€ํ™”๊ฐ€ ์žˆ์—ˆ๊ธฐ ๋•Œ๋ฌธ์— ์–ด๋–ค ๋ถ€๋ถ„๋“ค์ด ์–ด๋–ป๊ฒŒ ๋ณ€๊ฒฝ๋˜์—ˆ๋Š”์ง€ ์ง์ ‘ ๋Š๋ผ๊ณ  ์ด๋Ÿฐ ๊ณผ์ •์—์„œ ๊ฒช์—ˆ๋˜ ๋ฌธ์ œ๋“ค๊ณผ ํ•ด๊ฒฐํ•œ ๋ฌธ์ œ๋“ค์ด ๋‚ด ์†Œ์ค‘ํ•œ ์ž์‚ฐ์ด ๋˜์—ˆ๋‹ค๊ณ  ์ƒ๊ฐํ•œ๋‹ค.

 

๋ฐ˜์‘ํ˜•
profile

๐Ÿ’ป์šฉ๋‡ฝ ๊ฐœ๋ฐœ ๋…ธํŠธ๐Ÿ’ป

@์šฉ๋‡ฝ

ํฌ์ŠคํŒ…์ด ์ข‹์•˜๋‹ค๋ฉด "์ข‹์•„์š”โค๏ธ" ๋˜๋Š” "๊ตฌ๋…๐Ÿ‘๐Ÿป" ํ•ด์ฃผ์„ธ์š”!