Next.js - Pages Router(2)
0. Intro ๐ฏ ์ง๋ ๊ธ์์๋ Pages Router์์ ํ์ฉํ ์ ์๋ ๋ค์ํ ๋ ๋๋ง ๋ฐฉ์๊ณผ ๋๋ถ์ด RSC ๊ฐ๋ ๊น์ง ํญ๋๊ฒ ์ดํด๋ดค์ต๋๋ค. ๊ฐ๊ฐ์ ๋ฐฉ์์ด ์ด๋ค ๊ณ ๋ฏผ ์์์ ๋ฑ์ฅํ๋์ง, ์ด๋ค ํธ๋ ์ด๋์คํ๋ฅผ ๊ฐ์ง๊ณ ์๋์ง ์ดํดํ๋ ์๊ฐ์ด์์ต๋๋ค. ๋ ๋๋ง ๋ฐฉ์์ด '
๋ ๋๋ง์ ๋ํ ๋ ผ์๋ ์๋์ ๊ธ์ ์ฐธ๊ณ ํด ์ฃผ์ธ์.
reference: https://velog.io/@minkwan/Next.js-Pages-Router1
0. Intro ๐ฏ
์ง๋ ๊ธ์์๋ Pages Router์์ ํ์ฉํ ์ ์๋ ๋ค์ํ ๋ ๋๋ง ๋ฐฉ์๊ณผ ๋๋ถ์ด RSC ๊ฐ๋ ๊น์ง ํญ๋๊ฒ ์ดํด๋ดค์ต๋๋ค. ๊ฐ๊ฐ์ ๋ฐฉ์์ด ์ด๋ค ๊ณ ๋ฏผ ์์์ ๋ฑ์ฅํ๋์ง, ์ด๋ค ํธ๋ ์ด๋์คํ๋ฅผ ๊ฐ์ง๊ณ ์๋์ง ์ดํดํ๋ ์๊ฐ์ด์์ต๋๋ค.
๋ ๋๋ง ๋ฐฉ์์ด '์ด๋ป๊ฒ ํ๋ฉด์ ๊ทธ๋ ค๋ผ ๊ฒ์ธ๊ฐ'์ ๋ํ ๊ณ ๋ฏผ์ด๋ผ๋ฉด, ์ค๋ ๋ค๋ฃฐ Data Fetching์ ๊ทธ ํ๋ฉด์ ์ฑ์ธ '๋ฐ์ดํฐ๋ฅผ ์ด๋์, ์ธ์ , ์ด๋ป๊ฒ ๊ฐ์ ธ์ฌ ๊ฒ์ธ๊ฐ'์ ๊ดํ ๋ ผ์์ ๋๋ค.

Pages Router ํ๊ฒฝ์์๋ ๊ฐ ๋ ๋๋ง ์ ๋ต์ ๋ง์ถฐ ๋ฐ์ดํฐ๋ฅผ fetching ํ๋ ํน์ํ ํจ์๋ค์ด ์กด์ฌํฉ๋๋ค. ๋น๋ ํ์์ ๋ฐ์ดํฐ๋ฅผ ๋ฏธ๋ฆฌ ๊ฐ์ ธ์ค๋ getStaticProps, ๋งค ์์ฒญ๋ง๋ค ์ต์ ๋ฐ์ดํฐ๋ฅผ ํ๋ณดํ๋ getServerSideProps, ๋์ ๊ฒฝ๋ก๋ฅผ ์ฒ๋ฆฌํ๊ธฐ ์ํ getStaticPaths ๋ฑ์ ๊ฐ๋ ์ด ์์ฃ .
ํ์ตํ ๋ ๋๋ง ๊ฐ๋ ์ ๊ธฐ๋ฐ์ผ๋ก, Next.js Pages Router์ ํต์ฌ์ด๋ผ๊ณ ํ ์ ์๋ Data Fetching ๋ฉ์๋๋ค์ ๊ตฌ์ฒด์ ์ธ ๋์ ์๋ฆฌ์ ํ์ฉ๋ฒ์ ์ ๋ฆฌํด ๋ณด๊ฒ ์ต๋๋ค.
1. getStaticProps ๐ฏ
1-1. getStaticProps ์ฌ์ฉ โ
ํ์ด์ง์์ ์๋ ์ฝ๋์ ๊ฐ์ด getStaticProps ํจ์๋ฅผ export ํ๋ฉด, Next.js๋ ๋น๋ ํ์์ ํด๋น ํจ์๋ฅผ ์คํํ์ฌ ํ์ํ ๋ฐ์ดํฐ๋ฅผ ๊ฐ์ ธ์ค๊ณ , ๊ทธ ๊ฒฐ๊ณผ๋ฌผ(props)์ ์ฌ์ฉํด HTML ํ์ด์ง๋ฅผ ๋ฏธ๋ฆฌ ์์ฑํ๋ Pre-Rendering์ ์ํํฉ๋๋ค.
export async function getStaticProps() {
const res = await fetch('https://api.github.com/repos/vercel/next.js')
const repo = await res.json()
return { props: { repo } }
}
export default function Page({ repo }) {
return repo.stargazers_count
}์ด ๊ณผ์ ์์ getStaticProps๋ ์ค์ง ์๋ฒ ์ธก์์๋ง ์คํ๋๋ฉฐ, ๋น๋ ์์ ์ ๋ฐ์ดํฐ๊ฐ ์ฑ์์ง ์ ์ HTML๊ณผ JSON ํ์ผ์ ํจ๊ป ์์ฑํฉ๋๋ค. ์ด๋ ๊ฒ ์์ฑ๋ ์ ์ ์์ฐ์ CDN์ ์บ์ฑ ๋์ด ์ฌ์ฉ์์๊ฒ ๋งค์ฐ ๋น ๋ฅด๊ฒ ์ ๋ฌ๋๋ฏ๋ก, getStaticProps๋ SSG(Static Site Generation) ๋ฐฉ์์ ๊ตฌํํ๋ ํต์ฌ ๋ฉ์๋๋ผ๊ณ ํ ์ ์์ต๋๋ค.
Pitfall โ ๏ธ
๋ ๋๋ง ๋ฐฉ์๊ณผ ๋ฌด๊ดํ๊ฒ ๋ชจ๋ props๊ฐ ์ปดํฌ๋ํธ๋ก ์ ๋ฌ๋๋ฉฐ, ์ด๋ฅผ ์ด๊ธฐ HTML์ ํตํด ํด๋ผ์ด์ธํธ ์ธก์์ ํ์ธํ ์ ์์ต๋๋ค. ํ์ด์ง๊ฐ ์ฌ๋ฐ๋ฅด๊ฒ Hydration ๋ ์ ์๋๋ก ํ๊ธฐ ์ํจ์ ๋๋ค. ํด๋ผ์ด์ธํธ์์ ํ์ธ ๊ฐ๋ฅํด์๋ ์ ๋๋ ๋ฏผ๊ฐํ ์ ๋ณด๋ props์ ํฌํจํ์ฌ ์ ๋ฌํ์ง ์๋๋ก ์ฃผ์ํด์ผ ํฉ๋๋ค.
1-2. getStaticProps ์ฌ์ฉ ์์ โ
getStaticProps๋ ํ๋ง๋๋ก '๋๊ฐ ์ ์ํด๋ ๋๊ฐ์ ํ๋ฉด์ ๋ณด์ฌ์ค ๋' ๊ฐ์ฅ ๊ฐ๋ ฅํ ์ฑ๋ฅ์ ๋ณด์ฌ์ค๋๋ค. ๋ํ์ ์ผ๋ก ๋ค ๊ฐ์ง ์ํฉ์ด ์์ต๋๋ค.
๋น๋ ์์ ์ ๋ฐ์ดํฐ ์ค๋น๊ฐ ๊ฐ๋ฅํ ๊ฒฝ์ฐ
์ฌ์ฉ์๊ฐ ํ์ด์ง๋ฅผ ์์ฒญํ๊ธฐ ์ , ์ฆ ์๋น์ค๋ฅผ ๋น๋ ํ๋ ์์ ์ ์ด๋ฏธ ํ๋ฉด์ ๋ํ๋ผ ๋ฐ์ดํฐ๋ฅผ ํ์ ์ง์ ์ ์์ ๋ ์ฌ์ฉํฉ๋๋ค.
์ธ๋ถ CMS์์ ๋ฐ์ดํฐ๋ฅผ ๊ฐ์ ธ์ค๋ ๊ฒฝ์ฐ
Contentful, Strapi์ฒ๋ผ ํ๋ฉด์ ์๊ณ ๋ฐ์ดํฐ๋ง API๋ก ์ ์กํด ์ฃผ๋ ์ธ๋ถ ํค๋๋ฆฌ์ค CMS์์ ๊ด๋ฆฌํ๋ ์ฝํ ์ธ (๋ธ๋ก๊ทธ ํฌ์คํธ, ๊ณต์ง์ฌํญ ๋ฑ)๋ฅผ ๋ถ๋ฌ์ ํ๋ฉด์ ๊ตฌ์ฑํ ๋ ์ ํฉํฉ๋๋ค.
๊ฐ๋ ฅํ SEO์ ๋น ๋ฅธ ์๋๊ฐ ํ์์ ์ธ ๊ฒฝ์ฐ
๊ฒ์ ์์ง ์ต์ ํ๊ฐ ์ค์ํ๊ณ , ๋ก๋ฉ ์๋๊ฐ ๋งค์ฐ ๋นจ๋ผ์ผ ํ๋ ํ์ด์ง์ ์ฌ์ฉํฉ๋๋ค. HTML๊ณผ JSON ํ์ผ์ ๋ฏธ๋ฆฌ ๋ง๋ค์ด CDN์ ์ ์ฅํด๋๊ธฐ ๋๋ฌธ์ ์ฌ์ฉ์์๊ฒ ์ฆ์ ํ๋ฉด์ ๋ณด์ฌ์ค ์ ์์ต๋๋ค.
์ฌ์ฉ์๋ง๋ค ๋ด์ฉ์ด ๋ฌ๋ผ์ง์ง ์๋ ๊ฒฝ์ฐ
์ฅ๋ฐ๊ตฌ๋, ํ๋กํ์ฒ๋ผ ๊ฐ์ธํ๋ ์ ๋ณด๊ฐ ์๋๋ผ, ๋ชจ๋ ๋ฐฉ๋ฌธ์์๊ฒ ๋์ผํ๊ฒ ๋ณด์ด๋ ๊ณต์ฉ ๋ฐ์ดํฐ๋ฅผ ๋ ๋๋ง ํ ๋ ์ฌ์ฉํฉ๋๋ค.
1-3. getStaticProps ์คํ ์์ โ
getStaticProps๋ ๋ธ๋ผ์ฐ์ ์์ ์คํ๋์ง ์๊ณ , ์ค์ง ์๋ฒ์์๋ง ์คํ๋๋ค๋ ์ ์ด ํต์ฌ์ ๋๋ค. getStaticProps ๋ด๋ถ์ ์์ฑํ ๋ก์ง์ด๋ ๋ฐ์ดํฐ๋ฒ ์ด์ค ์ ๊ทผ ์ฝ๋๋ ๋ฐฐํฌ ์ ํด๋ผ์ด์ธํธ๊ฐ ๋ฐ๋ ์๋ฐ์คํฌ๋ฆฝํธ ๋ฒ๋ค ํ์ผ์์ ์์ ํ ์ ๊ฑฐ๋ฉ๋๋ค. getStaticProps๋ ๊ธฐ๋ณธ์ ์ผ๋ก next build ์์ ์ ์คํ๋์ด ์๋น์ค์ ํ์ํ ๋ชจ๋ ์ ์ ํ์ด์ง๋ฅผ ๋ฏธ๋ฆฌ ์์ฑํฉ๋๋ค. ๋ฌผ๋ก ๊ฐ๋ฐ ๋ชจ๋์์๋ ์ฑ๋ฅ ํธ์๋ฅผ ์ํด ๋งค ์์ฒญ๋ง๋ค ์คํ๋ฉ๋๋ค.
ํ์ง๋ง SSG ๋ฐฉ์ ์ธ์, ISR์ด๋ ๋์ ๊ฒฝ๋ก๋ฅผ ์ฒ๋ฆฌํ๋ ๊ฒฝ์ฐ์๋ ์คํ ์์ ์ด ์กฐ๊ธ ๋ ๋ค์ํด์ง๋๋ค. ์ด๋๋ revalidate ์ต์ ์ ํตํด ์ด๋ฏธ ๋ฐฐํฌ๋ ์ํ์์๋ ๋ฐฑ๊ทธ๋ผ์ด๋์์ ์ฃผ๊ธฐ์ ์ผ๋ก ์คํ๋์ด ํ์ด์ง๋ฅผ ์ต์ ๋ฐ์ดํฐ๋ก ์ ๋ฐ์ดํธํฉ๋๋ค. ๋ํ fallback ์ต์ ์ ์ฌ์ฉํ๋ฉด, ์ฌ์ฉ์๊ฐ ๋ฏธ๋ฆฌ ์์ฑํ์ง ์์ ํ์ด์ง์ ์ฒ์ ๋ฐฉ๋ฌธํ์ ๋ ์๋ฒ๋ ์ฆ์ ๋ฐฑ๊ทธ๋ผ์ด๋์์ getStaticProps๋ฅผ ํธ์ถํ์ฌ ์๋ก์ด HTML ํ์ด์ง๋ฅผ ๋ง๋ค์ด๋ ๋๋ค.
getStaticProps๋ ๋น๋ ํ์์ด๋ ๋ฐฑ๊ทธ๋ผ์ด๋์์ '๋ฏธ๋ฆฌ' ์คํ๋๋ ์ฑ๊ฒฉ์ ๊ฐ์ก์ต๋๋ค. ๋ฐ๋ผ์ ์ฌ์ฉ์๊ฐ ๋ธ๋ผ์ฐ์ ์ฃผ์์ฐฝ์ ์ ๋ ฅํ ์ฟผ๋ฆฌ ํ๋ผ๋ฏธํฐ๋ HTTP ํค๋ ์ ๋ณด์๋ ์ง์ ์ ๊ทผํ ์ ์์ต๋๋ค. HTML์ ๋ฏธ๋ฆฌ ๋ง๋ค์ด๋๋ ์์ ์๋ ๋๊ฐ ์ด๋ค ์์ฒญ์ ๋ณด๋ผ์ง ์ ์ ์๊ธฐ ๋๋ฌธ์ด์ฃ . ์ฌ์ฉ์ ๊ฐ๊ฐ์ธ์ ์์ฒญ ์ ๋ณด๊ฐ ๋ฐ๋์ ์๊ตฌ๋๋ค๋ฉด getStaticProps๊ฐ ์๋๋ผ ๋ฏธ๋ค์จ์ด๋ฅผ ํจ๊ป ํ์ฉํ๊ฑฐ๋ SSR ๋ฐฉ์์ ๊ณ ๋ คํด์ผ ํ๊ฒ ์ฃ ?
1-4. CMS ๋ฐ์ดํฐ ํ์นญ ํ์ฉ โ
์๋ ์ฝ๋์ ๊ฐ์ด CMS์์ ๋ธ๋ก๊ทธ ๊ฒ์๋ฌผ ๋ชฉ๋ก์ ๊ฐ์ ธ์ฌ ์ ์์ต๋๋ค.
// getStaticProps()์ ์ํด ๋น๋ ํ์์ posts ๋ฐ์ดํฐ๊ฐ ์ฑ์์ง๋๋ค.
export default function Blog({ posts }) {
return (
<ul>
{posts.map((post) => (
<li key={post.id}>{post.title}</li>
))}
</ul>
)
}
// ์๋ฒ ์ธก์ ๋น๋ ํ์์ ํธ์ถ๋ฉ๋๋ค.
// ํด๋ผ์ด์ธํธ ์ธก(๋ธ๋ผ์ฐ์ )์์๋ ํธ์ถ๋์ง ์์ผ๋ฏ๋ก,
// ๋ฐ์ดํฐ๋ฒ ์ด์ค์ ์ง์ ์ ๊ทผํ๋ ์ฟผ๋ฆฌ๋ฅผ ์์ฑํ ์๋ ์์ต๋๋ค.
export async function getStaticProps() {
// ๊ฒ์๋ฌผ์ ๊ฐ์ ธ์ค๊ธฐ ์ํด ์ธ๋ถ API ์๋ํฌ์ธํธ๋ฅผ ํธ์ถํฉ๋๋ค.
// ์ด๋ค ๋ฐ์ดํฐ ํ์นญ ๋ผ์ด๋ธ๋ฌ๋ฆฌ๋ ์ฌ์ฉ ๊ฐ๋ฅํฉ๋๋ค.
const res = await fetch('https://.../posts')
const posts = await res.json()
// { props: { posts } }๋ฅผ ๋ฐํํจ์ผ๋ก์จ, Blog ์ปดํฌ๋ํธ๋
// ๋น๋ ํ์์ `posts`๋ฅผ prop์ผ๋ก ์ ๋ฌ๋ฐ๊ฒ ๋ฉ๋๋ค.
return {
props: {
posts,
},
}
}1-5. ์๋ฒ ์ฌ์ด๋ ์ฝ๋ ์ง์ ์์ฑ โ
getStaticProps์์๋ ์๋น์ค์ ๋ด๋ถ API Route(/api/posts)๋ฅผ ๋ค์ fetch๋ก ํธ์ถํด์๋ ์ ๋ฉ๋๋ค. ์ด์ ๋ ๋จ์ํฉ๋๋ค. getStaticProps ์์ฒด๊ฐ ์ด๋ฏธ ์๋ฒ ํ๊ฒฝ์ด๊ธฐ ๋๋ฌธ์ ๋ด๋ถ API๋ฅผ fetch๋ก ํธ์ถํ๋ฉด ๋คํธ์ํฌ ์์ฒญ์ ์ด์ค์ผ๋ก ๋ณด๋ด๋ ์ํฉ์ด ๋ฉ๋๋ค.
pages/api/posts.js API Route๊ฐ ์๋ค๊ณ ์๊ฐํด ๋ณด์ฃ .
export default async function handler(req, res) {
const posts = await fetch("https://my-cms.com/posts").then(r => r.json());
res.status(200).json(posts);
}pages/index.js์ getStaticProps์์ ๋ค์ ์ด๋ฅผ ํธ์ถํ๋ค๊ณ ํด๋ด ์๋ค.
export async function getStaticProps() {
const res = await fetch("http://localhost:3000/api/posts");
const posts = await res.json();
return {
props: { posts },
};
}
export default function Home({ posts }) {
return <div>{posts.length} posts</div>;
}getStaticProps ์๋ฒ ์ฝ๋์์ ๋ด๋ถ api route๋ก ๋คํธ์ํฌ ์์ฒญ ํ ๋ฒ, /api/posts.js์์ CMS๋ก ๋คํธ์ํฌ ์์ฒญ ํ ๋ฒ, ์ด ๋ ๋ฒ์ ๋คํธ์ํฌ ์์ฒญ์ด ๋ฐ์ํฉ๋๋ค.
์๋ ์ฝ๋๋ฅผ ๋ณด์์ฃ .
// lib/load-posts.js (๊ณตํต ๋ก์ง)
export async function loadPosts() {
// ์ธ๋ถ CMS๋ DB์ ์ง์ ์ ๊ทผ (๋คํธ์ํฌ 1๋จ๊ณ)
const res = await fetch('https://external-cms.com/posts')
return await res.json()
}
// pages/blog.js
import { loadPosts } from '../lib/load-posts'
export async function getStaticProps() {
// ๋ด๋ถ API๋ฅผ fetch ํ์ง ์๊ณ , ๋ก์ง์ ์ง์ ์คํํ์ฌ ์ค๋ณต ๋คํธ์ํฌ๋ฅผ ์ ๊ฑฐํจ
const posts = await loadPosts()
return { props: { posts } }
}์ด ๊ฒฝ์ฐ์๋ getStaticProps์์ ๊ณง๋ฐ๋ก CMS๋ก ๋คํธ์ํฌ ์์ฒญ์ ํ ๋ฒ๋ง ๋ณด๋ด๊ฒ ๋ฉ๋๋ค.
๊ฒฐ๊ตญ getStaticProps๋ ๊ทธ ์์ฒด๊ฐ ์ด๋ฏธ ์๋ฒ์ด๋ฏ๋ก, ๋ ๋ค๋ฅธ ์๋ฒ ํต๋ก์ธ API Route๋ฅผ ๊ฑฐ์น ํ์ ์์ด ๋ฐ์ดํฐ ์์ค์ ๊ฐ์ฅ ๊ฐ๊น์ด ๋ก์ง์ ์ง์ ์คํํ๋ ๊ฒ์ด ์ฑ๋ฅ ์ต์ ํ์ ํต์ฌ์ ๋๋ค.
1-6. HTML ๋ฐ JSON ์ ์ ์์ฑ ๋งค์ปค๋์ฆ โ
getStaticProps๋ฅผ ์ฌ์ฉํ๋ ํ์ด์ง๊ฐ ๋น๋ ํ์์ Pre-Rendering ๋ ๋, Next.js๋ ํ์ด์ง HTML ํ์ผ๋ฟ๋ง ์๋๋ผ getStaticProps ์คํ ๊ฒฐ๊ณผ๋ฅผ ๋ด์ JSON ํ์ผ๋ ํจ๊ป ์์ฑํฉ๋๋ค.
JSON ํ์ผ์ next/link๋ next/router๋ฅผ ํตํ ํด๋ผ์ด์ธํธ ์ฌ์ด๋ ๋ผ์ฐํ ์ ์ฌ์ฉ๋ฉ๋๋ค. getStaticProps๋ก Pre-Rendering ๋ ํ์ด์ง๋ก ์ด๋ํ ๋, Next.js๋ ๋น๋ ํ์์ ๋ฏธ๋ฆฌ ๊ณ์ฐ๋ JSON ํ์ผ์ ๊ฐ์ ธ์ ํ์ด์ง ์ปดํฌ๋ํธ์ props๋ก ์ฌ์ฉํฉ๋๋ค. ์ฆ, ํด๋ผ์ด์ธํธ ์ฌ์ด๋ ํ์ด์ง ์ ํ ์์๋ ์ด๋ฏธ ์์ฑ๋ JSON๋ง ์ฌ์ฉํ๋ฏ๋ก getStaticProps๊ฐ ๋ค์ ํธ์ถ๋์ง ์์ต๋๋ค.
์ฆ๋ถ ์ ์ ์ฌ์์ฑ(ISR)์ ์ฌ์ฉํ๋ ๊ฒฝ์ฐ, ํด๋ผ์ด์ธํธ ์ฌ์ด๋ ๋ค๋น๊ฒ์ด์ ์ ํ์ํ JSON์ ์์ฑํ๊ธฐ ์ํด ๋ฐฑ๊ทธ๋ผ์ด๋์์ getStaticProps๊ฐ ์คํ๋ฉ๋๋ค. ์ด ๊ณผ์ ์์ ๋์ผํ ํ์ด์ง์ ๋ํด ์ฌ๋ฌ ๋ฒ์ ์์ฒญ์ด ๋ฐ์ํ๋ ๊ฒ์ ๋ณผ ์ ์์ง๋ง, ์๋๋ ๋์์ด๋ฉฐ ์ต์ข ์ฌ์ฉ์ ์ฑ๋ฅ์๋ ์ํฅ์ ๋ฏธ์น์ง ์์ต๋๋ค.
1-7. getStaticProps ์ฌ์ฉ ๊ฐ๋ฅ ๋ฒ์ โ
getStaticProps ํจ์๋ ์ค์ง 'ํ์ด์ง ํ์ผ'์์๋ง ์ฌ์ฉํ ์ ์์ต๋๋ค. getStaticProps๋ pages ํด๋ ๋ด์ ์๋ ํ์ด์ง์ฉ ํ์ผ์์๋ง export ํด์ผ ํฉ๋๋ค. ์ผ๋ฐ์ ์ธ UI ์ปดํฌ๋ํธ๋ ๋ชจ๋ ํ์ด์ง์ ๊ณตํต ์ค์ ์ ๋ด๋นํ๋ _app.js, _document.js ๊ฐ์ ํน์ ํ์ผ์์๋ ์ฌ์ฉํ ์ ์์ต๋๋ค. ๋ฆฌ์กํธ๊ฐ ํ๋ฉด์ ๊ทธ๋ฆฌ๊ธฐ ์ , ํด๋น ํ์ด์ง์ ํ์ํ ๋ฐ์ดํฐ๋ฅผ ์๋ฒ์์ ๋ฏธ๋ฆฌ ์๋ฒฝํ๊ฒ ์ค๋น(Pre-Fetching) ํด์ผ ํ๋ ๊ตฌ์กฐ์ ์ด์ ๋๋ฌธ์ ๋๋ค.
๋ํ, ๋ฐ๋์ '๋
๋ฆฝ๋ ํจ์'๋ก ๋ด๋ณด๋ด์ผ ํฉ๋๋ค. export async function getStaticProps() { ... }์ ๊ฐ์ด ๋
๋ฆฝ์ ์ธ ์ด๋ฆ์ ๊ฐ์ง ํจ์๋ก ๋ด๋ณด๋ด์ผ ์ ์์ ์ผ๋ก ์๋ํฉ๋๋ค. ๋ง์ฝ ํ์ด์ง ์ปดํฌ๋ํธ ๊ฐ์ฒด์ ์์ฑ์ผ๋ก ์ถ๊ฐํ๋ ๋ฐฉ์์ ์ฌ์ฉํ๋ฉด Next.js๊ฐ ์ด๋ฅผ ์ธ์ํ์ง ๋ชปํ๋ฏ๋ก ์ฃผ์๊ฐ ํ์ํฉ๋๋ค.
2. getStaticPaths ๐ฏ
2-1. getStaticPaths ์ฌ์ฉ โ
๋์ ๊ฒฝ๋ก๊ฐ ์๋ ํ์ด์ง์์ getStaticProps๋ฅผ ์ฌ์ฉํ๋ ๊ฒฝ์ฐ, ์ ์ ์ผ๋ก ์์ฑ๋ ๊ฒฝ๋ก ๋ชฉ๋ก์ ๋ฏธ๋ฆฌ ์ ์ํด์ผ ํฉ๋๋ค. ๋์ ๊ฒฝ๋ก๋ฅผ ์ฌ์ฉํ๋ ํ์ด์ง์์ getStaticPaths ํจ์๋ฅผ export ํ๋ฉด, Next.js๋ getStaticPaths์ ์ง์ ๋ ๋ชจ๋ ๊ฒฝ๋ก๋ฅผ ์ ์ ์ผ๋ก Pre-Rendering ํฉ๋๋ค. ๊ธฐ๋ณธ์ ์ธ ์ฌ์ฉ ํํ๋ ์๋์ ๊ฐ์ต๋๋ค.
export async function getStaticPaths() {
return {
paths: [
{
params: {
name: 'next.js',
},
},
],
fallback: true,
};
}
export async function getStaticProps() {
const res = await fetch('https://api.github.com/repos/vercel/next.js');
const repo = await res.json();
return { props: { repo } };
}
export default function Page({ repo }) {
return repo.stargazers_count;
}2-2. getStaticPaths ์ฌ์ฉ ์์ โ
getStaticPaths๋ ๋ณธ์ง์ ์ผ๋ก getStaticProps์ ๋์ผํ ์์ค์์ ๋ฐ์ดํฐ๋ฅผ ๊ฐ์ ธ์ต๋๋ค. CMS๋ DB, ํ์ผ ์์คํ ๋ฑ์์์. ํ์ง๋ง ๋ชฉ์ ์์๋ ๊ฒฐ์ ์ ์ธ ์ฐจ์ด๊ฐ ์์ต๋๋ค. about.js์ ๊ฐ์ ๊ณ ์ ๋ ๊ฒฝ๋ก์ ํ์ด์ง์ ๋ฌ๋ฆฌ, [id].js์ ๊ฐ์ ๋์ ๊ฒฝ๋ก๋ ๋น๋ ์์ ์ Next.js๊ฐ ๋๋์ฒด ๋ช ๊ฐ์ ํ์ด์ง๋ฅผ ๋ง๋ค์ด์ผ ํ ์ง ์ ์ ์์ต๋๋ค. ์ด ํ์ด๋ฐ์ getStaticPaths๊ฐ ๊ฐ์ ํ์ฌ "1๋ฒ๋ถํฐ 100๋ฒ๊น์ง์ ๊ฒ์๊ธ ์ฃผ์๊ฐ ์์ผ๋, ์ด ๋ชฉ๋ก์ ๊ธฐ๋ฐ์ผ๋ก ๋ฏธ๋ฆฌ ์ง๋๋ฅผ ๊ทธ๋ ค๋์ด๋ผ!"๋ผ๋ ์์ ๋ช ๋ น์ ๋ด๋ฆฝ๋๋ค. ๋ฐ์ดํฐ์ ๋ด์ฉ์ ๊ฐ์ ธ์ค๊ธฐ ์ ์ ์ฃผ์ ๋ฆฌ์คํธ๋ฅผ ํ์ ์ง๊ธฐ ์ํ ์ผ์ข ์ ์ฌ์ ์์ ์ธ ์ ์ ๋๋ค.
๋ฐ๋ผ์ getStaticPaths๋ SEO์ ์๋์ ์ธ ์ฑ๋ฅ์ด ๋์์ ์๊ตฌ๋๋ฉด์๋, ๊ทธ ๋์ ํ์ด์ง๊ฐ ๊ฐ๋ณ์ ์ธ ๋์ ๋ผ์ฐํ ๋ฐฉ์์ ๋ฐ๋ผ์ผ ํ ๋ ํ์์ ์ผ๋ก ์ฌ์ฉ๋ฉ๋๋ค. ์ ํ ์์ธ๋ ๋ธ๋ก๊ทธ ํฌ์คํธ ๋ฑ ํน์ ์ฌ์ฉ์์๊ฒ ์ข ์๋์ง ์๋ ๊ณต์ฉ ๋ฐ์ดํฐ๋ฅผ ๋ค๋ฃฐ ๋ ์ฌ์ฉํด์ผ ํ ๊ฒ ๊ฐ์ต๋๋ค.
๊ฒฐ๊ตญ getStaticPaths๋ฅผ ์ฌ์ฉํ๋ค๋ ๊ฒ์ ์์ฒ, ์๋ง ๊ฐ์ ์์ธ ํ์ด์ง๋ฅผ ์ฌ์ฉ์ ์์ฒญ์ด ๋ค์ด์ค๊ธฐ ์ ์ ๋ฏธ๋ฆฌ '์ค๋นํ๋ค'๋ผ๋ ๊ฒ์ ์๋ฏธํฉ๋๋ค. ์ฌ์ฉ์๋ ์ด๋ค ์์ธ ํ์ด์ง์ ์ ์ํ๋๋ผ๋ ์๋ฒ์ ์ฐ์ฐ์ ๊ธฐ๋ค๋ฆด ํ์ ์์ด, ์ด๋ฏธ ์ ์ธ๊ณ CDN์ ๋ฐฐํฌ๋ ์์ฑํ ํ์ด์ง๋ฅผ ๋น ๋ฅธ ์๋๋ก ๋ฐ์ ์ ์๊ฒ ๋ฉ๋๋ค.
2-3. getStaticProps์์ ์ํธ์์ฉ โ
getStaticPaths๋ ํ๋ก๋์ ํ๊ฒฝ์์ ์ค์ง ๋น๋ ์์ ์ ์คํ๋ฉ๋๋ค. ์ค์ํ ๊ฒ์ getStaticProps์์ ๊ด๊ณ์ ๋๋ค.
๊ฐ์ฅ ๋จผ์ next build ์ค์ getStaticPaths๋ฅผ ์คํํฉ๋๋ค. ์ดํ getStaticPaths๊ฐ ๋ฐํํ ๋ชจ๋ paths์ ๋ํด getStaticProps๋ฅผ ์คํํฉ๋๋ค. ์ด๋ fallback์ true๋ก ์ค์ ํ๋ฉด ๋น๋ ํ์์ ์์ฑ๋์ง ์์ ํ์ด์ง ์์ฒญ์ด ๋ค์ด์ค๋ฉด ๋ฐฑ๊ทธ๋ผ์ด๋์์ getStaticProps๊ฐ ์คํ๋๊ณ , blocking์ผ๋ก ์ค์ ํ๋ฉด ์ด๊ธฐ ๋ ๋๋ง์ด ์ผ์ด๋๊ธฐ ์ง์ ์ getStaticProps๊ฐ ํธ์ถ๋์ด ์๋ฃ๋ ๋๊น์ง ๊ธฐ๋ค๋ฆฝ๋๋ค.
getStaticPaths์ getStaticProps ๋ชจ๋ ์๋ฒ์ ๋น๋ ํ์์ด๋ผ๋ ํฐ ํ์์ ๋ณด๋ฉด ์์ ํ ๋์ผํ ๊ถค๋ฅผ ๊ทธ๋ฆฌ๋ฉฐ ์์ง์ ๋๋ค. ํต์ฌ์ SSG์ ๋๋ค. SSG์ ๋ชฉํ๋ ์ฌ์ฉ์๊ฐ ์์ฒญํ๊ธฐ ์ ์ ๋ชจ๋ ์ค๋น๋ฅผ ๋๋ด๋ ๊ฒ์ ๋๋ค. ๋ ํจ์ ๋ชจ๋ ๋น๋ ์์ ์ ์๋ฒ์์ ๋ฏธ๋ฆฌ ์์ ์ ๋๋ด๋ฒ๋ฆฌ๊ธฐ ๋๋ฌธ์, ์ฌ์ฉ์๊ฐ ์๋น์ค์ ์ ์ํ์ ๋์๋ ์๋ฒ๊ฐ ๋ฐ์ดํฐ๋ฒ ์ด์ค๋ฅผ ์ฐพ์๋ณผ ํ์ ์์ด ์ด๋ฏธ ๋ง๋ค์ด์ง HTML๊ณผ JSON ํ์ผ๋ง ํญ ๋์ ธ์ฃผ๋ฉด ๋๋ฉ๋๋ค.
getStaticPaths์ getStaticProps์ ๋ฏธ์ธํ ์ฐจ์ด๋ getServerSideProps์ Incremental Static Regeneration ๊ณผ์ ์์ ๋ฐ์ํ๋๋ฐ์, ์ด์ ๋ํด์๋ ํ์ ํ ์์ ์ ๋๋ค.
2-4. ์จ๋๋งจ๋(On-demand) ๊ฒฝ๋ก ์์ฑ โ
์ด๋ฏธ ์ธ๊ธํ์ง๋ง, getStaticPaths์ fallback ์ต์ ์ ํ์ฉํ๋ฉด ๋ชจ๋ ํ์ด์ง๋ฅผ ๋น๋ ์์ ์ ๋ง๋๋ ๋์ , ์์ฒญ์ด ๋ค์ด์ค๋ ์์ (On-demand)์ ์์ฑ๋๋๋ก ์ ์ดํ ์ ์์ต๋๋ค. ๋น๋ ์์ ์ ๋๋ฌด ๋ง์ ํ์ด์ง๋ฅผ ์์ฑํ๋ฉด ๋น๋ ์๋๊ฐ ๋๋ ค์ง ์ ์๊ธฐ ๋๋ฌธ์ ๋๋ค.
paths์ ๋น ๋ฐฐ์ด([])์ ๋ฐํํ๋ฉด ๋ชจ๋ ํ์ด์ง์ ์์ฑ์ On-demand ๋ฐฉ์์ผ๋ก ๋ฏธ๋ฃฐ ์ ์์ต๋๋ค. ์ด ๋ฐฉ์์ Next.js ์ ํ๋ฆฌ์ผ์ด์ ์ ์ฌ๋ฌ ํ๊ฒฝ์ ๋ฐฐํฌํ ๋ ํนํ ์ ์ฉํฉ๋๋ค. ์๋ฅผ ๋ค์ด, ์ ์ ํ์ด์ง๊ฐ ์๋ฐฑ, ์์ฒ ๊ฐ์ ๋ฌํ๋ ์ฌ์ดํธ์ ๊ฒฝ์ฐ ํ๋ฆฌ๋ทฐ ํ๊ฒฝ์์๋ ๋ชจ๋ ํ์ด์ง๋ฅผ On-demand๋ก ์์ฑํ๊ฒ ํ์ฌ ๋น๋ ์๋๋ฅผ ๋์ด๊ณ , ํ๋ก๋์ ํ๊ฒฝ์์๋ง ๋ฏธ๋ฆฌ ์์ฑํ๋ ๋ฐฉ์์ผ๋ก ํ์ฉํ ์ ์์ต๋๋ค.
export async function getStaticPaths() {
// ํ๊ฒฝ ๋ณ์ ์ค์ ์ ์ ์ ํ์ด์ง ๋ฏธ๋ฆฌ ์์ฑ ์ ํจ
// (๋น๋๋ ๋นจ๋ผ์ง์ง๋ง, ์ฒซ ์ ์ ์ ๋ก๋ฉ ์๋๋ ๋๋ ค์ง ์ ์์)
if (process.env.SKIP_BUILD_STATIC_GENERATION) {
return {
paths: [],
fallback: 'blocking',
}
}
// ์ธ๋ถ API๋ฅผ ํธ์ถํ์ฌ ๊ฒ์๊ธ ๋ชฉ๋ก ๊ฐ์ ธ์ค๊ธฐ
const res = await fetch('https://.../posts')
const posts = await res.json()
// ๊ฐ์ ธ์จ ๋ฐ์ดํฐ๋ฅผ ๊ธฐ๋ฐ์ผ๋ก ๋ฏธ๋ฆฌ ์์ฑํ ๊ฒฝ๋ก(ID) ๋ชฉ๋ก ์์ฑ
// ํ๋ก๋์
ํ๊ฒฝ์์๋ ๋ชจ๋ ํ์ด์ง๋ฅผ ๋ฏธ๋ฆฌ ์์ฑํ์ฌ ์๋ ์ต์ ํ
const paths = posts.map((post) => ({
params: { id: post.id },
}))
// ์ ์๋์ง ์์ ๊ฒฝ๋ก๋ 404 ์๋ฌ ์ฒ๋ฆฌ
return { paths, fallback: false }
}3. getServerSideProps ๐ฏ
3-1. getServerSideProps ์ฌ์ฉ โ
ํ์ด์ง ์ปดํฌ๋ํธ์์ getServerSideProps๋ฅผ export ํ์ฌ ์ฌ์ฉํ ์ ์์ต๋๋ค. ์๋๋ ๊ธฐ๋ณธ์ ์ธ ์ฌ์ฉ ์์์ ๋๋ค.
export async function getServerSideProps() {
const res = await fetch('https://api.github.com/repos/vercel/next.js')
const repo = await res.json()
return { props: { repo } }
}
export default function Page({ repo }) {
return (
<main>
<p>{repo.stargazers_count}</p>
</main>
)
}getServerSideProps๋ ๊ฐ์ธํ๋ ์ฌ์ฉ์ ๋ฐ์ดํฐ๋ ์์น ์ ๋ณด์ฒ๋ผ ์์ฒญ ์์ ์๋ง ์ ์ ์๋ ์ ๋ณด์ ์์กดํ๋ ํ์ด์ง๋ฅผ ๋ ๋๋ง ํด์ผ ํ ๋ ์ฌ์ฉํฉ๋๋ค.
์์ฒญ ์์ ์ ๋ฐ์ดํฐ๋ฅผ ๊ฐ์ ธ์ฌ ํ์๊ฐ ์๊ฑฐ๋, ๋ฐ์ดํฐ์ Pre-Rendering ๋ HTML์ ์บ์ฑ ํ๋ ์ํฉ์ด ๋ ์ ์ ํ๋ค๋ฉด getServerSideProps๊ฐ ์๋๋ผ getStaticProps๋ฅผ ์ฌ์ฉํ๋ ๊ฒ์ด ๋ ์ข๊ฒ ์ฃ ?
3-2. Edge Cases โ
getServerSideProps๋ฅผ ์ฌ์ฉํ ๋ ์ฃผ์ํด์ผ ํ ์ํฉ๋ค์ด ์์ต๋๋ค.
์์ธ ์ฒ๋ฆฌ์ ๋ฆฌ๋ค์ด๋ ํธ
๋ด๋ถ์์ ์๋ฌ๊ฐ ๋ฐ์ํ๊ฑฐ๋ ํน์ ์กฐ๊ฑด์ ๋ฐ๋ผ ์ฌ์ฉ์๋ฅผ ๋ค๋ฅธ ํ์ด์ง๋ก ๋ณด๋ด์ผ ํ ๋๊ฐ ์์ต๋๋ค. ์ด๋ฌํ ๊ฒฝ์ฐ notFound๋ redirect ์์ฑ์ ๋ฐํํ์ฌ ์ฒ๋ฆฌํ ์ ์๊ฒ ์ต๋๋ค.
export async function getServerSideProps(context) {
const res = await fetch(`https://api.example.com/data/${context.params.id}`)
// ๋ฐ์ดํฐ๊ฐ ์กด์ฌํ์ง ์๋ ๊ฒฝ์ฐ 404 ํ์ด์ง๋ก
if (!res.ok) {
return {
notFound: true,
}
}
const data = await res.json()
// ํน์ ์กฐ๊ฑด์ ๋ฐ๋ผ ๋ฆฌ๋ค์ด๋ ํธ
if (!data.published) {
return {
redirect: {
destination: '/',
permanent: false,
},
}
}
return {
props: { data },
}
}Context ๊ฐ์ฒด ํ์ฉ
getServerSideProps๋ ๋งค ์์ฒญ ์ ๋์ํ๊ธฐ์, ์์ฒญ ๊ด๋ จ ์ ๋ณด๋ฅผ ๋ด์ context ๊ฐ์ฒด์ ์ ๊ทผํ ์ ์์ต๋๋ค. ์ด ๋ถ๋ถ์ด getStaticProps์์ ๊ฒฐ์ ์ ์ธ ์ฐจ์ด์ ์ ๋๋ค.
export async function getServerSideProps(context) {
// context ๊ฐ์ฒด์๋ ๋ค์ํ ์ ๋ณด๊ฐ ๋ด๊ฒจ์์ต๋๋ค
const {
params,
req,
res,
query,
preview,
previewData,
resolvedUrl,
locale,
locales,
} = context
// ์ฟ ํค๋ ํค๋ ์ ๋ณด ์ ๊ทผ ๊ฐ๋ฅ
const token = req.cookies.auth_token
const userAgent = req.headers['user-agent']
return {
props: {
userAgent,
},
}
}์บ์ฑ ๋์ ์ ์ด
๋ง์ง๋ง์ผ๋ก, getServerSideProps๋ ๋งค ์์ฒญ๋ง๋ค ์คํ๋์ง๋ง Cache-Control ํค๋๋ฅผ ํตํด ์บ์ฑ ์ ๋ต์ ์ธ๋ฐํ๊ฒ ์กฐ์ ํ ์๋ ์์ต๋๋ค.
export async function getServerSideProps({ res }) {
// 60์ด ๋์ ์บ์ํ๊ณ , ๊ทธ ์ดํ 10์ด ๋์์ stale ์ฝํ
์ธ ๋ฅผ ์ ๊ณตํ๋ฉด์
// ๋ฐฑ๊ทธ๋ผ์ด๋์์ ์ฌ๊ฒ์ฆ(stale-while-revalidate)
res.setHeader(
'Cache-Control',
'public, s-maxage=60, stale-while-revalidate=10'
)
const data = await fetch('https://api.example.com/data')
const json = await data.json()
return {
props: { data: json },
}
}4. Incremental Static Regeneration(ISR) ๐ฏ
ISR์ ์ฐ๋ฆฌ๊ฐ ์์ ํ์ตํ๋ฏ, SSG์ ์ฑ๋ฅ๊ณผ SSR์ ์ ์ฐ์ฑ์ ๊ฒฐํฉํ ํ์ด๋ธ๋ฆฌ๋ ์ ๋ต์ ๋๋ค. ๋น๋ ์์ ์ ๋ชจ๋ ํ์ด์ง๋ฅผ ์์ฑํ๋ ๋์ , ์ผ๋ถ ํ์ด์ง๋ง ๋ฏธ๋ฆฌ ๋ง๋ค๊ณ ๋๋จธ์ง ํ์ด์ง๋ ์์ฒญ์ ๋ฐ๋ผ ์์ฑํ๋ฉด์๋, ํ ๋ฒ ์์ฑ๋ ํ์ด์ง๋ ์ผ์ ์๊ฐ ๋์ ์ฌ์ฌ์ฉํ ์ ์๋๋ก ํฉ๋๋ค.
4-1. On-Demand Revalidation โ
ISR์ revalidate ์ต์ ์ผ๋ก ์ค์ ํ ์๊ฐ์ด ์ง๋๋ฉด ๋ฐฑ๊ทธ๋ผ์ด๋์์ ํ์ด์ง๋ฅผ ์ฌ์์ฑํ๋ ๋ฐฉ์์ผ๋ก ๋์ํฉ๋๋ค.
// pages/blog/[slug].js
export async function getStaticProps({ params }) {
const post = await getPostBySlug(params.slug)
return {
props: { post },
// 60์ด๋ง๋ค ๋ฐฑ๊ทธ๋ผ์ด๋์์ ํ์ด์ง ์ฌ์์ฑ
revalidate: 60,
}
}
export async function getStaticPaths() {
return {
paths: [],
fallback: 'blocking',
}
}ํ์ง๋ง ๋๋ก๋ ์ผ์ ์๊ฐ์ด ์ง๋ ํ๊ฐ ์๋๋ผ, ๋ฐ์ดํฐ๊ฐ ๋ณ๊ฒฝ๋๋ ์ฆ์ ํ์ด์ง๋ฅผ ์ ๋ฐ์ดํธํด์ผ ํ ํ์๊ฐ ์์ต๋๋ค. ๊ฐ๋ น ๋ธ๋ก๊ทธ ํฌ์คํธ๋ฅผ ์์ ํ์๋ง์ ๋ณ๊ฒฝ์ฌํญ์ด ๋ฐ์๋๊ธฐ๋ฅผ ์ํ ์ ์์ฃ . ์ฆ On-Demand์ ์ํ Revalidation์ด ํ์ํ ๊ฒฝ์ฐ๊ฐ ์์ต๋๋ค.
On-Demand Revalidation์ ์ฌ์ฉํ ๋๋ getStaticProps์ revalidate ์๊ฐ์ ๋ช ์ํ ํ์๊ฐ ์์ต๋๋ค. ์๊ฐ ์ค์ ์ ์๋ตํ๋ฉด ๊ธฐ๋ณธ๊ฐ์ธ false๊ฐ ๋๋ฉฐ, ์ค์ง revalidate()๊ฐ ํธ์ถ๋ ๋๋ง ํ์ด์ง๋ฅผ ์ ๋ฐ์ดํธํฉ๋๋ค.
// pages/api/revalidate.js
export default async function handler(req, res) {
// ๋ณด์์ ์ํ ํ ํฐ ๊ฒ์ฆ
if (req.query.secret !== process.env.REVALIDATE_TOKEN) {
return res.status(401).json({ message: 'Invalid token' })
}
try {
// ํน์ ๊ฒฝ๋ก์ ํ์ด์ง๋ฅผ ์ฆ์ ์ฌ์์ฑ
await res.revalidate('/blog/my-post')
return res.json({ revalidated: true })
} catch (err) {
return res.status(500).send('Error revalidating')
}
}getServerSideProps๋ ๋งค ์์ฒญ๋ง๋ค ์๋ฒ์์ ์ฝ๋๋ฅผ ์คํํ์ฌ ๋ฐ์ดํฐ๋ฅผ ์ง์ ๊ฐ์ ธ์ค๊ธฐ์ ๋ณ๋์ ์บ์ฑ ๋ก์ง์ด ํ์ํ์ง ์์ต๋๋ค. ํ์ง๋ง ISR์ ๋น๋ ์์ ์ ์ด๋ฏธ ์์ฑ๋ ์ ์ ์์ฐ์ ์๋นํ๋ ๋ฐฉ์์ด๊ธฐ์, ๋ฌผ๋ฆฌ์ ์ธ ํ์ผ ๊ต์ฒด๋ฅผ ์ํ ๋ณ๋์ ํธ๋ฆฌ๊ฑฐ๊ฐ ํ์ํฉ๋๋ค.
์ฌ์ฉ์์ ํ์ด์ง ๋ฐฉ๋ฌธ ์์ ์ด ์๋๋ผ CMS์ ๋ฐ์ดํฐ ์์ ์์ ์ฒ๋ผ, ํน์ ์ด๋ฒคํธ๊ฐ ๋ฐ์ํ์ ๋ ์๋ฒ์ '์ ์ ํ์ผ ์ฌ์์ฑ'์ ๋ช ๋ นํด์ผ ํ๋ฏ๋ก, res.revalidate()๋ฅผ ์คํํ ์ ์๋ ๋ ๋ฆฝ์ ์ธ API ์๋ํฌ์ธํธ๊ฐ ์๊ตฌ๋ฉ๋๋ค.
ํ๋ง๋๋ก, ๋ฐ์ดํฐ ์์ฒญ๊ณผ ์บ์ ๋ฌดํจํ๋ ์คํ ์ฃผ์ฒด์ ์์ ์ด ์์ ํ ๋ค๋ฅธ ๋ก์ง์ด๊ธฐ์ API Route๋ฅผ ํตํด ์ ์ด๊ถ์ ๋ถ๋ฆฌํ์ฌ ๊ด๋ฆฌํฉ๋๋ค.
4-2. Error handling and revalidation โ
ISR ๋ฐฉ์์ ๋ฐ๋ฅผ ๋ ๋ฐ์ํ ์ ์๋ ์๋ฌ ์ํฉ๋ค์ ์์๋ณด๊ฒ ์ต๋๋ค.
Revalidation ์คํจ
getStaticProps ๋ด๋ถ์์ ์๋ฌ๊ฐ ๋ฐ์ํ๋๋ผ๋, ์ด๋ฏธ ์์ฑ๋ ํ์ด์ง๋ ์ ์ง๋ฉ๋๋ค. Revalidation ๊ณผ์ ์ค getStaticProps ๋ด์์ ์๊ธฐ์น ๋ชปํ ์๋ฌ๊ฐ ๋ฐ์ํ๋๋ผ๋, Next.js๋ ์ฆ์ ์๋ฌ ํ๋ฉด์ ๋์ฐ๋ ๋์ ๊ธฐ์กด์ ์ฑ๊ณต์ ์ผ๋ก ์์ฑ๋์๋ ์ต์ ์ ์ ํ์ด์ง๋ฅผ ๊ทธ๋๋ก ์ฌ์ฉ์์๊ฒ ์ ๊ณตํฉ๋๋ค. ๋ฐ์ดํฐ ์์ค๊ฐ ์ผ์์ ์ผ๋ก ๋ถ์์ ํ ์ํฉ์์๋ ์ฌ์ฉ์์๊ฒ ์ต์ํ์ ์ ์์ ์ธ ํ๋ฉด์ ๋ณด์ฅํ๋ฉฐ, ์์คํ ์ ๋ค์ ์ฌ๊ฒ์ฆ ์ฃผ๊ธฐ์ ๋ง์ถฐ ๋ค์ ํ์ด์ง ๊ฐฑ์ ์ ์๋ํฉ๋๋ค.
export async function getStaticProps() {
try {
const data = await fetchDataFromAPI()
return {
props: { data },
revalidate: 60,
}
} catch (error) {
// ์๋ฌ๊ฐ ๋ฐ์ํด๋ ์ด์ ์ ์์ฑ๋ ํ์ด์ง๋ ๊ณ์ ์ ๊ณต๋ฉ๋๋ค
// ๋ค์ ์ฌ๊ฒ์ฆ ์์ ์ ๋ค์ ์๋๋ฉ๋๋ค
console.error('Revalidation failed:', error)
// ์ด์ ํ์ด์ง๋ฅผ ์ ์งํ๋ ค๋ฉด throwํ์ง ์์ต๋๋ค
// ๋ง์ฝ ์๋ฌ ํ์ด์ง๋ฅผ ๋ณด์ฌ์ฃผ๊ณ ์ถ๋ค๋ฉด throw error
return {
notFound: true, // ๋๋ ์๋ฌ props ์ ๋ฌ
}
}
}์กฐ๊ฑด๋ถ Revalidation
๋ชจ๋ ํ์ด์ง๋ฅผ ๋์ผํ ์ฃผ๊ธฐ๋ก Revalidationํ ํ์๋ ์๊ฒ ์ฃ ? ๋ฐ์ดํฐ์ ํน์ฑ์ ๋ฐ๋ผ ์๋ก ๋ค๋ฅธ revalidate ๊ฐ์ ๋ฐํํ ์ ์์ต๋๋ค.
export async function getStaticProps({ params }) {
const post = await getPostBySlug(params.slug)
// ์ต๊ทผ ๊ฒ์๋ฌผ์ ์์ฃผ ์
๋ฐ์ดํธ, ์ค๋๋ ๊ฒ์๋ฌผ์ ๋ ์
๋ฐ์ดํธ
const isRecent = Date.now() - new Date(post.createdAt).getTime() < 7 * 24 * 60 * 60 * 1000 // 7์ผ
return {
props: { post },
// ์ต๊ทผ ๊ธ์ 1๋ถ, ์ค๋๋ ๊ธ์ 1์๊ฐ
revalidate: isRecent ? 60 : 3600,
}
}Revalidation ์ค๋ณต ๋ฐฉ์ง
๋์ผํ ํ์ด์ง์ ๋ํด ๋์์ ์ฌ๋ฌ Revalidation ์์ฒญ์ด ๋ค์ด์ฌ ์ ์์ต๋๋ค. Next.js๋ ๋ด๋ถ์ ์ผ๋ก ์ด๋ฌํ ์ํฉ์ ์ฒ๋ฆฌํ์ฌ ์ค๋ณต ์คํ์ ๋ฐฉ์งํ์ง๋ง, API ํธ์ถ์ ์ต์ ํํ๊ณ ์ถ๋ค๋ฉด ์ง์ ์ ์ดํ ์๋ ์์ต๋๋ค.
// lib/cache.js
const revalidationCache = new Map()
export async function fetchWithDedupe(key, fetcher, ttl = 5000) {
const cached = revalidationCache.get(key)
// ์ด๋ฏธ ์งํ ์ค์ธ ์์ฒญ์ด ์์ผ๋ฉด ์ฌ์ฌ์ฉ
if (cached && Date.now() - cached.timestamp < ttl) {
return cached.promise
}
const promise = fetcher()
revalidationCache.set(key, {
promise,
timestamp: Date.now(),
})
// ์บ์ ์ ๋ฆฌ
setTimeout(() => revalidationCache.delete(key), ttl)
return promise
}
// pages/blog/[slug].js
import { fetchWithDedupe } from '@/lib/cache'
export async function getStaticProps({ params }) {
const post = await fetchWithDedupe(
`post-${params.slug}`,
() => getPostBySlug(params.slug)
)
return {
props: { post },
revalidate: 60,
}
}5. Client-Side Fetching ๐ฏ
์๋ฒ์์ ๋ฐ์ดํฐ๋ฅผ ๊ฐ์ ธ์ HTML์ ๋ฏธ๋ฆฌ ๋ง๋๋ ๋ฐฉ์์ด ๋ฅ์ฌ๋ ์๋๋๋ค. ๋ธ๋ผ์ฐ์ ์์ ์ง์ ๋ฐ์ดํฐ๋ฅผ ๊ฐ์ ธ์ค๋ ๊ฒ์ด ๋ ์ ์ ํ ๊ฒฝ์ฐ๋ ์์ต๋๋ค.
5-1. Client-Side data fetching with useEffect โ
SEO๊ฐ ์ค์ํ์ง ์๊ฑฐ๋, ํ์ด์ง๋ฅผ Pre-Rendering ํ ํ์๊ฐ ์๊ฑฐ๋, ์ฌ์ฉ์ ์ํธ์์ฉ์ ๋ฐ๋ผ ๋ฐ์ดํฐ๊ฐ ์์ฃผ ์ ๋ฐ์ดํธ๋๋ ๊ฒฝ์ฐ์ ์ ํฉํฉ๋๋ค. ์ฌ์ค ์ง๊ธ๊น์ง ์์ฑํด์จ ๋ฐฉ์์ด๋ ํฉ๋๋ค.
import { useState, useEffect } from 'react'
function Profile() {
const [data, setData] = useState(null)
const [isLoading, setLoading] = useState(true)
useEffect(() => {
fetch('/api/profile-data')
.then((res) => res.json())
.then((data) => {
setData(data)
setLoading(false)
})
}, [])
if (isLoading) return <p>Loading...</p>
if (!data) return <p>No profile data</p>
return (
<div>
<h1>{data.name}</h1>
<p>{data.bio}</p>
</div>
)
}5-2. Client-Side data fetching with SWR โ
useEffect๋ก ์ง์ ๊ตฌํํ๋ ๊ฒ๋ ์ข์ง๋ง, ๋งค๋ฒ ๋ณด์ผ๋ฌ ํ๋ ์ดํธ ์ฝ๋๋ฅผ ์์ฑํด์ผ ํ๊ณ , ์บ์ฑ์ด๋ ์ฌ๊ฒ์ฆ ๋ฑ์ ๊ณ ๊ธ ๊ธฐ๋ฅ์ ์ง์ ๊ตฌํํ๊ธฐ๋ ์ด๋ ต์ต๋๋ค. ์ด๋ด ๋ SWR์ด๋ Tanstack Query์ ๊ฐ์ ๊ธฐ์ ์ ์ฌ์ฉํ๊ฒ ๋ฉ๋๋ค. Tanstack Query๋ ํ์์ ์์ฃผ ์ฌ์ฉํ์ผ๋ SWR ์ฝ๋๋ง ๊ฐ๋ณ๊ฒ ์ดํด๋ณด๊ฒ ์ต๋๋ค.
SWR - ๊ธฐ๋ณธ ์ฌ์ฉ๋ฒ
import useSWR from 'swr'
const fetcher = (url) => fetch(url).then((res) => res.json())
export default function Profile() {
const { data, error, isLoading } = useSWR('/api/user/profile', fetcher)
if (error) return <div>Failed to load</div>
if (isLoading) return <div>Loading...</div>
return (
<div>
<h1>{data.name}</h1>
<p>{data.bio}</p>
</div>
)
}SWR - Mutation๊ณผ Optimistic UI
import useSWR, { useSWRConfig } from 'swr'
export default function TodoList() {
const { data: todos } = useSWR('/api/todos', fetcher)
const { mutate } = useSWRConfig()
async function addTodo(title) {
// Optimistic UI ์
๋ฐ์ดํธ
mutate(
'/api/todos',
[...todos, { id: Date.now(), title, completed: false }],
false // ์ฌ๊ฒ์ฆ ๊ฑด๋๋ฐ๊ธฐ
)
// ์ค์ API ํธ์ถ
await fetch('/api/todos', {
method: 'POST',
body: JSON.stringify({ title }),
})
// ์๋ฒ ๋ฐ์ดํฐ๋ก ์ฌ๊ฒ์ฆ
mutate('/api/todos')
}
return (
<div>
{todos?.map((todo) => (
<div key={todo.id}>{todo.title}</div>
))}
<button onClick={() => addTodo('์ ํ ์ผ')}>์ถ๊ฐ</button>
</div>
)
}6. Outro ๐ฏ
Next.js Pages Router์ Data Fetching์ ํ์ด์ง์ ์ฑ๊ฒฉ์ ๋ฐ๋ผ ์ต์ ์ ์ฌ์ฉ์ ๊ฒฝํ์ ์ ๊ณตํ๊ธฐ ์ํ ๋ค ๊ฐ์ง ์ ๋ต์ผ๋ก ๋๋ฉ๋๋ค.
๋ชจ๋ ์ฌ์ฉ์์๊ฒ ๋์ผํ ํ๋ฉด์ ๋ณด์ฌ์ฃผ๋ SSG๋ getStaticProps์ getStaticPaths๋ฅผ ํตํด ๋น๋ ํ์์ ์ ์ ์์ฐ์ ๋ฏธ๋ฆฌ ์์ฑํ์ฌ ์๋์ ์ธ ์ฑ๋ฅ๊ณผ SEO๋ฅผ ๋ณด์ฅํ๋ฉฐ, ๊ฐ์ธํ๋ ์ต์ ๋ฐ์ดํฐ๊ฐ ํ์ํ ๊ฒฝ์ฐ์๋ getServerSideProps๋ฅผ ํ์ฉํด ๋งค ์์ฒญ๋ง๋ค ์๋ฒ์์ ๋ ๋๋ง์ ์ํํฉ๋๋ค.
์ด์ ๋ํด ISR์ ์ ์ ํ์ด์ง ๋ฐฐํฌ ํ์๋ ๋ฐฑ๊ทธ๋ผ์ด๋์์ ๋ฐ์ดํฐ๋ฅผ ์ฃผ๊ธฐ์ ์ผ๋ก ๊ฐฑ์ ํ์ฌ SSG์ ํจ์จ์ฑ๊ณผ SSR์ ์ ์ฐ์ฑ์ ๋์์ ํ๋ณดํ๋ฉฐ, ๋์๋ณด๋์ฒ๋ผ ์ค์๊ฐ ์ํธ์์ฉ์ด ์ค์ํ ์์ญ์ SWR์ด๋ Tanstack Query๋ฅผ ํ์ฉํ CSR ๋ฐฉ์์ผ๋ก ๋ณด์ํ์ฌ ํจ์จ์ ์ธ ๋ฐ์ดํฐ ์ํคํ ์ฒ๋ฅผ ๊ตฌ์ถํ ์ ์์ต๋๋ค.
More to read
Amazon VPC Architecture ์ดํดํ๊ธฐ
์๋ก์ด ํ๋ก์ ํธ๋ฅผ ๊ธฐํํ๋ฉฐ, ๊ฐ๋ฐ์์ ๋ฌด์์ ๊ฐ์ฅ ๋จผ์ ๊ณ ๋ฏผํด์ผ ํ๋์ง ๋ค์ ๋์๋ณด๊ฒ ๋์์ต๋๋ค.ํ๋๋ ํ๋ก ํธ์๋๊ฐ ๋ชจ๋ ์ค๊ณ์ ์ถ๋ฐ์ ์ด๋ผ๊ณ ๋ฏฟ์์ต๋๋ค. ์ ์ ๊ฐ ๋ฌด์์ ๋ณด๊ณ , ์ด๋ค ํ๋ฆ์์ ๋จธ๋ฌด๋ฅด๊ณ ์ดํํ๋์ง์ ๋ํ ์ดํด ์์ด ์๋น์ค๋ฅผ ๋ง๋ ๋ค๋ ๊ฑด ๋ถ๊ฐ๋ฅํ๋ค๊ณ ์๊ฐํ๊ธฐ
'์์ฌ์ดํธ'ํ๋ก ํธ์๋ ๊ด์ ์ผ๋ก ์๊ณ ๋ฆฌ์ฆ ์ดํดํ๊ธฐ
์ค๋๋ง์ ๋ฐฉ๋ฒ๋ก ์ ๊ดํ ๊ธ์ ์ฐ๊ฒ ๋์์ต๋๋ค. ์ต๊ทผ ์ํฉ์ ์ด๋ ์ต๋๋ค. SSAFY์์๋ ํ๋ฃจ์ ์์ฒญ๋ ์์ ์๊ณ ๋ฆฌ์ฆ ๋ฌธ์ ๋ค์ ๊ณผ์ ๋ก ์ํํ๊ฒ ๋ฉ๋๋ค. ๊ทธ ๊ณผ์ ์์, '๊ตฌํ๋ ฅ'์ด ๋งค์ฐ ๋จ์ด์ง๋ค๋ ์๊ฐ์ด ๋ค์์ต๋๋ค. ์์ ํ ์ด๋ ค์ด ๋ฌธ์ ๋ผ๋ฉด '์์ฌ์'์ด๋ผ๋ ๊ฐ์ ์กฐ์ฐจ ๋๋ผ์ง
SubnetVPC ์ค๊ณ์ ์์: IP์ Subnet
๋ฐ๋ณต๋๋ ๋ฃจํด ์์์ ์ป์ ์์ ๊ฐ์ ๋ฐํ ์ผ์, ์ด์ ๋ ๊ธฐ์ ์ ์คํํธ๋ผ์ ๋ํ๊ธฐ ์ํ ๊ฐ์ธ ํ๋ก์ ํธ์ ์ฐฉ์ํ๊ณ ์ ํฉ๋๋ค.์ด๋ฒ ํ๋ก์ ํธ์ ๋ชฉํ๋ ๋จ์ํ ํฌํธํด๋ฆฌ์ค ๊ตฌ์ถ์ ๋์ด, ์ค์ ์๋น์ค ์์ค์ ๋ธ๋ก๊ทธ ์์คํ ๊ตฌํ๊ณผ ๋ค๊ตญ์ด ์ฒ๋ฆฌ ์ ์ฉ ๋ฑ ์ค๋ฌด์ ๊ฐ๊น์ด ์ญ๋์ ํ ๋จ๊ณ