- μ±λ₯ μ§ν
- μλ² μ»΄ν¬λνΈ μ΅μ ν
- λ²λ€ μ¬μ΄μ¦ μ΅μ ν
- λ°μ΄ν°λ² μ΄μ€ μ΅μ ν
- μΊμ± μ λ΅
| μ§ν | κ°μ μ | κ°μ ν | ν₯μλ₯ |
|---|---|---|---|
| νμ΄μ§ λ‘λ | 127.6μ΄ | 0.1μ΄ | 1,276λ°° |
| API μλ΅ | 500ms | 50ms | 10λ°° |
| λ²λ€ ν¬κΈ° | 2MB | 500KB | 75% κ°μ |
| LCP | 3.2μ΄ | 1.8μ΄ | 44% κ°μ |
| FID | 120ms | 45ms | 63% κ°μ |
κΈ°μ‘΄ λ°©μ (λλ¦Ό)
// β ν΄λΌμ΄μΈνΈ β API β DB (127.6μ΄)
'use client'
export default function PostList() {
const [posts, setPosts] = useState([])
useEffect(() => {
fetch('/api/posts')
.then(res => res.json())
.then(data => setPosts(data))
}, [])
return <div>{/* λ λλ§ */}</div>
}κ°μ λ λ°©μ (λΉ λ¦)
// β
μλ²μμ μ§μ DB μ κ·Ό (0.1μ΄)
export default async function PostList() {
const posts = await prisma.mainPost.findMany({
include: {
author: true,
category: true,
_count: { select: { comments: true, likes: true }}
}
})
return <div>{/* μ¦μ λ λλ§ */}</div>
}// 3κ° μΏΌλ¦¬ λμ μ€ν
export default async function DashboardPage() {
const [posts, users, stats] = await Promise.all([
prisma.mainPost.findMany({ take: 10 }),
prisma.user.findMany({ take: 5 }),
prisma.siteStats.findFirst()
])
// μμ°¨ μ€ν λλΉ 3λ°° λΉ λ¦
return <Dashboard data={{ posts, users, stats }} />
}1. λμ μν¬νΈ
// νμν λλ§ λ‘λ
const SearchModal = lazy(() => import('@/components/search/SearchModal'))
// μ¬μ©μκ° ν΄λ¦ν λ λ‘λ
{isSearchOpen && (
<Suspense fallback={<Loading />}>
<SearchModal />
</Suspense>
)}2. ν¨ν€μ§ μ΅μ ν
// β μ 체 μν¬νΈ (200KB)
import _ from 'lodash'
// β
νμν κ²λ§ (5KB)
import debounce from 'lodash/debounce'3. μ΄λ―Έμ§ μ΅μ ν
// Next.js Image μ»΄ν¬λνΈ + WebP/AVIF
<Image
src={url}
alt="Post"
width={800}
height={400}
quality={85}
placeholder="blur"
formats={['webp', 'avif']}
/>1. Select νλ μ ν
// β λͺ¨λ νλ κ°μ Έμ€κΈ°
const posts = await prisma.mainPost.findMany()
// β
νμν νλλ§
const posts = await prisma.mainPost.findMany({
select: {
id: true,
title: true,
createdAt: true,
author: { select: { name: true, image: true }}
}
})2. λ³΅ν© μΈλ±μ€ νμ©
model MainPost {
// μμ£Ό μ‘°ννλ μ‘°ν©μ μΈλ±μ€
@@index([status, categoryId, createdAt])
@@index([authorId, status])
@@index([slug])
}3. N+1 λ¬Έμ ν΄κ²°
// β N+1 λ¬Έμ λ°μ
const posts = await prisma.mainPost.findMany()
for (const post of posts) {
const author = await prisma.user.findUnique({ where: { id: post.authorId }})
}
// β
Includeλ‘ ν λ²μ μ‘°ν
const posts = await prisma.mainPost.findMany({
include: { author: true }
})1. νμ΄μ§ μΊμ±
export async function getPostsByCategory(categoryId: string) {
const cacheKey = `posts:category:${categoryId}`
// μΊμ νμΈ
const cached = await redis.get(cacheKey)
if (cached) return JSON.parse(cached)
// DB μ‘°ν
const posts = await prisma.mainPost.findMany({
where: { categoryId },
take: 20
})
// μΊμ μ μ₯ (1μκ°)
await redis.set(cacheKey, JSON.stringify(posts), 'EX', 3600)
return posts
}2. μ€λ§νΈ μΊμ 무ν¨ν
// κ²μκΈ μ
λ°μ΄νΈ μ κ΄λ ¨ μΊμλ§ μμ
async function updatePost(postId: string, data: any) {
const post = await prisma.mainPost.update({ where: { id: postId }, data })
// κ΄λ ¨ μΊμ 무ν¨ν
await Promise.all([
redis.del(`post:${postId}`),
redis.del(`posts:category:${post.categoryId}`),
redis.del('posts:recent')
])
return post
}// 1. μ€μ 리μμ€ ν리λ‘λ
<link rel="preload" href="/fonts/main.woff2" as="font" crossOrigin="" />
// 2. μ΄λ―Έμ§ μ°μ μμ
<Image priority src={heroImage} alt="Hero" />
// 3. μλ² μ»΄ν¬λνΈ νμ©
export default async function Hero() {
const data = await getHeroData() // μλ²μμ μ€ν
return <HeroSection data={data} />
}// 1. μ½λ μ€ν리ν
const HeavyComponent = dynamic(() => import('./HeavyComponent'), {
loading: () => <Skeleton />,
ssr: false
})
// 2. μ΄λ²€νΈ νΈλ€λ¬ μ΅μ ν
const handleClick = useCallback(() => {
// μ΅μ νλ λ‘μ§
}, [dependency])/* 1. μ΄λ―Έμ§ ν¬κΈ° λͺ
μ */
.post-image {
aspect-ratio: 16/9;
width: 100%;
}
/* 2. ν°νΈ λ‘λ© μ΅μ ν */
@font-face {
font-display: swap; /* FOUT λ°©μ§ */
}
/* 3. μ€μΌλ ν€ UI */
.skeleton {
animation: pulse 2s infinite;
}// app/layout.tsx
import { Analytics } from '@vercel/analytics/react'
import { SpeedInsights } from '@vercel/speed-insights/next'
export default function RootLayout({ children }) {
return (
<html>
<body>
{children}
<Analytics />
<SpeedInsights />
</body>
</html>
)
}// 컀μ€ν
μ±λ₯ μΈ‘μ
export function measurePerformance(name: string, fn: () => Promise<any>) {
return async (...args: any[]) => {
const start = performance.now()
const result = await fn(...args)
const end = performance.now()
console.log(`${name}: ${(end - start).toFixed(2)}ms`)
return result
}
}{
"functions": {
"app/api/*": {
"maxDuration": 10
}
},
"images": {
"formats": ["image/avif", "image/webp"],
"minimumCacheTTL": 31536000
}
}# Turbopack μ¬μ© (κ°λ°)
npm run dev --turbo
# νλ‘λμ
λΉλ
npm run build -- --debug # λΉλ λΆμ
# Bundle Analyzer
ANALYZE=true npm run build