π νμ΅ λͺ©ν
- Next.js νμΌ κΈ°λ° λΌμ°ν μμ€ν λ§μ€ν°
- React λΆλ³μ±κ³Ό μν μ λ°μ΄νΈ μμ μ΄ν΄
- λμ λΌμ°ν μ€μ΅μΌλ‘ μ€λ¬΄ λ₯λ ₯ ν₯μ
// App.js
import { BrowserRouter, Routes, Route } from 'react-router-dom'
function App() {
return (
<BrowserRouter>
<Routes>
<Route path="/" element={<Home />} />
<Route path="/about" element={<About />} />
<Route path="/blog" element={<Blog />} />
<Route path="/blog/:slug" element={<BlogPost />} />
<Route path="/products/:category" element={<Products />} />
<Route path="/user/:id/profile" element={<UserProfile />} />
</Routes>
</BrowserRouter>
)
}
λ¬Έμ μ :
app/ URL
βββ page.js β '/'
βββ about/
β βββ page.js β '/about'
βββ blog/
β βββ page.js β '/blog'
β βββ [slug]/
β βββ page.js β '/blog/[slug]'
βββ products/
β βββ [category]/
β βββ page.js β '/products/[category]'
βββ user/
βββ [id]/
βββ profile/
βββ page.js β '/user/[id]/profile'
μ₯μ :
app/
βββ layout.js π λ£¨νΈ λ μ΄μμ (νμ)
βββ page.js π ννμ΄μ§
βββ loading.js β³ λ‘λ© UI
βββ error.js β μλ¬ UI
βββ not-found.js π 404 νμ΄μ§
βββ global-error.js π₯ μ μ μλ¬
βββ favicon.ico π― νλΉμ½
// app/page.js - ννμ΄μ§ ('/' κ²½λ‘)
export default function HomePage() {
return (
<div>
<h1>ννμ΄μ§μ μ€μ κ²μ νμν©λλ€!</h1>
<p>Next.js App Routerλ₯Ό μ¬μ©ν ννμ΄μ§μ
λλ€.</p>
</div>
)
}
// app/about/page.js - μκ° νμ΄μ§ ('/about' κ²½λ‘)
export default function AboutPage() {
return (
<div>
<h1>νμ¬ μκ°</h1>
<p>μ°λ¦¬λ νμ μ μΈ κΈ°μ λ‘ μΈμμ λ°κΏλκ°κ³ μμ΅λλ€.</p>
</div>
)
}
// app/layout.js - λ£¨νΈ λ μ΄μμ (λͺ¨λ νμ΄μ§μ μ μ©)
export const metadata = {
title: 'μ°λ¦¬ νμ¬',
description: 'νμ μ μΈ κΈ°μ νμ¬'
}
export default function RootLayout({ children }) {
return (
<html lang="ko">
<body>
<header>
<nav>
<a href="/">ν</a>
<a href="/about">μκ°</a>
<a href="/blog">λΈλ‘κ·Έ</a>
</nav>
</header>
<main>{children}</main>
<footer>
<p>© 2024 μ°λ¦¬ νμ¬</p>
</footer>
</body>
</html>
)
}
// app/blog/layout.js - λΈλ‘κ·Έ μΉμ
λ μ΄μμ
export default function BlogLayout({ children }) {
return (
<div className="blog-container">
<aside>
<h3>μΉ΄ν
κ³ λ¦¬</h3>
<ul>
<li><a href="/blog/tech">κΈ°μ </a></li>
<li><a href="/blog/business">λΉμ¦λμ€</a></li>
</ul>
</aside>
<div className="blog-content">
{children}
</div>
</div>
)
}
// app/loading.js - μ μ λ‘λ©
export default function Loading() {
return (
<div className="loading-container">
<div className="spinner"></div>
<p>λ‘λ© μ€...</p>
</div>
)
}
// app/blog/loading.js - λΈλ‘κ·Έ μΉμ
λ‘λ©
export default function BlogLoading() {
return (
<div>
<div className="skeleton-title"></div>
<div className="skeleton-content"></div>
</div>
)
}
// app/error.js - μ μ μλ¬
'use client' // μλ¬ μ»΄ν¬λνΈλ ν΄λΌμ΄μΈνΈ μ»΄ν¬λνΈμ¬μΌ ν¨
export default function Error({ error, reset }) {
return (
<div>
<h2>λ¬Έμ κ° λ°μνμ΅λλ€!</h2>
<p>{error.message}</p>
<button onClick={() => reset()}>λ€μ μλ</button>
</div>
)
}
app/
βββ layout.js (λ£¨νΈ λ μ΄μμ)
βββ page.js (ν)
βββ dashboard/
β βββ layout.js (λμ보λ λ μ΄μμ)
β βββ page.js (λμ보λ ν)
β βββ analytics/
β β βββ layout.js (λΆμ μΉμ
λ μ΄μμ)
β β βββ page.js (λΆμ νμ΄μ§)
β βββ settings/
β βββ page.js (μ€μ νμ΄μ§)
λ μ΄μμ μμ ꡬ쑰:
/dashboard/analytics νμ΄μ§ μ μμ:
βββββββββββββββββββββββββββββββββββββββ
β λ£¨νΈ λ μ΄μμ (app/layout.js) β
β βββββββββββββββββββββββββββββββββββ β
β β λμ보λ λ μ΄μμ (dashboard/) β β
β β βββββββββββββββββββββββββββββββ β β
β β β λΆμ λ μ΄μμ (analytics/) β β β
β β β βββββββββββββββββββββββββββ β β β
β β β β λΆμ νμ΄μ§ λ΄μ© β β β β
β β β βββββββββββββββββββββββββββ β β β
β β βββββββββββββββββββββββββββββββ β β
β βββββββββββββββββββββββββββββββββββ β
βββββββββββββββββββββββββββββββββββββββ
λΆλ³μ±(Immutability) = μλ³Έμ 건λλ¦¬μ§ λ§κ³ μλ‘μ΄ κ²μ λ§λ€μ΄!
β κ°λ³μ (Mutable):
μλ³Έ λ¬Έμμ μ§μ μμ β μλ³Έμ΄ λ³κ²½λ¨
β
λΆλ³μ (Immutable):
μλ³Έ λ¬Έμλ κ·Έλλ‘ λκ³ β 볡μ¬λ³Έμ μμ β μ λ¬Έμ μμ±
let a = 5
let b = a // κ° λ³΅μ¬
b = 10 // aλ μ¬μ ν 5
console.log(a) // 5
console.log(b) // 10
// β μλͺ»λ λ°©λ² - μλ³Έ μμ
let user = { name: 'κΉκ°λ°', age: 25 }
let updatedUser = user // μ°Έμ‘° 볡μ¬!
updatedUser.name = 'μ΄κ°λ°'
console.log(user.name) // 'μ΄κ°λ°' (μλ³Έμ΄ λ°λ!)
console.log(updatedUser.name) // 'μ΄κ°λ°'
// β
μ¬λ°λ₯Έ λ°©λ² - μ κ°μ²΄ μμ±
let user = { name: 'κΉκ°λ°', age: 25 }
let updatedUser = { ...user, name: 'μ΄κ°λ°' } // μ€νλ λλ‘ λ³΅μ¬
console.log(user.name) // 'κΉκ°λ°' (μλ³Έ μ μ§!)
console.log(updatedUser.name) // 'μ΄κ°λ°'
// React λ΄λΆ λμ (λ¨μν)
function useState(initialValue) {
let state = initialValue
function setState(newValue) {
// π₯ μμ λΉκ΅ (μ°Έμ‘° λΉκ΅)
if (state === newValue) {
return // κ°μΌλ©΄ 리λ λλ§ μν¨!
}
state = newValue
reRender() // λ€λ₯΄λ©΄ 리λ λλ§
}
return [state, setState]
}
// β λ¬Έμ κ° μλ μ½λ
function UserProfile() {
const [user, setUser] = useState({
name: 'κΉκ°λ°',
hobbies: ['μ½λ©', 'λ
μ'],
address: { city: 'μμΈ', district: 'κ°λ¨κ΅¬' }
})
const addHobby = (newHobby) => {
// β μλ³Έ λ°°μ΄ μ§μ μμ !
user.hobbies.push(newHobby)
setUser(user) // React: "μ΄? κ°μ κ°μ²΄λ€? 리λ λλ§ μν΄!"
}
const updateCity = (newCity) => {
// β μλ³Έ κ°μ²΄ μ§μ μμ !
user.address.city = newCity
setUser(user) // React: "μ΄? κ°μ κ°μ²΄λ€? 리λ λλ§ μν΄!"
}
return (
<div>
<h2>{user.name}</h2>
<p>μ·¨λ―Έ: {user.hobbies.join(', ')}</p> {/* μ
λ°μ΄νΈ μλ¨! */}
<p>λμ: {user.address.city}</p> {/* μ
λ°μ΄νΈ μλ¨! */}
</div>
)
}
// β
μ¬λ°λ₯Έ μ½λ
function UserProfile() {
const [user, setUser] = useState({
name: 'κΉκ°λ°',
hobbies: ['μ½λ©', 'λ
μ'],
address: { city: 'μμΈ', district: 'κ°λ¨κ΅¬' }
})
const addHobby = (newHobby) => {
// β
μ λ°°μ΄ μμ±
setUser({
...user, // κΈ°μ‘΄ κ°μ²΄ 볡μ¬
hobbies: [...user.hobbies, newHobby] // μ λ°°μ΄ μμ±
})
}
const removeHobby = (hobbyToRemove) => {
// β
νν°λ‘ μ λ°°μ΄ μμ±
setUser({
...user,
hobbies: user.hobbies.filter(hobby => hobby !== hobbyToRemove)
})
}
const updateHobby = (index, newHobby) => {
// β
mapμΌλ‘ μ λ°°μ΄ μμ±
setUser({
...user,
hobbies: user.hobbies.map((hobby, i) =>
i === index ? newHobby : hobby
)
})
}
const updateCity = (newCity) => {
// β
μ€μ²© κ°μ²΄λ μλ‘ μμ±
setUser({
...user, // μ΅μμ 볡μ¬
address: {
...user.address, // address κ°μ²΄ 볡μ¬
city: newCity // cityλ§ μ
λ°μ΄νΈ
}
})
}
return (
<div>
<h2>{user.name}</h2>
<div>
<h3>μ·¨λ―Έ λͺ©λ‘:</h3>
{user.hobbies.map((hobby, index) => (
<div key={index}>
{hobby}
<button onClick={() => removeHobby(hobby)}>μμ </button>
<button onClick={() => updateHobby(index, hobby + '!')}>μμ </button>
</div>
))}
<button onClick={() => addHobby('μνκ°μ')}>μ·¨λ―Έ μΆκ°</button>
</div>
<div>
<p>μ£Όμ: {user.address.city}, {user.address.district}</p>
<button onClick={() => updateCity('λΆμ°')}>λμ λ³κ²½</button>
</div>
</div>
)
}
URLμ μΌλΆλ₯Ό λ³μλ‘ μ¬μ©νλ λΌμ°ν
μ μ λΌμ°ν
: /blog/react-tutorial
λμ λΌμ°ν
: /blog/[slug] β /blog/μ΄λ€κ°μ΄λ
app/
βββ blog/
β βββ page.js β '/blog'
β βββ [slug]/
β βββ page.js β '/blog/react-tutorial'
β β '/blog/javascript-guide'
β β '/blog/next-js-tips'
// app/blog/[slug]/page.js
export default function BlogPost({ params }) {
const { slug } = params // URLμμ slug μΆμΆ
return (
<div>
<h1>λΈλ‘κ·Έ ν¬μ€νΈ</h1>
<p>νμ¬ μ¬λ¬κ·Έ: {slug}</p>
</div>
)
}
// λ°©λ¬Έ μμ:
// /blog/react-tutorial β params = { slug: 'react-tutorial' }
// /blog/javascript-guide β params = { slug: 'javascript-guide' }
app/
βββ user/
β βββ [id]/
β βββ page.js β '/user/123'
β βββ profile/
β β βββ page.js β '/user/123/profile'
β βββ settings/
β βββ page.js β '/user/123/settings'
// app/user/[id]/page.js
export default function UserPage({ params }) {
const { id } = params
return (
<div>
<h1>μ¬μ©μ λμ보λ</h1>
<p>μ¬μ©μ ID: {id}</p>
</div>
)
}
// app/user/[id]/profile/page.js
export default function UserProfile({ params }) {
const { id } = params
return (
<div>
<h1>μ¬μ©μ νλ‘ν</h1>
<p>μ¬μ©μ ID: {id}μ νλ‘ν νμ΄μ§μ
λλ€.</p>
</div>
)
}
app/
βββ shop/
β βββ [category]/
β βββ [productId]/
β βββ page.js β '/shop/electronics/laptop-123'
β β '/shop/clothing/shirt-456'
// app/shop/[category]/[productId]/page.js
export default function ProductPage({ params }) {
const { category, productId } = params
return (
<div>
<h1>μν μμΈ</h1>
<p>μΉ΄ν
κ³ λ¦¬: {category}</p>
<p>μν ID: {productId}</p>
</div>
)
}
// λ°©λ¬Έ μμ:
// /shop/electronics/laptop-123 β { category: 'electronics', productId: 'laptop-123' }
app/
βββ docs/
β βββ [...slug]/
β βββ page.js β '/docs/a'
β β '/docs/a/b'
β β '/docs/a/b/c'
// app/docs/[...slug]/page.js
export default function DocsPage({ params }) {
const { slug } = params // λ°°μ΄λ‘ λ€μ΄μ΄!
return (
<div>
<h1>λ¬Έμ νμ΄μ§</h1>
<p>κ²½λ‘: {slug.join(' / ')}</p>
<ul>
{slug.map((segment, index) => (
<li key={index}>Level {index + 1}: {segment}</li>
))}
</ul>
</div>
)
}
// λ°©λ¬Έ μμ:
// /docs/getting-started β slug = ['getting-started']
// /docs/api/users/create β slug = ['api', 'users', 'create']
// νκΈ μ λͺ©μ URL μΉνμ μΌλ‘ λ³ν
function createSlug(title) {
return title
.toLowerCase()
.replace(/[^a-z0-9κ°-ν£]/g, '-') // νΉμλ¬Έμλ₯Ό λμλ‘
.replace(/-+/g, '-') // μ°μ λμ μ κ±°
.replace(/^-|-$/g, '') // μλ€ λμ μ κ±°
}
// μμ:
// "Next.js μμ μ 볡!" β "next-js-μμ -μ 볡"
// TypeScript μ¬μ©μ
interface PageProps {
params: { slug: string }
searchParams: { [key: string]: string | string[] | undefined }
}
export default function BlogPost({ params, searchParams }: PageProps) {
// νλΌλ―Έν° κ²μ¦
if (!params.slug || typeof params.slug !== 'string') {
notFound() // Next.js 404 νμ΄μ§λ‘ 리λ€μ΄λ νΈ
}
return <div>μμ ν νλΌλ―Έν°: {params.slug}</div>
}
// app/blog/[slug]/page.js
export async function generateMetadata({ params }) {
const post = await getPost(params.slug)
return {
title: post.title,
description: post.excerpt,
openGraph: {
title: post.title,
description: post.excerpt,
images: [post.thumbnail]
}
}
}
π₯ μ€λ¬΄μμ μ λ§ λ§μ΄ μ°λ κ²λ€ (μ° κ²½νλ΄)
π μ€λ¬΄ νμ©λ λνΉ
κ±°μ λ§€μΌ μ°λ νμ κΈ°μ !
jsx// π₯ μ€λ¬΄μμ λ§€μΌ λ³΄λ ν¨ν΄λ€
const [user, setUser] = useState({...})
// νΌ μ
λ°μ΄νΈ
setUser({...user, name: newName})
// λ°°μ΄μ μμ΄ν
μΆκ°
setItems([...items, newItem])
// λ°°μ΄μμ μμ
setItems(items.filter(item => item.id !== deleteId))
μ μ€μνκ°?
λͺ¨λ React νλ‘μ νΈμμ μν κ΄λ¦¬ν λ νμ
μ μλ©΄ λ²κ·Έκ° λμμμ΄ λ°μ
μ±λ₯ μ΅μ νμ κΈ°λ³Έ
μ¬λ§ν νλ‘μ νΈμλ λ€ λ€μ΄κ°
jsx// μ€μ νλ‘μ νΈ μμλ€
/blog/[slug] // λΈλ‘κ·Έ, λ΄μ€ μ¬μ΄νΈ
/user/[id]/profile // μ¬μ©μ μμ€ν
/product/[category]/[id] // μ΄μ»€λ¨Έμ€
/admin/[...slug] // κ΄λ¦¬μ νμ΄μ§
μ€λ¬΄ μ¬λ‘:
Next.js μ°λ©΄ μλμΌλ‘ μ¬μ©
μ€λ¬΄ μ₯μ :
μ νμ΄μ§ μΆκ° = νμΌλ§ μμ±
ν΄λ ꡬ쑰 = URL ꡬ쑰
μ½λ 리뷰ν λ ꡬ쑰 νμ
μ¬μ
π― μ€λ¬΄μμ μ΄λ κ² νμ©λΌμ
μ μμκ±°λ μ¬μ΄νΈ μμ
π νμΌ κ΅¬μ‘° (μ€μ νμ¬ κ΅¬μ‘°μ λΉμ·)
app/
βββ page.js // λ©μΈ νμ΄μ§
βββ products/
β βββ page.js // μν λͺ©λ‘
β βββ [category]/
β β βββ page.js // μΉ΄ν
κ³ λ¦¬λ³ μν
β β βββ [productId]/
β β βββ page.js // μν μμΈ
βββ user/
β βββ [userId]/
β β βββ page.js // λ§μ΄νμ΄μ§
β β βββ orders/
β β β βββ page.js // μ£Όλ¬Έ λ΄μ
β β βββ wishlist/
β β βββ page.js // μ° λͺ©λ‘
βββ admin/
βββ [...slug]/
βββ page.js // κ΄λ¦¬μ λͺ¨λ νμ΄μ§
π μΌνμΉ΄νΈ μν κ΄λ¦¬ (λΆλ³μ±)
jsx// μ€λ¬΄μμ λ§€μΌ μ°λ ν¨ν΄
function ShoppingCart() {
const [cart, setCart] = useState([])
// μν μΆκ°
const addItem = (product) => {
setCart(prev => {
const existing = prev.find(item => item.id === product.id)
if (existing) {
return prev.map(item =>
item.id === product.id
? { ...item, quantity: item.quantity + 1 }
: item
)
}
return [...prev, { ...product, quantity: 1 }]
})
}
// μλ λ³κ²½
const updateQuantity = (id, quantity) => {
setCart(prev =>
prev.map(item =>
item.id === id ? { ...item, quantity } : item
)
)
}
// μν μμ
const removeItem = (id) => {
setCart(prev => prev.filter(item => item.id !== id))
}
}
μ€ννΈμ
/μ€μΌμΌμ
νμ¬
jsx// π₯ λ§€μ° μμ£Ό μ¬μ©
jsx// β μ΄λ° λ²κ·Έ λλ¬Έμ λ°€μλ κ²½μ° λ§μ
const updateUser = () => {
user.name = newName // νλ©΄μ΄ μ λ°λ!
setUser(user)
}
// β
μ΄λ κ² ν΄μΌ ν¨
const updateUser = () => {
setUser({...user, name: newName})
}
λμ λΌμ°ν
μ€μ
jsx// β μ΄λ° μμΌλ‘ νλμ½λ©
<Link href={`/product/electronics/123`}>
// β
λμ μΌλ‘ μ²λ¦¬
<Link href={`/product/${category}/${productId}`}>
μ’μ λ΅λ³:
"λΆλ³μ±μ μ§μΌμΌ ν©λλ€. κ°μ²΄λ λ°°μ΄μ κ²½μ°
μ€νλ λ μ°μ°μλ map, filter λ±μ μ¬μ©ν΄μ
μλ‘μ΄ μ°Έμ‘°λ₯Ό λ§λ€μ΄μΌ Reactκ° λ³νλ₯Ό κ°μ§νκ³
리λ λλ§ν μ μμ΅λλ€."
"Next.jsμ νμΌ κΈ°λ° λΌμ°ν
μ μ¬μ©ν΄μ
SEO μΉνμ μΈ λμ λΌμ°ν
ꡬ쑰λ₯Ό μ€κ³νκ³ ,
λΆλ³μ± μμΉμ μ§μΌμ μν κ΄λ¦¬ μ΅μ νλ₯Ό κ²½ννμ΅λλ€."
π‘ κ²°λ‘ : μ€λ λ°°μ΄ κ²μ μ€λ¬΄ κ°μΉ
π₯ λ§€μΌ μ°λ κ²λ€ (νμ)