1. Eliminating Waterfalls
์ค์๋ : CRITICAL (์ํฐํด ํ์์ ๊ฐ์ฅ ํฐ ์ฑ๋ฅ ์ ํ ์์ธ์ด๋ค.)
[!faq] Waterfall ์ด๋? ์ด์ ์์ฒญ์ด ์๋ฃ๋์ด์ผ ๋ค์ ์์ฒญ์ด ์งํ๋๋ ๊ฒ์ ๋งํ๋ค. ์ฆ ์ฌ๋ฌ ๋คํธ์ํฌ ์์ฒญ์ด ์์๋ ์์ฐจ์ ์ผ๋ก ํ๋์ฉ ์งํ๋๋ ํ์์ ์๋ฏธํ๋๋ฐ,
await์ ์ฌ์ฉํ๋ค๋ณด๋ฉด ๋ฐ์ํ ์ ์๋ค๊ณ ํ๋ค. ๋ฌผ๋ก ์๋์ ์ผ๋ก ์ํฐํด์ด ์ผ์ด๋๊ฒ ์ค๊ณํด์ผํ ์๋ ์์ง๋ง, ๊ทธ๋ ์ง ์์ ๊ฒฝ์ฐ๊ฐ ๋ ๋ง๋ค. ์ถ์ฒ
1. Defer Await Until Needed
์ค์๋ : HIGH
๊ฐ์ฅ ๊ฐ๋จํ์ง๋ง ํจ๊ณผ ๋ฑ๊ธ์ HIGH ์ด๋ค. ์คํต๋๋ ๊ฒฝ์ฐ๊ฐ ์ฆ์ ์ค๊ณ, ๋ฏธ๋ค์ง ์์ ์ด ๋ฌด๊ฑฐ์ด ๊ฒฝ์ฐ์ ํนํ ํจ๊ณผ์ ์ด๋ค.
Incorrect
async function handleRequest(userId: string, skipProcessing: boolean) {
const userData = await fetchUserData(userId)
if (skipProcessing) {
// Returns immediately but still waited for userData
return { skipped: true }
}
// Only this branch uses userData
return processUserData(userData)
}
Correct
async function handleRequest(userId: string, skipProcessing: boolean) {
if (skipProcessing) {
// Returns immediately without waiting
return { skipped: true }
}
// Fetch only when needed
const userData = await fetchUserData(userId)
return processUserData(userData)
}
2. Dependency-Based Parallelization
์ค์๋ : CRITICAL (2-10๋ฐฐ ์๋ ๊ฐ์ )
Partial Dependencies ๋ถ๋ถ์ ์์กด์ฑ์ด๋ ์ฌ๋ฌ ๋น๋๊ธฐ ์์ ์ด ์์ ๋, ์ด๋ค ์์ ์ ๋ค๋ฅธ ์์ ์ ๊ฒฐ๊ณผ๊ฐ ํ์ํ์ง๋ง, ์ด๋ค ์์ ์ ํ์ํ์ง ์์ ๊ฒฝ์ฐ๋ฅผ ๋งํ๋ค.
fetchUser(): 1์ด ์์fetchConfig(): 10์ด ์์fetchProfile(user.id): 10์ด ์์
์ด ์ธ ์์
์ด ์์๋ fetchProfile(user.id)๋ fetchUser()์ ๊ฒฐ๊ณผ๋ฅผ ํ์๋ก ํ๋ค. ์ฆ ์ด ๊ฒฝ์ฐ์๋ ์ํฐํด์ด ํ์ํ ์ํฉ.
Incorrect
๊ทธ๋์ ์์ ๋ ์์
์ Promise.all()๋ก ํ๋ฒ์ ๊ฐ์ ธ์ค๊ณ ์ ๊ทธ ํ์ ์๋์ ์์
์ ์คํํ๋๋ก ์ฝ๋๋ฅผ ์์ฑํ๋๊ฒ ํํ ํจํด์ด๋ค.
profile์ด ํ์๋กํ๋ user๋ฅผ ๊ฐ์ ธ์ค๋๋ฐ 1์ด๊ฐ ๊ฑธ๋ฆฌ๋๋ฐ ์ธ๋ฐ์์ด config ๊น์ง ๊ธฐ๋ค๋ฆฌ๋ฉด์ 10์ด๋ ๊ธฐ๋ค๋ฆฌ๋ ์ํฉ.
const [user, config] = await Promise.all([
fetchUser(), // 1s
fetchConfig() // 10s
]) // 10s
const profile = await fetchProfile(user.id) // 10s
// total : 20s
user๋ง ๋จผ์ ๊ฐ์ ธ์ค๋ ๋ฐฉ๋ฒ์ผ๋ก ํด๊ฒฐํ ์ ์๋ค.
const user = await fetchUser(); // 1s
const [profile, config] = await Promise.all([
fetchProfile(user.id), // 10s
fetchConfig() // 10s
]) // 10s
// total : 11s
๊ทธ๋ฐ๋ฐ ๋ฌธ์ ๋ ๋คํธ์ํฌ ์ฐ๊ฒฐ ์ํ ๋ณ๋๊ณผ ๊ฐ์ ์ด๋ค ์ด์ ๋ค๋ก ์ธํด ์ ์์ ๋ค์ ์์ ์๊ฐ์ด ๋ฐ๋๋ค๋ฉด? ์๋์ ์ฝ๋๊ฐ ์์ ์ฝ๋๋ณด๋ค ์คํ๋ ค ์๊ฐ์ด ๋ ๊ฑธ๋ฆด์๋ ์๊ฒ ๋๋ค.
๋ฐ๋ผ์ ๋ชจ๋ ์ํฉ์์ ํญ์ ์ต์ํ์ ์๊ฐ๋ง ์ฌ์ฉํ๊ณ ์ถ๋ค๋ฉด ๋ค์๊ณผ ๊ฐ์ด ์ง์ ์์กด ๊ด๊ณ๋ฅผ ํฌํจํด ์์ฑํด์ผ ํ๋ค.
const [[user, profile], config] = await Promise.all([
fetchUser().then(user=> fetchProfile(user.id).then(profile => [user, profile])),
fetchConfig()
])
๋ญ๊ฐ ๋ฌธ์ ์ธ๊ฐ ์ถ์ง๋ง, (์ญ์๋ ๋ค๋ฅธ ๋ชจ๋ ๋ฌธ์ ๋ค์ฒ๋ผ) ํ๋ก์ ํธ์ ๊ท๋ชจ๊ฐ ๊ฑฐ๋ํด์ง๋ฉด ์ด๋ ๊ฒ ์์ฑํ๋๊ฑด ํ๋ค๋ค๊ณ ํ๋ค. ๋งํฌ
์ด๋ ๊ฒ ์์ ๋น๊ตํด ์กฐ๊ธ๋ง ๋ณต์กํด์ ธ๋ ๊ฐ๋ ์ฑ์ด ํฌ๊ฒ ๋จ์ด์ง๋ค.
const promiseA = getA()
const promiseB = getB()
const promiseC = getC()
const promiseD = Promise.all([promiseA, promiseC]).then(([a, c]) => getD(a, c))
const promiseE = Promise.all([promiseB, promiseC]).then(([b, c]) => getE(b, c))
const [a, b, c, d, e] = await Promise.all([promiseA, promiseB, promiseC, promiseD, promiseE])
Correct
better-all ๋ผ์ด๋ธ๋ฌ๋ฆฌ๋ฅผ ์ฌ์ฉํ๋ฉด ๊ฐ๋จํ ํด๊ฒฐ๋๋ค.
import { all } from 'better-all'
const { user, config, profile } = await all({
async user() { return fetchUser() },
async config() { return fetchConfig() },
async profile() {
return fetchProfile((await this.$.user).id)
}
})
const { a, b, c, d, e } = await all({
async a() { return getA() },
async b() { return getB() },
async c() { return getC() },
async d() { return getD(await this.$.a, await this.$.c) },
async e() { return getE(await this.$.b, await this.$.c) }
})
better-all ๋ผ์ด๋ธ๋ฌ๋ฆฌ ๋งํฌ
3. Prevent Waterfall Chains in API Routes
์ค์๋ : CRITICAL (2-10๋ฐฐ ์๋ ๊ฐ์ )
api ๋ฃจํธ๋ ์๋ฒ์ก์
์ ์ฌ์ฉํ ๋, await ์์ด Promise๋ฅผ ์ฌ์ฉํ๋ค. ๋ฌผ๋ก ๋ณต์กํ ๊ฒฝ์ฐ์๋ ์์์ ์ ์ํ better-all ๋ผ์ด๋ธ๋ฌ๋ฆฌ ์ฌ์ฉํ๋๊ฒ ๋ ์ ํฉํ ํด๊ฒฐ๋ฒ.
Incorrect
์ํฐํด์ ์ ํ์ ์ธ ์์. fetchConfig()๋ ์์ ๊ณผ ์๋ฌด ์๊ด ์๋ auth()์ ๊ฐ๋๋ผ๊ธฐ๊ณ fetchData()๋ ์์ ๊ณผ ์๊ด ์๋ fetchConfig()๋ฅผ ๊ธฐ๋ค๋ฆฐ๋ค.
export async function GET(request: Request) {
const session = await auth() // 1s
const config = await fetchConfig() // 10s
const data = await fetchData(session.user.id) // 10s
return Response.json({ data, config })
// total 21s
}
Correct
export async function GET(request: Request) {
const sessionPromise = auth()
const configPromise = fetchConfig()
const session = await sessionPromise // 1s
const [config, data] = await Promise.all([
configPromise, // 10s
fetchData(session.user.id) // 10s
])
return Response.json({ data, config })
}
// total 11s
4. Promise.all() for Independent Operations
์ค์๋ : CRITICAL (2-10๋ฐฐ ์๋ ๊ฐ์ )
async ์์
๋ค ๋ผ๋ฆฌ ์์กดํ๊ณ ์์ง ์์๋(2๋ฒ๊ณผ ๊ฐ์ ๊ฒฝ์ฐ๊ฐ ์๋ ๋)๋ Promise.all()์ ์ฌ์ฉํ๋ค.
// Incorrect
const user = await fetchUser()
const posts = await fetchPosts()
const comments = await fetchComments()
// Correct
const [user, posts, comments] = await Promise.all([
fetchUser(),
fetchPosts(),
fetchComments()
])
5. Strategic Suspense Boundaries
์ค์๋ : HIGH
Suspense๋ฉํผ ์ปดํฌ๋ํธ๋ฅผ ์ฌ์ฉํ๋ค. ์ด๋ฌ๋ฉด ๋ฐ์ดํฐ๋ฅผ ๊ธฐ๋ค๋ฆฌ๋ ๋์์ ๋ฏธ๋ฆฌ ์ ์ํด๋ Skeleton ์ด ๋ณด์ด๊ฒ ๋๊ณ ๋ ๋น ๋ฅธ ์ด๊ธฐ ํ์ธํ
์ ํตํด ํจ์ฌ ๋์ ์ฌ์ฉ์ ๊ฒฝํ์ ์ ๊ณตํ๋ค.
Incorrect
async function Page() {
const data = await fetchData() // Blocks entire page
return (
<div>
<div>Sidebar</div>
<div>Header</div>
<div>
<DataDisplay data={data} />
</div>
<div>Footer</div>
</div>
)
}
Correct
function Page() {
return (
<div>
<div>Sidebar</div>
<div>Header</div>
<div>
<Suspense fallback={<Skeleton />}>
<DataDisplay />
</Suspense>
</div>
<div>Footer</div>
</div>
)
}
async function DataDisplay() {
const data = await fetchData() // Only blocks this component
return <div>{data.content}</div>
}
ํ์ง๋ง ๋ฌด์์ ์ฌ์ฉํ๋ฉด ์๋๊ณ ๋ค์๊ณผ ๊ฐ์ ๊ฒฝ์ฐ์๋ Suspense ์ฌ์ฉ์ ํ๋ฉด ์๋๋ค.
- ๋ ์ด์์์ ๊ฒฐ์ ํ ๋ ๊ผญ ํ์ํ ๋ฐ์ดํฐ์ธ ๊ฒฝ์ฐ.
- SEO์ ์ค์ํ ๋ฐ์ดํฐ์ธ ๊ฒฝ์ฐ.
- ๋ฐ์ดํฐ ํฌ๊ธฐ๊ฐ ์์ ๊ตณ์ด ๋น์ฉ์ ์๋ชจํด๊ฐ๋ฉฐ ์ค์ผ๋ ํค์ ๋ณด์ฌ์ค ํ์๊ฐ ์๋ ๊ฒฝ์ฐ.
- ์๊ฐ ํ๋ ๋ ์ด์์ ๋ณ๋ layout shift, content jumping์ ๋ฐฉ์งํด์ผ ํ๋ ๊ฒฝ์ฐ : Trade-off - ๋น ๋ฅธ ํ์ด์ง ๋ก๋ฉ์ ์ฑ๊ธฐ์ง๋ง potential layout shift๋ฅผ ๊ฐ์ํ ๊ฒ์ธ๊ฐ ์๋๋ฉด ๋๋ฆฌ๋๋ผ๋ ์์ ์ ์ธ Ui๋ฅผ ๋ง๋ค๊ฒ์ธ๊ฐ
[!faq] ๊ฒฐํฉ๋ ๋ฌธ์ ์์ ๊ฐ์ ์ค๊ณ๋๋ก๋ฉด
DataDisplay์ปดํฌ๋ํธ์ ํ ์คํธ ๋ณต์ก๋๊ฐ ์ฌ๋ผ๊ฐ์ง ์์๊น?