SSG(Static Site Generation)λ?π
Next.jsμμ SSG(Static Site Generation, μ μ μ¬μ΄νΈ μμ±)
λΉλ μμ μ HTMLμ μμ±νμ¬, μμ² μ λΉ λ₯΄κ² μ 곡ν μ μλλ‘ νλ λ λλ§ λ°©μμ λλ€.
μ¦, νμ΄μ§λ₯Ό μλ²μμ 미리 λ λλ§ν΄λκ³ μ μ μΈ HTML νμΌλ‘ μ μ₯νμ¬, μ¬μ©μκ° μ μν λ μ¦μ μ 곡νλ λ°©μμ΄μμ.
μ΄λ κ² νλ©΄ μ¬μ©μμκ² λΉ λ₯Έ λ‘λ© μλλ₯Ό μ 곡νκ³ , κ²μ μμ§ μ΅μ ν(SEO)μλ μ 리νλ€λ μ₯μ μ΄ μμ΅λλ€.
π₯Έ SSGλ μ μ겨λ¬μκΉμ?
κ³Όκ±°μλ λμ μ½ν μΈ λ₯Ό μ 곡νκΈ° μν΄ μλ²μ¬μ΄λ λ λλ§(SSR)μ΄λ ν΄λΌμ΄μΈνΈμ¬μ΄λ λ λλ§(CSR) λ°©μμ΄ μ£Όλ‘ μ¬μ©λμμ΅λλ€.
κ·Έλ¬λ μ΄ λ°©μμ νμ΄μ§ λ‘λ© μκ°μ΄λ μλ² λΆν λ¬Έμ λ±μμ νκ³λ₯Ό λλ¬λμ΅λλ€.
νΉν, μ¬μ©μκ° μΉμ¬μ΄νΈλ₯Ό λ°©λ¬Έν λλ§λ€ λ§€λ² μλ²μμ μμ²νκ³ μλ΅μ λ°μμΌ νλ κ³Όμ μ΄ UX κ²½νμ μλΉν μμ’μμ£ .
μ΄λ¬ν λ¬Έμ λ₯Ό ν΄κ²°νκΈ° μν΄ CSRκ³Ό SSRκ³Όλ λ€λ₯΄κ² "κ·Έλ₯ 미리 λΉλν΄λ²λ¦¬μ!"λ νμμ SSGκ° λ±μ₯νκ² λμμ΅λλ€.
μ΄λ μ μ μΈ νμ΄μ§λ₯Ό 미리 μμ±νμ¬ μ¬μ©μκ° μμ²ν λ μ¦μ μ 곡ν¨μΌλ‘μ¨ μ±λ₯μ νκΈ°μ μΌλ‘ ν₯μμμΌ°μ΅λλ€.
π₯ SSG(Static Site Generation)μ μ리
π λΉλ νμμ HTMLμ μμ±
- Next.jsλ getStaticProps(Pages Router) λλ generateStaticParams(App Router)λ₯Ό μ¬μ©νμ¬ μ μ μΈ λ°μ΄ν°λ₯Ό κ°μ Έμ€κ³ , μ΄λ₯Ό λ°νμΌλ‘ 미리 HTMLμ μμ±ν©λλ€.
- μμ±λ HTMLμ CDN(μ½ν μΈ μ μ‘ λ€νΈμν¬)μ λ°°ν¬λμ΄ μ΄κ³ μ λ‘λ© μλλ₯Ό μ 곡ν©λλ€.
- λ°λΌμ, μλ² μμ² μμ΄ HTMLκ³Ό νμν μλ°μ€ν¬λ¦½νΈλ§ λ‘λνλ©΄ λ!
π SSGκ° μ ν©ν μν©
- βοΈ λ³κ²½μ΄ μ κ³ , λͺ¨λ μ¬μ©μμκ² λμΌν λ°μ΄ν°λ₯Ό μ 곡νλ νμ΄μ§
- βοΈ κ²μ μμ§ μ΅μ ν(SEO)κ° μ€μν λΈλ‘κ·Έ, λ¬Έμ μ¬μ΄νΈ, λ§μΌν νμ΄μ§
- βοΈ μλ² λΆνλ₯Ό μ€μ΄κ³ μΆμ λ
π SSGμ μ£Όμ ν¨μ
π Pages Routerμμ μ¬μ©
export async function getStaticProps() {
const res = await fetch('https://jsonplaceholder.typicode.com/posts');
const posts = await res.json();
return {
props: { posts }, // λΉλ νμμ posts λ°μ΄ν°λ₯Ό κ°μ Έμμ μ μ μΈ HTMLμ μ½μ
};
}
π App Routerμμ μ¬μ©
export async function generateStaticParams() {
const res = await fetch('https://jsonplaceholder.typicode.com/posts');
const posts = await res.json();
return posts.map((post) => ({
slug: post.id.toString(),
}));
}
π― CSR, SSRκ³Ό λΉκ΅
λ λλ§ λ°©μ | μ€λͺ | μμ² μ μλ² μμ | μ΄κΈ° λ‘λ© μλ | SEO |
CSR (Client-Side Rendering) | ν΄λΌμ΄μΈνΈμμ λ°μ΄ν°λ₯Ό κ°μ Έμ λ λλ§ | νμ | λλ¦Ό | SEOμ λΆλ¦¬ |
SSR (Server-Side Rendering) | μμ²λ§λ€ μλ²μμ HTMLμ μμ± | νμ | λλ¦Ό | SEO κ°λ₯ |
SSG (Static Site Generation) | λΉλ μ HTMLμ μμ±νμ¬ μ 곡 | μμ | λΉ λ¦ | SEO κ°λ₯ |
β CSR(Client-Side Rendering)κ³Ό μ°¨μ΄μ
- CSRμ μ΄κΈ° HTMLμ λ°μ΄ν°κ° μμ, λΈλΌμ°μ μμ API μμ² ν λ°μ΄ν°λ₯Ό κ°μ Έμ 그립λλ€.
- μ¬μ©μκ° μ²μ νμ΄μ§λ₯Ό λ‘λν λκΉμ§ λΉ νλ©΄μ΄ λμ¬ μ μμ(Loading UI νμ)
β SSR(Server-Side Rendering)κ³Ό μ°¨μ΄μ
- SSRμ λ§€ μμ²λ§λ€ μλ²μμ HTMLμ μμ±νκΈ° λλ¬Έμ TTV(Time To View)κ° λ¦μ κ°λ₯μ±μ΄ μμ
- SSGλ ν λ²λ§ λΉλνλ©΄ λμ΄λΌ μ΄κΈ° λ‘λ©μ΄ λ λΉ λ¦.
π SSGμμ λΉλλ λ μμ±λλ νμΌ
π οΈ SSGλ‘ μμ±λ νμΌμ νμΈνλ λ°©λ²
1οΈβ£ Next.js νλ‘μ νΈλ₯Ό λΉλν©λλ€.
npm run build
2οΈβ£ .next/ ν΄λλ₯Ό νμΈν΄λ³΄λ©΄, μ μ HTML νμΌμ΄ μμ±λ©λλ€.
ls .next/static
3οΈβ£ out/ ν΄λμμ HTML νμΌμ νμΈν μλ μμ΅λλ€.
npm run export # μ μ μ¬μ΄νΈλ₯Ό export
ls out/
ποΈ μμ±λλ μ£Όμ νμΌλ€
π .next/ λ΄λΆ ꡬ쑰
.next/
βββ static/ # μ μ μΈ JS, CSS, μ΄λ―Έμ§ λ±
βββ server/ # SSR κ΄λ ¨ νμΌ
βββ build-manifest.json # λΉλλ 리μμ€ λͺ©λ‘
βββ prerender-manifest.json # SSGλ νμ΄μ§ λͺ©λ‘
βββ routes-manifest.json # κ²½λ‘ κ΄λ¦¬
π₯ Hydration κ³Όμ μμ μ΄λ€ μΌμ΄ μΌμ΄λ κΉ?
- HTMLμ 미리 λ λλ§λμ΄ μ 곡λμ§λ§, Reactκ° λΈλΌμ°μ μμ μ΄λ₯Ό νμ±ν(hydrate)νμ¬ μνΈμμ© κ°λ₯νλλ‘ ν¨
- λΈλΌμ°μ μμ JavaScriptλ₯Ό μ€ννλ©΄μ ν΄λ¦, μ λ ₯ λ±μ μ΄λ²€νΈλ₯Ό μ°κ²°νλ κ³Όμ μ΄ ν¬ν¨λ¨
- λ°λΌμ, Hydrationμ΄ μλ£λκΈ° μ κΉμ§λ λ²νΌ ν΄λ¦μ΄ μ λ μλ μμ!
π App Router(νμΌ κΈ°λ° λΌμ°ν )μμ SSG μ¬μ© μ μ°¨μ΄μ
π Pages Router(getStaticProps) vs. App Router(generateStaticParams)
Pages Router (getStaticProps) | App Router (generateStaticParams) | |
νΈμΆ μμ | λΉλ νμ | λΉλ νμ |
λ°μ΄ν° μ 곡 λ°©μ | propsλ‘ μ λ¬ | paramsλ‘ μ μ κ²½λ‘ μμ± |
λ€μ΄λ΄λ―Ή λΌμ°ν μ§μ | β | β |
ν΄λΌμ΄μΈνΈ μμ² νμ μ¬λΆ | β | β |
π‘ κ²°λ‘
App Routerλ κ²½λ‘λ₯Ό 미리 μμ±νμ¬ μ μ HTMLλ‘ μ 곡νλ λ°©μμ΄μ§λ§,
Pages Routerμ λ€λ₯΄κ² generateStaticParams()λ‘ λμ κ²½λ‘λ₯Ό μ μ μΌλ‘ μ μν΄μΌ νλ€λ μ°¨μ΄μ μ΄ μμ.
β SSGκ° μ ν©ν κ²½μ° vs SSRμ΄ μ ν©ν κ²½μ°
μꡬ μ¬ν | SSG(Static Generation) | SSR(Server-Side Rendering) |
λ°μ΄ν° λ³κ²½ λΉλ | κ±°μ λ³νμ§ μμ (λΈλ‘κ·Έ, λ¬Έμ) | μμ£Ό λ³κ²½λ¨ (μ¬μ©μ νλ‘ν, λμ보λ) |
SEO μ€μλ | λ§€μ° μ€μ (κ²μμμ§ μ΅μ ν) | μ€μνμ§λ§, SSG보λ€λ λλ¦Ό |
첫 νμ΄μ§ λ‘λ© μλ | λΉ λ¦ (μ μ HTML μ 곡) | μλμ μΌλ‘ λλ¦Ό (μλ² λ λλ§ νμ) |
API μμ² νμ μ¬λΆ | β (미리 HTML μμ±) | β (μμ²λ§λ€ μλ‘μ΄ λ°μ΄ν°) |
π‘ κ²°λ‘ :
- βοΈ SSGλ μ μ μΈ νμ΄μ§μ κ°λ ₯νμ§λ§, μ€μκ° λ°μ΄ν°κ° νμν κ²½μ° SSR λλ CSRμ κ³ λ €ν΄μΌ ν¨
- βοΈ μ΄λ―Έμ§ κ°μ μ μ νμΌμ CDNκ³Ό ν¨κ» μ¬μ©νλ©΄ μ±λ₯ μ΅μ ν κ°λ₯
- βοΈ λ€μ΄λλ―Ή API λ°μ΄ν°κ° λ§λ€λ©΄ ISR(Incremental Static Regeneration) κ³ λ € κ°λ₯
π₯ SSG vs. MPA(λ©ν° νμ΄μ§ μ ν리μΌμ΄μ )
SSG (Static Site Generation) | MPA (Multi-Page Application) | |
νμ΄μ§ λ‘λ© λ°©μ | 미리 μμ±λ μ μ HTML μ 곡 | μλ‘μ΄ νμ΄μ§ μμ²λ§λ€ μ 체 HTML λ€μ λ€μ΄λ‘λ |
SEO | β λ§€μ° μ 리 (HTMLμ΄ λ―Έλ¦¬ μμ±λ¨) | β μ 리νμ§λ§, μλ² λ λλ§μ΄ νμ |
μλ | β μ΅μ΄ λ‘λ© λΉ λ¦ | β λ€νΈμν¬ μμ² λ§μΌλ©΄ λλ €μ§ μ μμ |
μλ² λΆν | β μλ² μμ²μ΄ κ±°μ μμ | β λ§€ νμ΄μ§λ§λ€ μλ²μ μμ² |
UX | β λΆλλ¬μ΄ μ ν (ν΄λΌμ΄μΈνΈ λ΄λΉκ²μ΄μ ) | β μλ‘κ³ μΉ¨μ΄ λ°μνμ¬ UXκ° λ λΆλλ¬μ |
μ¬μ© μ¬λ‘ | β λΈλ‘κ·Έ, λ¬Έμ μ¬μ΄νΈ, μ μ μ½ν μΈ | β κ΄λ¦¬μ νμ΄μ§, ν¬νΈ μ¬μ΄νΈ, κ²μ κΈ°λ° μλΉμ€ |
β MPAλ μ΄λ€ κ²½μ°μ λ μ 리ν κΉ?
- κ²μ κΈ°λ° μΉ μλΉμ€: κ²μ μμ§μ΄ λ§€λ² μλ‘μ΄ λ°μ΄ν°λ₯Ό κ°μ ΈμμΌ ν λ
- λν μ¬μ΄νΈ/ν¬νΈ: λ€λΉκ²μ΄μ μ΄ μ€μνκ³ , νμ΄μ§ κ° μν 곡μ κ° μ μ λ
- 보μμ΄ μ€μν μλΉμ€: λ§€ μμ²λ§λ€ μλ‘μ΄ HTMLμ λΆλ¬μ€λ κ²μ΄ 보μμ μ 리
π‘ κ²°λ‘
MPAλ λ§€ μμ²λ§λ€ μ 체 HTMLμ λ€μ λ°μμ€κΈ° λλ¬Έμ UXκ° λ€μ λ¨μ΄μ§ μ μμ§λ§,
μλ² μ€μ¬ μ ν리μΌμ΄μ μμλ μ¬μ ν κ°λ ₯ν μ νμ§μμ.
π§ SSGκ° νμ μ΅κ³ μ μ νμΌκΉ?
SSGλ μλμ SEO μΈ‘λ©΄μμ κ°λ ₯νμ§λ§, λͺ¨λ μν©μμ μ΅μ μ μλμμ. μλμ κ°μ νκ³μ μ΄ μ‘΄μ¬ν©λλ€.
β (1) λ°μ΄ν°κ° μμ£Ό λ³κ²½λλ©΄ μ ν©νμ§ μμ
- SSGλ λΉλ μ λ°μ΄ν°λ₯Ό κ³ μ νκΈ° λλ¬Έμ, μμ£Ό λ³νλ λ°μ΄ν°μλ μ ν©νμ§ μμ
- μλ₯Ό λ€μ΄, μ€μκ° λ΄μ€, μ£Όμ κ°κ²©, μ¬μ©μ νλ‘ν νμ΄μ§μ κ°μ΄ μμ£Ό μ λ°μ΄νΈλλ λ°μ΄ν°λ SSGκ° λΉν¨μ¨μ μΌ μ μμ΄μ.
β ν΄κ²° λ°©λ²: ISR(Incremental Static Regeneration) μ¬μ©νκΈ°
Next.jsμ ISR(μ¦λΆ μ μ μμ±)μ νμ©νλ©΄, νΉμ μ£ΌκΈ°λ‘ μ μ νμ΄μ§λ₯Ό λ€μ μμ±ν μ μμ΄μ.
export async function getStaticProps() {
const res = await fetch('https://api.example.com/data');
const data = await res.json();
return {
props: { data },
revalidate: 60, // 60μ΄λ§λ€ νμ΄μ§ μ¬μμ±
};
}
π μ΄λ κ² νλ©΄ μ μ νμ΄μ§μ μ±λ₯μ μ μ§νλ©΄μλ μ΅μ λ°μ΄ν° λ°μ κ°λ₯! π―
β (2) λΉλ μκ°μ΄ μ€λ 걸릴 μ μμ
- νμ΄μ§κ° λ§μμλ‘ λΉλ μκ°μ΄ κΈΈμ΄μ§
- μλ₯Ό λ€μ΄, μμ² κ°μ λΈλ‘κ·Έ κΈμ SSGλ‘ μμ±νλ©΄ λΉλ μκ°μ΄ μμ λΆ μ΄μ μμλ μλ μμ
β ν΄κ²° λ°©λ²: λμ κ²½λ‘ μ ν λ° ISR μ μ©
- generateStaticParams() λλ getStaticProps()μμ νμν λ°μ΄ν°λ§ μ¬μ μμ±νκ³ , λλ¨Έμ§λ λμ μμ²μΌλ‘ μ²λ¦¬
- ISRμ νμ©νμ¬ μΌλΆ νμ΄μ§λ μ μ μμ± ν μ μ§μ μΌλ‘ μ λ°μ΄νΈ
β (3) μ¬μ©μλ³ λ°μ΄ν°κ° νμν κ²½μ° μ΄λ €μ
- SSGλ ν λ² μμ±λλ©΄ λͺ¨λ μ¬μ©μμκ² λμΌν HTMLμ μ 곡
- νμ§λ§ λ‘κ·ΈμΈν μ¬μ©μλ§λ€ λ€λ₯Έ λ°μ΄ν°λ₯Ό μ 곡ν΄μΌ νλ κ²½μ° SSGλ μ ν©νμ§ μμ
- μλ₯Ό λ€μ΄, μΌνλͺ°μ λ§μ΄νμ΄μ§, λμ보λ, μ¬μ©μ λ§μΆ€ μ½ν μΈ λ±
β ν΄κ²° λ°©λ²:
1οΈβ£ SSG + ν΄λΌμ΄μΈνΈ μ¬μ΄λ λ°μ΄ν° ν¨μΉ(CSR) μ‘°ν©
function UserProfile() {
const [user, setUser] = useState(null);
useEffect(() => {
fetch('/api/user')
.then((res) => res.json())
.then((data) => setUser(data));
}, []);
if (!user) return <p>Loading...</p>;
return <p>Welcome, {user.name}!</p>;
}
2οΈβ£ SSR(Server-Side Rendering)λ‘ νΉμ νμ΄μ§λ§ μλ²μμ λ λλ§
export async function getServerSideProps(context) {
const res = await fetch(`https://api.example.com/user/${context.params.id}`);
const user = await res.json();
return { props: { user } };
}
π κ²°λ‘ : SSG + CSR + ISR + SSRμ μ μ ν μ‘°ν©νλ©΄ λ¬Έμ ν΄κ²° κ°λ₯! π₯
π― SSGκ° λΉμ λ°νλ κ²½μ° vs. λ€λ₯Έ μ λ΅μ΄ νμν κ²½μ°
μν© | SSG μ¬μ© κ°λ₯? | λμ |
β μ μ μΈ μ½ν μΈ (λΈλ‘κ·Έ, λ¬Έμ) | SSG | |
β μ€μκ° λ°μ΄ν° (λ΄μ€, μ£Όμ) | ISR / CSR / SSR | revalidate μ¬μ© |
β μ¬μ©μλ³ λ§μΆ€ λ°μ΄ν° (λμ보λ) | SSR / CSR | getServerSideProps λλ API μμ² |
β λ§€μ° λ§μ νμ΄μ§ (100,000+ νμ΄μ§) | SSG + ISR | generateStaticParamsλ‘ μ ν |
β SEOκ° μ€μν νμ΄μ§ | SSG / SSR | |
β μλκ° μ€μν λλ© νμ΄μ§ | SSG |
π λ§λ¬΄λ¦¬
π‘ SSGλ λΉ λ₯΄κ³ SEO μ΅μ νμ μ 리νμ§λ§, νμ μ λ΅μ μλλ€!
π‘ μμ£Ό λ³νλ λ°μ΄ν°, μ¬μ©μλ³ μ½ν μΈ κ° μλ€λ©΄ ISR/SSR/CSRμ μ‘°ν©νλ κ²μ΄ νμνλ€.
π μ 리νλ©΄:
β λ³κ²½μ΄ κ±°μ μλ νμ΄μ§λ SSGκ° μ΅κ³
β λ°μ΄ν°κ° κ°λ λ³νλ€λ©΄ ISR(Incremental Static Regeneration) μ¬μ©
β μ¬μ©μλ³ λ°μ΄ν°κ° νμνλ©΄ SSR λλ CSR νμ©
π·μ μ€μ κ°λ°μκ° λμ΄λ΄ μλΉ! π·