
๐ฑ ์ธํ๋ฐ์์ [์์ ์ต๋ Next.js] Part 1 - ๊ณต์ ๋ฌธ์ ํ์ด๋ณด๊ธฐ๋ฅผ ์ฝ์ ํ ๊ฐ์ ๋ด์ฉ์ ์ ๋ฆฌํ์ต๋๋ค.
ํ์ํ ๋ถ๋ถ์ ๋ํด์๋ง ์ ๋ฆฌํ๊ธฐ ๋๋ฌธ์ ๋ถ์กฑํ ๋ถ๋ถ์ด ์์ ์ ์์ต๋๋ค.
"use client"๋ผ๊ณ ๋ช
์ ํ์๐จ ํด๋ผ์ด์ธํธ ์ปดํฌ๋ํธ ์ฌ์ฉ ์ ์ฃผ์ํ ์
โ ํด๋ผ์ด์ธํธ ์ปดํฌ๋ํธ์์ ์๋ฒ ์ปดํฌ๋ํธ๋ฅผimportํ ์ ์๋ค.
โ ํด๋ผ์ด์ธํธ ์ปดํฌ๋ํธ์์ ์๋ฒ ์ปดํฌ๋ํธ๋ฅผ ์ฐ๊ณ ์ถ์ ๋,props๋ก ๋๊ฒจ์ฃผ์ด์ผ ํ๋ค.
"use client"
import { useState } from 'react';
export default function ExampleClientComponent({
children,
} : {
children : React.ReactNode
}) {
const [count, setCount] = useState(0);
return (
<button onClick={() => setCount(count+1)}>
{children}
</button>
)
}
// -> ์ด์ฒ๋ผ ํด๋ผ์ด์ธํธ ์ปดํฌ๋ํธ์์ ์๋ฒ ์ปดํฌ๋ํธ๋ฅผ ์ฐ๊ณ ์ถ๋ค๋ฉด children๊ณผ ๊ฐ์ด props๋ก ์ ๋ฌํด ์ฌ์ฉํด์ผ ํ๋ค.
pre-rendering์ด๋ผ๋ ๋ฐฉ์์ผ๋ก ์ ๊ณตํ๋ ๋ฐฉ์์ผ๋ก ์ฌ์ ํ ๋ ๋๋งํ ์ ์๋ค.๐ง ์ ํด๋ผ์ด์ธํธ ์ปดํฌ๋ํธ๊ฐ ํธ๋ฆฌ์ ๋์ ์์นํ๋ ๊ฒ์ด ์ข์๊น?
โ ์๋ฒ ์ปดํฌ๋ํธ๋ฅผ ์ต๋ํ ํ์ฉํ๊ธฐ ์ํด์ ์ด๋ค.
- ํด๋ผ์ด์ธํธ ์ปดํฌ๋ํธ๊ฐ ํธ๋ฆฌ์ ์์์ ์์ผ๋ฉด, ํ์์ ์๋ ์ปดํฌ๋ํธ๋ ์์ ์์๋ก ์ ๋ฌ๋์ง ์์ ์ด์ ์๋ฒ ์ปดํฌ๋ํธ๋ฅผ ํ์ฉํ๊ธฐ ์ด๋ ต๋ค.
- ๋๋ฌธ์ ๊ฐ๋ฅํ๋ฉด ํด๋ผ์ด์ธํธ ์ปดํฌ๋ํธ๋ฅผ ํธ๋ฆฌ์ ๋์ ๋๋ฉด ์ฑ ์ ์ฒด์์ ์๋ฒ ์ปดํฌ๋ํธ์ ๋น์ค์ ๋๋ฆด ์ ์๋ค.
- ์ด๋ ๊ฒฐ๊ณผ์ ์ผ๋ก ๋ฒ๋ค ํฌ๊ธฐ์ ๊ฐ์๋ก ์ด์ด์ ธ ์ ์ฒด์ ์ธ ์ฑ๋ฅ ํฅ์์ผ๋ก ์ด์ด์ง๋ค.
โ๏ธ TTI(Time To Interact)
- ์ฌ์ฉ์๊ฐ ํด๋ฆญ์ ํ๊ฑฐ๋ ์ธํฐ๋ ์ ์ด ๊ฐ๋ฅํ๊ฒ ๋๋๋ฐ ๊ฑธ๋ฆฌ๋ ์๊ฐ
โ๏ธ TTV(Time To View)
- ์ฌ์ฉ์๊ฐ ์น์ฌ์ดํธ๋ฅผ ๋ณด๋๋ฐ๊น์ง ๊ฑธ๋ฆฌ๋ ์๊ฐ
SSR ๋ฐฉ์์ ์๋ฒ ์ปดํฌ๋ํธ์ ๋ค๋ฅธ ๋ฐฉ์์ด๋ค.
โ ์๋ฒ์์ ์ปดํฌ๋ํธ๋ฅผ ๋ ๋๋งํ๋ค๋ ์ ์์ ๋์ผํ์ง๋ง, ๋ ๋๋ง์ ๊ฒฐ๊ณผ๊ฐ๊ณผ ๋์ ๋ฐฉ์์ด ๋ค๋ฅด๋ค.
Pre-rendering ๋ฐฉ์์ผ๋ก ์๋ฒ์์ HTML์ ๋ง๋ค์ด๋ธ๋ค. ์ดํ ํด๋ผ์ด์ธํธ๋ก ์ ๋ฌ๋ JS ๋ฒ๋ค ํ์ผ๊ณผ ์ํ๋๋ ๊ณผ์ ์ ๊ฑฐ์ณ React ์ปดํฌ๋ํธ๋ก ์ฌ์ฉ๋๋ค.children์์๋ก ํด๋ผ์ด์ธํธ ์ปดํฌ๋ํธ์๊ฒ ์ ๋ฌ๋๋ ๊ฒฝ์ฐ์ด๋ค.๐ข Hydration์ด๋
- Hydration์ด๋ HTML ์ฝ๋๊ฐ React ์ปดํฌ๋ํธ๋ก ์ธ์๋๋๋ก JavaScript ์ฝ๋์ ๋งค์นญ์ํค๋ ๋จ๊ณ์ด๋ค.
- ์๋ฒ ์ปดํฌ๋ํธ๋ ์๋ฒ์์
Pre-rendering๋ฐฉ์์ผ๋ก HTML์ ๋ง๋ค์ด ์ ๋ฌํ๊ธฐ ๋๋ฌธ์ Hydration ๋จ๊ณ๊ฐ ์๋ค.
- ์๋ฒ ์ปดํฌ๋ํธ๋ ์๋ฒ์์ ๋ด๋ ค์ฌ ๋RSC Payload๋ผ๋ ์ผ์ข ์ JSON์ผ๋ก ์ ๋ฌ๋๋ฉฐ ๊ณง์ฅ React ์ปดํฌ๋ํธ๋ก ์ฌ์ฉ๋ ์ ์๋ค.


์ ์ฝ์๋ ํ์ผ๋ช
์ ๋ฐ๋ผ ํ์ผ ์ด๋ฆ์ ์ง์ ํด ์ธ๊ทธ๋จผํธ(ํด๋) ์์ ์ ์ํ๋ฉด Next.js๊ฐ ๋ค์๊ณผ ๊ฐ์ด ์์์ React ์ปดํฌ๋ํธ๋ฅผ ๋ฐฐ์นํด์ค๋ค.

export default function Page({ params }: { params: { id: string } }) {
return <div>My Post: {params.id}</div>
}
โ๏ธ Dynamic Routes
- Dynamic Routes๋ ํ์ผ ์ด๋ฆ์ ๋๊ดํธ๋ก ๋ฌถ์ด์ฃผ๋ ๊ฑธ ์์น์ผ๋ก ํ๋ค.
- ์ฌ์ฉ๋ฐฉ๋ฒ์ ๋๊ดํธ ์์ ์ง์ ํ ์ด๋ฆ์ Page ์ปดํฌ๋ํธ์ ์ธ์๋ก ๋ฐ์ ์ ์๋ค.
a ํ๊ทธ์ ํ์ฅ๋ ๋ฒ์ ์ด๋ค.import Link from "next/link";
export default function Page() {
return <Link href="/dashboard">Dashboard</Link>
}
'use client';
import { useRouter } from "next/navigation";
export default function Page() {
const router = useRouter();
return (
<button onClick={() => router.push('/dashboard')}>
Dashboard
</button>
)
}
๐ข Link / Router์ ๋ํ ๋ ์์ธํ ๋ด์ฉ์ ๋ณ๋ ํฌ์คํ ์์ ํ์ธํ ์ ์๋ค!
.module.css ํ์ฅ์๋ก ์ด๋ฆ์ ๋ถ์ฌ ์คํ์ผ์ ์ ์ํ๊ณ ์ปดํฌ๋ํธ์์ ๊ฐ์ ธ์ ์ฌ์ฉํ ์ ์๋ค.// styles.modules.css
.dashboard {
padding: 24px;
}
// layout.tsx
import styles from './styles.module.css'
export default function DashboardLayout({
children,
}) {
return <section className={styles.dashboard}>{children}</section>
}
Next.js์์ ๋ค์ด๋๋ฏนํ๊ฒ ์ด๋ค id ๋ฅผ ํตํด ๋ฐ์ดํฐ๋ฅผ ๋ฐ์์์ ๋, ์ดํ์ ํด๋น ๋ฐ์ดํฐ๋ฅผ ๋์ ์ผ๋ก ๋ฐ์์ค์ง ๋ชปํ๋ค๋ฉด ๋์ ์ธ ํ์ด์ง๋ฅผ ๊ทธ๋ฆด ์ ์๋ค.
๊ทธ๋ ๊ธฐ ๋๋ฌธ์ ๋ฐ์ดํฐ ํจ์นญ์ "์ผ๋ง๋ ๋ฏธ๋ คํ๊ฒ ์ฒ๋ฆฌํ๋๊ฐ", ๊ทธ๋ฆฌ๊ณ "๋ฐ์ดํฐ ํจ์นญ์์ ์ผ๋ง๋ ์๋๋ฅผ ๋ง์ด ์ค์ผ ์ ์๋๊ฐ" ์ ๋ฐ๋ผ ์ ์ ๊ฒฝํ(UX) ๋ฐ ์น ์ ํ๋ฆฌ์ผ์ด์
์ ๋ง์ ์ํฅ์ ๋ผ์น๋ค.
๐ข ๋ฐ์ดํฐ ํจ์นญ์ ์๋ฒ์ ํด๋ผ์ด์ธํธ ๋ชจ๋์์ ๊ฐ๋ฅํ์ง๋ง, NEXT.js์์๋ ๋ณด์๊ณผ ์ฑ๋ฅ์์ ์ด์ ์ด ๋ง๊ธฐ ๋๋ฌธ์ ์๋ฒ์์ ๋ฐ์ดํฐ๋ฅผ ํจ์นญํ๋ ๊ฒ์ ๊ถ์ฅํ๋ค.
๋ ์ฃผ์ํ ์ ์ Next๋ ํจ์นญ๋ ๋ฐ์ดํฐ, ์ฆ ๋์ ์ผ๋ก ๋ฐ์์จ ๋ฐ์ดํฐ๋ฅผ ์บ์ฑํ๊ณ ์๋ค๋ ์ ์ด๋ค.
๊ธฐ๋ณธ์ ์ผ๋ก fetch์ ์ํด ๋ฐ์ํ ์์ฒญ์ ์๋์ผ๋ก ์บ์ฑ๋๋ค. ๋ฐ๋ผ์ ์ฌ๋ฌ๋ฒ ์์ฒญํด๋ ์ ์ฅํด๋ ๊ฐ์ ๋ฐํํด ๋ถํ์ํ API ํธ์ถ์ ์ค์ฌ์ค๋ค.
์ฆ ์บ์ ๋ฐ์ดํฐ๋ ์๋ฒ์ ์ ์ฅ๋์ด ๋น๋ ํ์ ํน์ ์ดํ ์์ฒญ์๋ ์ฌ์ฌ์ฉํ ์ ์๋ ๊ฒ์ด๋ค.
// fetch์ cache ํ๋ผ๋ฏธํฐ์ ๊ธฐ๋ณธ ๊ฐ : 'force-cache'
fetch('https://...', { cache: 'force-cache' })
๋ฐ์ดํฐ ์บ์ฑ์ ์๋ ๊ทธ๋ฆผ์์ ๋ณด๋ผ์ ์์ญ์ ํด๋นํ๋ค.

์บ์ฑ๋ ๋ฐ์ดํฐ๊ฐ ์๋ ์ต์ ์ ๋ฐ์ดํฐ๋ฅผ ์ํ๋ค๋ฉด, ๋ฐ์ดํฐ ์ฌ๊ฒ์ฆ์ ํตํด ์บ์ฑ๋ ๋ฐ์ดํฐ๋ฅผ ๋ฌดํจํํ ์ ์๋ค.
์ด๋ ์บ์ฑ๋ ๋ฐ์ดํฐ๋ฅผ ์ฌ๊ฒ์ฆํ๋ ๋ฐฉ๋ฒ์๋ ๋ ๊ฐ์ง ๋ฐฉ๋ฒ์ ์ฌ์ฉํ ์ ์๋ค.
parameter์ value๋ก ์ ์ํ ํน์ ์๊ฐ์ด ์ง๋ ํ์ ์๋์ผ๋ก ๋ฐ์ดํฐ๋ฅผ ์ฌ๊ฒ์ฆํ๋ ๋ฐฉ๋ฒ์ด๋ค.
// 3600์ด(60๋ถ) ๋์ ์ ํจํ fetch
fetch('https://...', { next: { revalidate: 3600 } })
์๊ฐ ๊ธฐ๋ฐ ์ฌ๊ฒ์ฆ์ ๋ฐ์ดํฐ๊ฐ ์์ฃผ ๋ณ๊ฒฝ๋์ง ์๊ณ , ์ ์ ๋๊ฐ ์ค์ํ์ง ์์ ๊ฒฝ์ฐ์ ์ ์ฉํ๋ค.
- ํด๋น URL๋ก ํธ์ถ๋ ๋ฐ์ดํฐ๋ "60์ด ๋์ ์ ํจํ ๋ฐ์ดํฐ"๋ก ์ ์ธํ ํจ์๋ฅผ ๋ง๋ ๋ค.
- ์์ง 60์ด๊ฐ ์ง๋์ง ์์ ๊ฒฝ์ฐ, ์ ํจ์๋ฅผ ์ฌํธ์ถํ์ง ์๊ณ ์บ์ฑ๋ ๋ฐ์ดํฐ๋ฅผ ์ ๋ฌํ๋ค.
- ๐จ 60์ด๊ฐ ์ง๋ ๊ฒฝ์ฐ, ์ ํจ์๊ฐ์ด ์ง๋ฌ๊ธฐ ๋๋ฌธ์ ์ ํจ์๋ฅผ ๋ค์ ํธ์ถํ๋ค.
์ด๋ 60์ด ํ์ ๋ฐ๋ก ์๋ก์ด ๋ฐ์ดํฐ๋ฅผ ๊ฐ์ ธ์ค๋ ๊ฒ์ด ์๋๋ผ, 60์ด๊ฐ ์ง๋ ํ ๋ฐ์ดํฐ๋ฅผ ์์ฒญํ์ ๋ ํด๋น ๋ฐ์ดํฐ๊ฐ ์ ํจํ์ง ์๋ค๋ ๊ฒ์ ์๊ฒ๋๋ ๊ฒ ๋ฟ์ด๋ค.
์ค์ ๋ก Revalidate๊ฐ ์ผ์ด๋ ์ดํ์ ๋ฐ์ดํฐ๋ฅผ ํธ์ถํด์ผ ์ ์ ํ ๋ฐ์ดํฐ๋ฅผ ๊ฐ์ ธ์ฌ ์ ์๋ค.

"์ง๊ธ ์ฃผ๋ ๋ฐ์ดํฐ๋ ์ด๋ฏธ ์ ํจ์๊ฐ์ด ์ง๋ ๋ฐ์ดํฐ์ผ, ์ฆ ์ ํจํ์ง ์์. ํ์ง๋ง ์ผ๋จ ์์ ๋ฐ์ดํฐ๋ ์ด๊ฑฐ์์ด"๋ผ๊ณ ๋ณด๋ด์ค๋ค.
์ดํ Revalidate ํด ๋ค์ ์ ํจํ ๋ฐ์ดํฐ๋ฅผ ๊ฐ์ ธ์จ๋ค.
60์ด ์ดํ ๋ ๋ฒ์งธ ํธ์ถ๋ถํฐ ์ ์ ํ ๋ฐ์ดํฐ๋ฅผ ๋ณผ ์ ์๊ฒ ๋๋ค.
demand, ์ฆ ์์๊ฐ ์์ ๋ ์ฌ๊ฒ์ฆํ๋ ๋ฐฉ์์ผ๋ก, ์๊ฐ ๊ธฐ๋ฐ๊ณผ ๋ฌ๋ฆฌ ์๋์ผ๋ก ๋ฐ์ดํฐ๋ฅผ ์ฌ๊ฒ์ฆํ ์ ์๋ ๋ฐฉ๋ฒ์ด๋ค.
ํ๊ทธ(tag)ํน์ ๊ฒฝ๋ก(path)๋ฅผ ๊ธฐ๋ฐ์ผ๋ก ํน์ ๋ฐ์ดํฐ ๊ทธ๋ฃน์ ์ผ๊ด ์ฌ๊ฒ์ฆํ ์ ์๋ค. ์ด๋ ์ฌ๊ฒ์ฆ๋ ์บ์ ๋ฐ์ดํฐ๋ ์ญ์ ๋๋ค.
On-demand ์ฌ๊ฒ์ฆ ๋ฐฉ๋ฒ์ ์ฆ๊ฐ์ ์ผ๋ก ์ต์ ๋ฐ์ดํฐ๋ฅผ ํ๋ณดํด์ผํ๋ ๊ฒฝ์ฐ ์ ์ฉํ๋ค.
// ๋ฐ์ดํฐ์ ํ๊ทธ๋ฅผ ๋ฌ์๋๋ค.
fetch('https://...', { next: { tags: ['collection'] } })
// ํ๊ทธ๋ ๋ฐ์ดํฐ๋ฅผ ์ฌ๊ฒ์ฆํ๋ค.
revalidateTag('collection')

๐ก ๊ทธ๋ฆผ๊ณผ ๊ฐ์ด ์ฌ๊ฒ์ฆ์ด ํ์ํ ๋ Revalidate ํ๊ทธ ํจ์๋ฅผ ํธ์ถํ๊ณ , ๋งค๊ฐ๋ณ์๋ก ํ๊ทธ ๊ฐ์ ์ ๋ฌํ๋ฉด ํด๋น ์บ์๊ฐ ์ฌ๊ฒ์ฆ๋๋ค.
๐ ์ฐธ๊ณ ๋ฌธ์