μ€λμ λΈλΌμ°μ λ λλ§ λ°©μμΈ SSG, CSR, SSR, ISR λ΄λΆ μλμ리μ λν΄ μ΄ν΄λ³Ό κ²μ΄λ€.
1. SSG (Static Site Generation) μλμ리
μ 체 νλ¦λ
[λΉλ νμ] [λ°νμ]
κ°λ°μ μ½λ μ¬μ©μ μμ²
↓ ↓
npm run build CDN/Server
↓ ↓
λ°μ΄ν° νμΉ (API νΈμΆ) μ μ HTML νμΌ μ μ‘
↓ ↓
HTML μμ± (λͺ¨λ νμ΄μ§) λΈλΌμ°μ μμ μ¦μ λ λλ§
↓ ↓
.next/static ν΄λμ μ μ₯ (HydrationμΌλ‘ μΈν°λν°λΈ)
1. λΉλνμ(Build Time)
- κ°λ°μκ° μ½λλ₯Ό μμ±νκ³ nun run build λͺ λ Ήμ΄λ₯Ό μ€ννλ©΄, λͺ¨λ νμ΄μ§κ° 미리 HTMLλ‘ μμ±λλ€.
- μ΄ κ³Όμ μμ νμν λ°μ΄ν°κ° μλ€λ©΄ APIλ₯Ό νΈμΆν΄ κ°μ Έμ€κ³ , κ°μ Έμ¨ λ°μ΄ν°λ₯Ό λ°νμΌλ‘ HTML νμΌμ λ§λ€μ΄ .next/static (NextλΌλ©΄) κ°μ ν΄λμ μ μ₯νλ€.
- μ¦, μ¬μ©μκ° νμ΄μ§λ₯Ό μμ²νκΈ° μ 미리 λͺ¨λ νμ΄μ§λ₯Ό μ€λΉνλ λ¨κ³μ΄λ€.
2. λ°νμ(Runtime)
- μ¬μ©μκ° λΈλΌμ°μ μμ νμ΄μ§λ₯Ό μμ²νλ©΄, μλ²λ CDNμ΄ λ―Έλ¦¬ λ§λ€μ΄λ μ μ HTMLνμΌμ μ¦μ μ μ‘νλ€.
- λΈλΌμ°μ μμλ λ°μ HTMLμ λ°λ‘ νλ©΄μ 보μ¬μ£Όκ³ , νμνλ©΄ Reactμ Hydration κ³Όμ μ ν΅ν΄ μΈν°λν°λΈ κΈ°λ₯(λ²νΌ ν΄λ¦, νΌ μ λ ₯ λ±)μ μ¬μ©ν μ μλ€.
μμΈ μλ κ³Όμ
1λ¨κ³: λΉλ νμ (Build Time)
npm run build
λ΄λΆμμ μΌμ΄λλ μΌ
1. Next.jsκ° λͺ¨λ νμ΄μ§ μ€μΊ
ββ app/page.tsx
ββ app/about/page.tsx
ββ app/blog/[slug]/page.tsx
ββ ...
2. κ° νμ΄μ§λ³λ‘ μ€ν:
ββ generateStaticParams() νΈμΆ (λμ λΌμ°νΈ)
ββ λ°μ΄ν° νμΉ ν¨μ μ€ν
ββ React μ»΄ν¬λνΈ λ λλ§
ββ HTML νμΌ μμ±
3. μΆλ ₯λ¬Ό:
ββ .next/server/app/page.html
ββ .next/server/app/about.html
ββ .next/server/app/blog/post-1.html
ββ JavaScript λ²λ€ (.next/static/chunks/)
Next.js SSG λ΄λΆμμ μΌμ΄λλ κ³Όμ
- νμ΄μ§ μ€μΊ
- Next.jsλ λΉλ κ³Όμ μμ νλ‘μ νΈ λ΄ λͺ¨λ νμ΄μ§λ₯Ό νμΈν©λλ€.
- μλ₯Ό λ€μ΄:
- app/page.tsx → ν νμ΄μ§
- app/about/page.tsx → μκ° νμ΄μ§
- app/blog/[slug]/page.tsx → λμ λΈλ‘κ·Έ κΈ νμ΄μ§
- μ΄λ κ² λͺ¨λ νμ΄μ§λ₯Ό μλμΌλ‘ μ°Ύμμ μ²λ¦¬ λμμΌλ‘ λ±λ‘ν©λλ€.
- νμ΄μ§λ³ μ²λ¦¬
- λμ λΌμ°νΈ μ²λ¦¬: generateStaticParams() ν¨μκ° νΈμΆλμ΄, λμ URL(slug λ±)μ νμν λͺ¨λ μ‘°ν©μ μμ±ν©λλ€.
- λ°μ΄ν° κ°μ Έμ€κΈ°: κ° νμ΄μ§μ λ°μ΄ν° νμΉ ν¨μ(API νΈμΆ λ±)λ₯Ό μ€νν΄μ νμν λ°μ΄ν°λ₯Ό λΆλ¬μ΅λλ€.
- React μ»΄ν¬λνΈ λ λλ§: κ°μ Έμ¨ λ°μ΄ν°λ₯Ό μ΄μ©ν΄ React μ»΄ν¬λνΈλ₯Ό HTMLλ‘ λ λλ§ν©λλ€.
- HTML νμΌ μμ±: λ λλ§λ κ²°κ³Όλ₯Ό μ μ HTML νμΌλ‘ λ³νν©λλ€.
- μΆλ ₯λ¬Ό μμ±
- μμ±λ HTML νμΌκ³Ό JavaScript λ²λ€μ΄ νλ‘μ νΈ λ΄λΆ .next ν΄λμ μ μ₯λ©λλ€.
- μμ:
- .next/server/app/page.html → ν νμ΄μ§
- .next/server/app/about.html → μκ° νμ΄μ§
- .next/server/app/blog/post-1.html → λΈλ‘κ·Έ κΈ
- .next/static/chunks/ → React κΈ°λ₯κ³Ό μΈν°λν°λΈλ₯Ό μν JS λ²λ€
Next.js μ½λ μμ
// app/blog/[slug]/page.tsx
// 1. λΉλ μ μμ±ν κ²½λ‘ μ μ
export async function generateStaticParams() {
console.log('ποΈ λΉλ νμ: κ²½λ‘ μμ± μ€...');
// APIμμ λͺ¨λ ν¬μ€νΈ κ°μ Έμ€κΈ°
const posts = await fetch('<https://api.example.com/posts>')
.then(res => res.json());
// κ° ν¬μ€νΈλ§λ€ μ μ νμ΄μ§ μμ±
return posts.map((post: any) => ({
slug: post.slug, // /blog/post-1, /blog/post-2 λ±
}));
}
// 2. κ° νμ΄μ§μ λ°μ΄ν° νμΉ
export default async function BlogPost({
params
}: {
params: Promise<{ slug: string }>
}) {
console.log(`ποΈ λΉλ νμ: ${params.slug} νμ΄μ§ μμ± μ€...`);
const { slug } = await params;
// μ΄ fetchλ λΉλ μ ν λ²λ§ μ€νλ¨
const post = await fetch(`https://api.example.com/posts/${params.slug}`, {
cache: 'force-cache' // SSGμ ν΅μ¬ μ€μ (DEFAULT)
}).then(res => res.json());
// React μ»΄ν¬λνΈλ₯Ό HTMLλ‘ λ³ν
return (
<article>
<h1>{post.title}</h1>
<div dangerouslySetInnerHTML={{ __html: post.content }} />
</article>
);
2λ¨κ³: λΉλ κ²°κ³Όλ¬Ό
.next/
βββ static/
β βββ chunks/ # JavaScript μ½λ μ‘°κ°λ€
β β βββ main.js # React λ°νμ
β β βββ pages/ # νμ΄μ§λ³ JS
β βββ css/ # CSS νμΌλ€
βββ server/
β βββ app/
β βββ page.html # 미리 μμ±λ HTML
β βββ about.html
β βββ blog/
β βββ post-1.html # κ° ν¬μ€νΈμ HTML
β βββ post-2.html
μμ±λ HTML μμ
<!DOCTYPE html>
<html>
<head>
<title>My Blog Post</title>
<link rel="stylesheet" href="/_next/static/css/app.css" />
</head>
<body>
<!-- μ΄λ―Έ λ λλ§λ μ½ν
μΈ -->
<article>
<h1>λ΄ μ²« λ²μ§Έ ν¬μ€νΈ</h1>
<div>
<p>μ΄κ²μ λ΄μ©μ
λλ€...</p>
</div>
</article>
<!-- JavaScriptλ Hydrationμ μν΄ λ‘λ -->
<script src="/_next/static/chunks/main.js"></script>
<script src="/_next/static/chunks/pages/blog/post-1.js"></script>
</body>
</html>
3λ¨κ³: μ¬μ©μ μμ² (Request Time)
μ¬μ©μκ° /blog/post-1 μ μ
↓
CDN/Serverκ° λ―Έλ¦¬ μμ±λ HTML μ μ‘
↓
λΈλΌμ°μ κ° HTML μ¦μ λ λλ§ (0.1μ΄)
↓
JavaScript λ€μ΄λ‘λ & μ€ν
↓
Hydration (HTMLμ μ΄λ²€νΈ 리μ€λ μ°κ²°)
↓
μΈν°λν°λΈν νμ΄μ§ μμ±
Hydration κ³Όμ
// 1. μλ²μμ μμ±λ HTML (μ μ )
<button>ν΄λ¦νμΈμ</button>
// 2. JavaScript λ‘λ ν Hydration
<button onClick={() => alert('ν΄λ¦!')}>ν΄λ¦νμΈμ</button>
// ↑ μ΄λ²€νΈ 리μ€λκ° μΆκ°λ¨
Hydration μ½λ νλ¦:
// Reactκ° λ΄λΆμ μΌλ‘ μν
ReactDOM.hydrateRoot(
document.getElementById('root'),
<App />
);
// κ³Όμ :
// 1. κΈ°μ‘΄ HTML μμ μ°ΎκΈ°
// 2. Virtual DOM μμ±
// 3. κΈ°μ‘΄ DOMκ³Ό λΉκ΅
// 4. μ΄λ²€νΈ 리μ€λ μ°κ²°
// 5. μν κ΄λ¦¬ νμ±ν
2. ISR (Incremental Static Regeneration) μλμ리
μ 체 νλ¦λ
[λΉλ νμ] [첫 μμ²] [revalidate ν]
λͺ¨λ νμ΄μ§ μμ± → μ μ HTML μ 곡 → λ°±κ·ΈλΌμ΄λ μ¬μμ±
↓ ↓
μΊμλ λ²μ μ 곡 μ λ²μ μΌλ‘ κ΅μ²΄
ISR μμΈ μλ κ³Όμ
μλ리μ€: revalidate: 60 μ€μ
// app/blog/[slug]/page.tsx
export default async function BlogPost({ params }: { params: { slug: string } }) {
const post = await fetch(`https://api.example.com/posts/${params.slug}`, {
next: {
revalidate: 60 // 60μ΄λ§λ€ μ¬κ²μ¦
}
}).then(res => res.json());
return <article>{post.title}</article>;
}
νμλΌμΈ λΆμ
μκ° μμ² μλ΅ λ΄λΆ λμ
ββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ
00:00 λΉλ μλ£ - HTML v1 μμ±
(μ΄κΈ° μν)
01:00 μ¬μ©μ A HTML v1 μ 곡 β
μΊμ ννΈ
λ°©λ¬Έ (0.1μ΄) (νμμ€ν¬ν: 00:00)
01:30 μ¬μ©μ B HTML v1 μ 곡 β
μΊμ ννΈ
λ°©λ¬Έ (0.1μ΄) (μμ§ 60μ΄ μλ¨)
02:00 μ¬μ©μ C HTML v1 μ 곡 π μ¬μμ± νΈλ¦¬κ±°!
λ°©λ¬Έ (0.1μ΄) (60μ΄ κ²½κ³Ό)
λ°±κ·ΈλΌμ΄λμμ:
- API μ¬νΈμΆ
- HTML v2 μμ±
- μΊμ κ΅μ²΄ (10μ΄ μμ)
02:05 μ¬μ©μ D HTML v1 μ 곡 β³ μ¬μμ± μ§ν μ€
λ°©λ¬Έ (0.1μ΄)
02:10 μ¬μμ± μλ£ - β
μΊμ μ
λ°μ΄νΈ
HTML v2 μ€λΉ μλ£
02:15 μ¬μ©μ E HTML v2 μ 곡 β
μ λ²μ !
λ°©λ¬Έ (0.1μ΄)
03:00 μ¬μ©μ F HTML v2 μ 곡 β
μΊμ ννΈ
λ°©λ¬Έ (0.1μ΄)
ISR λ΄λΆ μΊμ λ©μ»€λμ¦
// Next.js λ΄λΆ μΊμ ꡬ쑰 (λ¨μν)
const cache = {
'/blog/post-1': {
html: '<article>...</article>',
timestamp: 1704067200000, // μμ± μκ°
revalidateAfter: 60, // μ¬κ²μ¦ κ°κ²© (μ΄)
isRevalidating: false // μ¬μμ± μ€μΈμ§
}
};
// μμ² μ²λ¦¬ λ‘μ§
async function handleRequest(path) {
const cached = cache[path];
const now = Date.now();
const age = (now - cached.timestamp) / 1000; // μ΄ λ¨μ
// 1. μΊμκ° μ ν¨ν κ²½μ°
if (age < cached.revalidateAfter) {
console.log('β
μΊμ ννΈ: μ μ ν λ°μ΄ν°');
return cached.html;
}
// 2. μΊμκ° λ§λ£λμμ§λ§ μ¬μμ± μ€μ΄ μλ κ²½μ°
if (!cached.isRevalidating) {
console.log('π Stale-While-Revalidate: μ€λλ μΊμ μ 곡 + λ°±κ·ΈλΌμ΄λ μ¬μμ±');
// μ€λλ μΊμ μ¦μ λ°ν
const staleResponse = cached.html;
// λ°±κ·ΈλΌμ΄λμμ μ¬μμ±
cached.isRevalidating = true;
regeneratePage(path).then(newHtml => {
cache[path] = {
html: newHtml,
timestamp: Date.now(),
revalidateAfter: 60,
isRevalidating: false
};
console.log('β
μ¬μμ± μλ£: μΊμ μ
λ°μ΄νΈλ¨');
});
return staleResponse;
}
// 3. μ΄λ―Έ μ¬μμ± μ€μΈ κ²½μ°
console.log('β³ μ¬μμ± μ§ν μ€: μ€λλ μΊμ μ 곡');
return cached.html;
}
ISRμ Stale-While-Revalidate μ λ΅
ISRμ ν΅μ¬μ "μ€λλ μ½ν μΈ λ₯Ό μ 곡νλ©΄μ λμμ μ μ½ν μΈ λ₯Ό μ€λΉ"νλ κ²μ λλ€.
μμ² → μΊμ νμΈ
ββ μ μ ν¨ → μ¦μ λ°ν β
ββ μ€λλ¨ → μ€λλ κ² λ°ν + λ°±κ·ΈλΌμ΄λ κ°±μ π
(λ€μ μμ²λΆν° μ λ²μ )
On-Demand Revalidation
μλμΌλ‘ μΊμλ₯Ό 무ν¨νν μλ μμ΅λλ€.
// app/api/revalidate/route.ts
import { revalidatePath } from 'next/cache';
import { NextRequest } from 'next/server';
export async function POST(request: NextRequest) {
const path = request.nextUrl.searchParams.get('path');
if (path) {
// νΉμ κ²½λ‘ μ¬κ²μ¦
revalidatePath(path);
console.log(`π μλ μ¬κ²μ¦: ${path}`);
return Response.json({ revalidated: true });
}
return Response.json({ revalidated: false });
}
// μ¬μ© μ:
// POST /api/revalidate?path=/blog/post-1
ISR λ΄λΆ λμ:
// revalidatePath λ΄λΆ λ‘μ§
function revalidatePath(path) {
// 1. μΊμμμ ν΄λΉ κ²½λ‘ μ°ΎκΈ°
const cached = cache[path];
if (cached) {
// 2. νμμ€ν¬νλ₯Ό κ³Όκ±°λ‘ μ€μ (λ§λ£μν΄)
cached.timestamp = 0;
console.log(`β
${path} μΊμ 무ν¨νλ¨`);
// 3. λ€μ μμ² μ μλμΌλ‘ μ¬μμ±λ¨
}
}
3. SSR (Server-Side Rendering) μλμ리
μ 체 νλ¦λ
μ¬μ©μ μμ²
↓
Next.js μλ²
↓
λ°μ΄ν° νμΉ (API νΈμΆ)
↓
React μ»΄ν¬λνΈ λ λλ§
↓
HTML μμ±
↓
λΈλΌμ°μ λ‘ μ μ‘
↓
Hydration
μμΈ μλ κ³Όμ
μμ²λ³ μ²λ¦¬ νλ¦
// app/dashboard/page.tsx
export default async function Dashboard() {
console.log('π₯οΈ μλ²μμ μ€ν μ€...');
const data = await fetch('<https://api.example.com/user>', {
cache: 'no-store', // SSR νμ±ν
headers: {
cookie: cookies().toString() // μΏ ν€ ν¬ν¨
}
}).then(res => res.json());
return (
<div>
<h1>νμν©λλ€, {data.name}λ</h1>
<p>λ§μ§λ§ λ‘κ·ΈμΈ: {data.lastLogin}</p>
</div>
);
}
SSR νμλΌμΈ λΆμ
μ¬μ©μ Aκ° /dashboard μμ² (10:00:00)
β
ββ [10:00:00.000] μμ² λμ°©
β ββ μλ²: "μ μμ² λ°μ"
β
ββ [10:00:00.050] μΏ ν€/ν€λ νμ±
β ββ μλ²: "μΈμ¦ ν ν° νμΈ"
β
ββ [10:00:00.100] API νΈμΆ μμ
β ββ μλ² → API: "μ¬μ©μ λ°μ΄ν° μμ²"
β
ββ [10:00:00.500] API μλ΅ λκΈ° μ€...
β ββ μλ²: "μλ΅ λκΈ° μ€"
β
ββ [10:00:00.800] API μλ΅ λμ°©
β ββ μλ²: "λ°μ΄ν° μμ μλ£"
β
ββ [10:00:00.850] React λ λλ§
β ββ μλ²: "μ»΄ν¬λνΈλ₯Ό HTMLλ‘ λ³ν"
β
ββ [10:00:00.900] HTML μμ± μλ£
β ββ μλ²: "μ΅μ’
HTML μ€λΉ"
β
ββ [10:00:00.950] λΈλΌμ°μ λ‘ μ μ‘
ββ μ¬μ©μ: "νμ΄μ§ νμ"
ββββββββββββββββββββββββββββββββββββββββββ
μ¬μ©μ Bκ° /dashboard μμ² (10:00:01)
β
ββ μ κ³Όμ λ°λ³΅ (μΊμ μμ!)
ββ μ΄ μμμκ°: ~1μ΄
μλ² λ΄λΆ μ²λ¦¬
// Next.js μλ²μ μμ² νΈλ€λ¬ (λ¨μν)
async function handleSSRRequest(req, res) {
console.log('π₯οΈ SSR μμ² μ²λ¦¬ μμ');
const startTime = Date.now();
try {
// 1. λΌμ°νΈ λ§€μΉ
const route = matchRoute(req.url);
console.log(`π λΌμ°νΈ: ${route.path}`);
// 2. νμ΄μ§ μ»΄ν¬λνΈ κ°μ Έμ€κΈ°
const PageComponent = await import(route.componentPath);
// 3. μλ² μ»΄ν¬λνΈ μ€ν (λ°μ΄ν° νμΉ ν¬ν¨)
console.log('π λ°μ΄ν° νμΉ μμ...');
const props = await PageComponent.getServerSideProps?.(req, res);
// 4. React λ λλ§
console.log('π¨ React λ λλ§ μμ...');
const html = ReactDOMServer.renderToString(
<PageComponent {...props} />
);
// 5. HTML ν
νλ¦Ώμ μ½μ
const fullHtml = `
<!DOCTYPE html>
<html>
<head>
<title>${props.title}</title>
<link rel="stylesheet" href="/styles.css" />
</head>
<body>
<div id="root">${html}</div>
<script src="/runtime.js"></script>
<script>
window.__INITIAL_DATA__ = ${JSON.stringify(props)};
</script>
</body>
</html>
`;
const endTime = Date.now();
console.log(`β
λ λλ§ μλ£ (${endTime - startTime}ms)`);
// 6. μλ΅ μ μ‘
res.setHeader('Content-Type', 'text/html');
res.send(fullHtml);
} catch (error) {
console.error('β SSR μλ¬:', error);
res.status(500).send('Server Error');
}
}
SSR μΈμ¦/μΏ ν€ μ²λ¦¬
// app/profile/page.tsx
import { cookies, headers } from 'next/headers';
export default async function Profile() {
// 1. μΏ ν€ μ½κΈ°
const cookieStore = cookies();
const token = cookieStore.get('auth_token');
console.log('π μΈμ¦ ν ν°:', token?.value);
if (!token) {
redirect('/login');
}
// 2. ν€λ μ½κΈ°
const headersList = headers();
const userAgent = headersList.get('user-agent');
console.log('π€ User-Agent:', userAgent);
// 3. μΈμ¦λ API νΈμΆ
const user = await fetch('<https://api.example.com/profile>', {
headers: {
'Authorization': `Bearer ${token.value}`,
'User-Agent': userAgent || ''
},
cache: 'no-store'
}).then(res => res.json());
return
μ±λ₯ μ΅μ ν: Streaming SSR
// app/dashboard/page.tsx
import { Suspense } from 'react';
export default function Dashboard() {
return (
<div>
<h1>λμ보λ</h1>
{/* μ¦μ λ λλ§ */}
<QuickStats />
{/* λλ¦° λΆλΆμ λμ€μ */}
<Suspense fallback={<Loading />}>
<SlowComponent />
</Suspense>
</div>
);
}
async function SlowComponent() {
// 3μ΄ κ±Έλ¦¬λ API νΈμΆ
const data = await fetch('https://slow-api.com/data', {
cache: 'no-store'
});
return <HeavyChart data={data} />;
}
Streaming λμ:
ν΄λΌμ΄μΈνΈλ‘ μ μ‘λλ HTML:
[0.1μ΄]
<div>
<h1>λμ보λ</h1>
<div>λΉ λ₯Έ ν΅κ³: ...</div>
<div id="suspense-boundary">λ‘λ© μ€...</div>
</div>
[3.1μ΄ - μΆκ° μ²ν¬]
<script>
// Suspense κ²½κ³ μ
λ°μ΄νΈ
replaceSuspenseBoundary('suspense-boundary', `
<div>λλ¦° μ»΄ν¬λνΈ λ΄μ©...</div>
`);
</script>
4. CSR (Client-Side Rendering) μλμ리
μ 체 νλ¦λ
μ¬μ©μ μμ²
↓
λΉ HTML + JavaScript λ²λ€ μ μ‘
↓
λΈλΌμ°μ μμ JavaScript μ€ν
↓
React μ± μ΄κΈ°ν
↓
API νΈμΆ (fetch/axios)
↓
μ»΄ν¬λνΈ λ λλ§
↓
νλ©΄μ νμ
μμΈ μλ κ³Όμ
μ΄κΈ° HTML (κ±°μ λΉμ΄μμ)
<!DOCTYPE html>
<html>
<head>
<title>My App</title>
</head>
<body>
<!-- κ±°μ λΉ κ»λ°κΈ° -->
<div id="root"></div>
<!-- JavaScript λ²λ€λ§ λ‘λ -->
<script src="/static/js/runtime.js"></script>
<script src="/static/js/vendors.js"></script>
<script src="/static/js/main.js"></script>
</body>
</html>
JavaScript μ€ν νλ¦
// app/dashboard/page.tsx
'use client'; // CSR λͺ
μ
import { useState, useEffect } from 'react';
export default function Dashboard() {
const [user, setUser] = useState(null);
const [loading, setLoading] = useState(true);
useEffect(() => {
console.log('π λΈλΌμ°μ μμ μ€ν μ€...');
console.log('π‘ API νΈμΆ μμ...');
fetch('/api/user')
.then(res => {
console.log('β
API μλ΅ λ°μ');
return res.json();
})
.then(data => {
console.log('π¦ λ°μ΄ν° νμ± μλ£');
setUser(data);
setLoading(false);
})
.catch(error => {
console.error('β μλ¬:', error);
setLoading(false);
});
}, []);
if (loading) {
return <div>λ‘λ© μ€...</div>;
}
return (
<div>
<h1>νμν©λλ€, {user.name}λ!</h1>
</div>
);
}
CSR νμλΌμΈ λΆμ
[0.0μ΄] μ¬μ©μκ° /dashboard μμ²
β
ββ [0.1μ΄] HTML λ€μ΄λ‘λ (5KB)
β ββ νλ©΄: λΉ νμ΄μ§
β
ββ [0.2μ΄] JavaScript λ€μ΄λ‘λ μμ
β ββ runtime.js (50KB)
β ββ vendors.js (200KB - React λ±)
β ββ main.js (100KB - μ± μ½λ)
β ββ νλ©΄: μ¬μ ν λΉ νμ΄μ§
β
ββ [1.0μ΄] JavaScript νμ± & μ€ν
β ββ νλ©΄: μ¬μ ν λΉ νμ΄μ§
β
ββ [1.2μ΄] React μ± μ΄κΈ°ν
β ββ ReactDOM.render(<App />)
β ββ νλ©΄: "λ‘λ© μ€..." νμ
β
ββ [1.3μ΄] useEffect μ€ν
β ββ fetch('/api/user') νΈμΆ
β ββ νλ©΄: "λ‘λ© μ€..."
β
ββ [1.8μ΄] API μλ΅ λμ°©
β ββ νλ©΄: "λ‘λ© μ€..."
β
ββ [2.0μ΄] setState μλ£ & 리λ λλ§
ββ νλ©΄: μ€μ μ½ν
μΈ νμ β
CSR λΈλΌμ°μ λ΄λΆ μ²λ¦¬
// Reactμ CSR μ΄κΈ°ν (λ¨μν)
window.addEventListener('DOMContentLoaded', () => {
console.log('π DOM λ‘λ μλ£');
// 1. React λ£¨νΈ μμ±
const root = ReactDOM.createRoot(document.getElementById('root'));
console.log('π± React λ£¨νΈ μμ±');
// 2. μ± λ λλ§
console.log('π¨ μ± λ λλ§ μμ');
root.render(<App />);
// 3. μ»΄ν¬λνΈ λ§μ΄νΈ
// App → Dashboard → useEffect μμλ‘ μ€ν
});
CSR μν κ΄λ¦¬μ 리λ λλ§
'use client';
import { useState } from 'react';
export default function Counter() {
const [count, setCount] = useState(0);
console.log('π Counter μ»΄ν¬λνΈ λ λλ§:', count);
const increment = () => {
console.log('β μ¦κ° λ²νΌ ν΄λ¦');
setCount(prev => {
const newCount = prev + 1;
console.log(`μν λ³κ²½: ${prev} → ${newCount}`);
return newCount;
});
// μν λ³κ²½ ν μλμΌλ‘ 리λ λλ§ νΈλ¦¬κ±°
};
return (
<div>
<p>μΉ΄μ΄νΈ: {count}</p>
<button onClick={increment}>μ¦κ°</button>
</div>
);
}
μ€ν νλ¦:
[μ΄κΈ° λ λλ§]
ββ π Counter μ»΄ν¬λνΈ λ λλ§: 0
[λ²νΌ ν΄λ¦]
ββ β μ¦κ° λ²νΌ ν΄λ¦
ββ μν λ³κ²½: 0 → 1
ββ π Counter μ»΄ν¬λνΈ λ λλ§: 1
ββ Virtual DOM μμ±
ββ μ€μ DOMκ³Ό λΉκ΅ (Reconciliation)
ββ λ³κ²½λ λΆλΆλ§ μ
λ°μ΄νΈ
ββ <p>μΉ΄μ΄νΈ: 1</p> β
Reactμ Reconciliation (μ¬μ‘°μ )
// React λ΄λΆ λ‘μ§ (λ¨μν)
function updateComponent(Component, newProps) {
// 1. μλ‘μ΄ Virtual DOM μμ±
const newVDOM = Component(newProps);
console.log('π μ Virtual DOM:', newVDOM);
// 2. μ΄μ Virtual DOMκ³Ό λΉκ΅
const diff = compareVDOM(oldVDOM, newVDOM);
console.log('π λ³κ²½ μ¬ν:', diff);
// 3. λ³κ²½λ λΆλΆλ§ μ€μ DOM μ
λ°μ΄νΈ
diff.forEach(change => {
if (change.type === 'UPDATE_TEXT') {
const element = document.querySelector(change.selector);
element.textContent = change.newValue;
console.log(`βοΈ ν
μ€νΈ μ
λ°μ΄νΈ: ${change.oldValue} → ${change.newValue}`);
}
});
// 4. μ΄μ VDOMμ μ VDOMμΌλ‘ κ΅μ²΄
oldVDOM = newVDOM;
}
'Developingπ©βπ» > Front-end' μΉ΄ν κ³ λ¦¬μ λ€λ₯Έ κΈ
| [μλ¬Έ λ²μ] μλ² μνμμ ν΄λΌμ΄μΈνΈ μν λμ΄μ€κΈ° - tkdodo (0) | 2025.10.07 |
|---|---|
| [μλ¬Έ λ²μ] useCallbackμ μΈλͺ¨μμ - tkdodo (0) | 2025.10.07 |
| [Next.js] nuqsμ useQueryState (0) | 2025.10.06 |
| [FE] λΉλκΈ° λμ λμμ±μ ꡬννλ Web Worker, WebAssembly μμ보기(λ©ν°μ€λ λ) (0) | 2025.08.24 |
| [FE] Webpack, Rollup.js, κ·Έλ¦¬κ³ Vite (0) | 2025.08.22 |