π‘ μ΄ κΈμ λ²μ λ° ν΄μν κΈ μ λλ€.
ν¬λ‘¬μ μλ°μ€ν¬λ¦½νΈ μ€νμμ€ μνκ³μ νλ μ μν¬ λ° λꡬλ€κ³Ό νλ ₯νκ³ μμ΅λλ€. μ΅κ·Όμ, Nextjsμ Gatsbyμ λ‘λ© μλλ₯Ό ν₯μμν€κΈ° μν΄ μλ§μ μ κ· μ΅μ νκ° μΆκ°λμμ΅λλ€. μ΄ κΈμ λ νλ μμν¬μμ μ°¨μ© μ€μΈ ν₯μλ granular Chunking μ λ΅μ λν΄ λ€λ£Ήλλ€.
λ§μ μΉ νλ μμν¬μ κ°μ΄, Next.jsμ Gatsbyλ μΉν©μ μ£Όμ λ²λ€λ¬λ‘ μ¬μ©νκ³ μμ΅λλ€. μΉν© v3μμλ μλ‘ λ€λ₯Έ entry point κ° κ³΅μ λλ λͺ¨λ(e.g ν΄μμ μ£Όμ - μλ‘ λ€λ₯Έ νμ΄μ§μ΄μ§λ§ μλ‘ κ³΅μ νλ λͺ¨λλ‘ μλ₯Ό λ€μ΄ reactμ κ°μ λͺ¨λ)μ λ¨μΌ(λλ μΌλΆ)μ βcommonβ chunkλ‘ λ§λ€ μ μλλ‘ CommonChunkPlugin
μ μκ°νμμ΅λλ€. 곡μ λλ μ½λλ κ°κ° λ€μ΄λ°μ μ μκ³ , λΈλΌμ°μ μΊμμ μ μ₯λμ΄ κ²°κ³Όμ μΌλ‘ λ‘λ© μλ κ°μ μ κΈ°μ¬ν©λλ€.(e.g ν΄μμ μ£Όμ - 곡μ νλ λͺ¨λμ 미리 λ€μ΄λ°κ³ μ μ₯νκ³ μμΌλ©΄, λ€μ entry pointμ μ§μ
ν λ λ°λ‘ λ€μ΄λ°μ§ μμλ λκΈ° λλ¬Έ)
μ΄λ° ν¨ν΄μ, λ€μκ³Ό κ°μ λ²λ€λ§ μ€μ μ μ±νν λ§μ SPA νλ μμν¬μμ λμ λμμ΅λλ€.
λΉλ‘ μ€μ©μ μ΄μ§λ§, λͺ¨λ 곡μ λͺ¨λμ νλμ chunkλ‘ κ³΅μ νλ€λ 컨μ
μ μ μ½μ΄ μμ΅λλ€. λͺ¨λ entryPointμμ 곡μ λμ§ μλ λͺ¨λλ ν΄λΉ λͺ¨λμ΄ νμμλ κ²½λ‘μμλ κ²°κ³Όμ μΌλ‘ λ€μ΄λ‘λ λλ κ²°κ³Όκ° μ΄λλ©λλ€. μλ₯Ό λ€μ΄ page1μμ common
chunkλ₯Ό λ€μ΄λ°μ λ, page1
μ΄ moduleC
λ₯Ό μ¬μ©νμ§ μμμλ ν΄λΉ μ½λλ₯Ό λ€μ΄λ‘λ λ°κ² λ©λλ€. μ΄λ° μ΄μ λ‘, μΉν© v4λ ν΄λΉ νλ¬κ·ΈμΈ μ κ±°νκ³ μλ‘μ΄ νλ¬κ·ΈμΈμ λμ
νμ΅λλ€ : SplitChunksPlugin
SplitChunksPluginμ κΈ°λ³Έ μ€μ μ λλΆλΆμ μ¬μ©μμκ² μ λμν©λλ€. λ€μμ κ²½λ‘μ κ±Έμ³μ μ€λ³΅λ μ½λλ₯Ό fetchingνμ§ μλλ‘ νκΈ° μν 쑰건μ κΈ°λ°νμ¬ λ€μμ λΆν λ chunkλ€μ΄ μμ±λ©λλ€.
νμ§λ§, ν΄λΉ νλ¬κ·ΈμΈμ μ¬μ©νλ λ€μμ μΉ νλ μμν¬λ€μ μ¬μ ν chunk λΆν μ μν΄ βλ¨μΌ commonsβ μ λ΅μ μ·¨νκ³ μμ΅λλ€. μλ₯Ό λ€μ΄ Next.jsλ 50% μ΄μμ νμ΄μ§μμ μ¬μ©νλ λͺ¨λκ³Ό λͺ¨λ νλ μμν¬ μμ‘΄μ± λͺ¨λ(react, react-dom λ±)μ ν¬ν¨νλ λ¨μΌ commons
λ²λ€μ μμ±ν©λλ€.
const splitChunksConfigs = {
β¦
prod: {
chunks: 'all',
cacheGroups: {
default: false,
vendors: false,
commons: {
name: 'commons',
chunks: 'all',
minChunks: totalPages > 2 ? totalPages * 0.5 : 2,
},
react: {
name: 'commons',
chunks: 'all',
test: /[\\/]node_modules[\\/](react|react-dom|scheduler|use-subscription)[\\/]/,
},
},
},
λΉλ‘ framework-dependent code(react λ±)λ₯Ό νλμ 곡μ λ chunkλ‘ ν¬ν¨νλ€λ 건 μ΄λ€ entryPoint λ μ§ λ€μ΄λ°μ λ€μ μΊμλ μ μλκ±Έ μλ―Ένμ§λ§, μ λ° μ΄μμ νμ΄μ§ μ΄μμμ νμ©λλ κ³΅ν΅ λͺ¨λμ ν¬ν¨νλ μ¬μ© κΈ°λ°(usage-based)μ μ κ·Όλ²(νμ΅λ²)μ λ§€μ° ν¨μ¨μ μ΄μ§ λͺ»ν©λλ€. μ΄ λΉμ¨μ μ‘°μ νλ κ²μ λ€μ λ κ²°κ³Όλ₯Ό μΌκΈ°ν©λλ€.
μ΄ λ¬Έμ λ₯Ό ν΄κ²°νκΈ° μν΄μ, Next.jsλ λΆνμν μ½λλ₯Ό μ€μ΄κΈ° μν΄ SplitChunksPlugin
μ μν λ€λ₯Έ μ€μ μ μ±ννμ΅λλ€.
framworks
λΌλ μ΄λ¦μ κ°λ³ chunkλ‘ λΆλ¦¬νλ€.μ΄λ° granular Chunking μ λ΅μ λ€μκ³Ό κ°μ μ΄μ μ μ 곡ν©λλ€.
...
prodGranular: {
chunks: 'all',
// chunk λ¨μλ₯Ό λͺ
μ
cacheGroups: {
default: false,
vendors: false,
// framework κ΄λ ¨ λͺ¨λ
framework: {
name: 'framework',
// 'all' | 'async' | 'initial'
// μ΄λ€ chunksκ° μ΅μ νλ₯Ό μν΄ μ νλ μ§λ₯Ό λͺ
μ
chunks: 'all',
test: /[\\/]node_modules[\\/](react|react-dom)[\\/]/,
priority: 40,
},
lib: {
// 160KB μ΄μ λλμ§ μ²΄ν¬
test(module: { size: Function }): boolean {
return module.size() > 160000
},
// 160KB μ΄μ λλ λͺ¨λλ€μ chunk μ΄λ¦ μ§μ
name(module: { identifier: Function }): string {
let nodeModuleNameResults = /node_modules\/(.*)/.exec(
module.identifier()
)
return nodeModuleNameResults
? nodeModuleNameResults[1]
: module.identifier()
},
priority: 30,
minChunks: 1,
reuseExistingChunk: true,
},
commons: {
name: 'commons',
chunks: 'all',
minChunks: totalPages, // entryPointsμ κ°μ
priority: 20,
},
shared: {
name: false,
priority: 10,
minChunks: 2,
reuseExistingChunk: true,
},
},
maxInitialRequests: 20,
},
...
granular chunkingμ μν SplitChunksPlugin
μ€μ κ³Ό Nextjsμ κ°μ νλ μμν¬μμ μ΄λ₯Ό μ μ©νλ μ κ·Όλ²μ μμ ν μλ‘μ΄ μ»¨μ
μ μλλλ€. νμ§λ§ μ¬μ ν λ§μ νλ μμν¬μμ λͺ κ°μ§ μ΄μ λλ¬Έμ λ¨μΌ βcommonβ λ²λ€ μ λ΅μ μ μ§νκ³ μμ΅λλ€. μ΄λ° μ΄μ μλ λ§μ HTTP μμ²μ΄ μ¬μ΄νΈ μ±λ₯μ λΆμ μ μΈ μν₯μ μ€ μ μλ€λ μ°λ €κ° ν¬ν¨λμ΄ μμ΅λλ€.
λΈλΌμ°μ λ νλμ origin μΌλ‘λΆν° μ νλ μμ TCP μ°κ²°(Chromeμ 6κ°)μ΄ κ°λ₯νκΈ° λλ¬Έμ, λ²λ€λ¬μ μν΄μ μ¬λ¬ κ°μ chunkλ‘ λλλ κ²μ μ΄λ° μ μ½ μν©(μ νλ TCP μ°κ²° κ°μ) μμ μ΄ μμ² μμ μ¦κ°λ‘ μ΄μ΄μ§λ€λ κ²μ νμ ν μ μμ΅λλ€. νμ§λ§ μ΄λ μ€μ§ HTTP/1.1 μ κ²½μ°μλ§ μ ν¨ν©λλ€. HTTP/2μ λ©ν°νλ μ±(Multiplexing)μ νλμ originκ°μ λ¨μΌ connectionμ μ΄μ©ν΄ λ³λ ¬μ μΌλ‘ λ€μμ μμ²λ€μ΄ μ²λ¦¬λλ κ²(be streamed)μ νμ©ν©λλ€. λ€λ₯Έ λ§λ‘ λ§νμλ©΄, μ°λ¦¬λ μΌλ°μ μΌλ‘ λ²λ€λ¬μ μν΄ μμ±λ chunk κ°μλ₯Ό μ ννλ κ²κ³Ό κ΄λ ¨ν΄ μ°λ €ν νμκ° μλ€λ λ§μ λλ€.
λλΆλΆμ μ£Όμ λΈλΌμ°μ λ HTTP/2λ₯Ό μ§μνκ³ μμ΅λλ€. ν¬λ‘¬κ³Ό Next.js νμ μ΄λ€ λ°©μμΌλ‘λ μ§ Next.jsμ λ¨μΌ common
chunkλ₯Ό μ¬λ¬ κ°μ chunkλ‘ λΆν ν¨μΌλ‘μ μ¦κ°νλ μμ²μ κ°μκ° λ‘λ© μ±λ₯μ μν₯μ μ£Όλμ§ νμΈνκ³ μΆμμ΅λλ€. κ·Έλ€μ maxInitialRequests
μμ±μ μ¬μ©νμ¬ λ³λ ¬ μμ²μ μ΅λ κ°μλ₯Ό λ³κ²½νλ©΄μ λ¨μΌ μ¬μ΄νΈμ μ±λ₯μ μΈ‘μ νκΈ° μμνμ΅λλ€.
νκ· μ μΌλ‘ μ¬λ¬ λ² μλνλ κ³Όμ μμ, load
, start-render
, FCP
μκ°μ΄ μ΅λ μ΄κΈ° μμ² κ°μμ λ³ν(5 to 15)λ₯Ό μ€ λμ λΉμ·ν μμΉλ₯Ό μ μ§νμμ΅λλ€. ν₯λ―Έλ‘κ²λ, μ°λ¦¬κ° μλ°±κ°μ μμ²μ νκΈ° μν΄ κ³΅κ²©μ μΌλ‘ splittingν μ΄νμ μ±λ₯μ μ½κ°μ overheadκ° λ°μνλ€λ μ μ μκ² λμμ΅λλ€.
μ΄ κ²°κ³Όλ μ λ’°ν μ μλ μκ³κ°(20~25κ°μ μμ²) λ΄μμ λ‘λ© μ±λ₯κ³Ό μΊμ± ν¨μ¨μ± μ¬μ΄μ μ μ ν κ· νμ μ μ§ν μ μμμ 보μ¬μ€λλ€. λͺ λ²μ ν
μ€ν
μ΄ν, maxInitialRequests
κ°μΌλ‘ 25λ²μ΄ μ νλμμ΅λλ€.
μμ²μ μ΅λ κ°μλ₯Ό λ³κ²½νλ κ²κ³Ό entry pointμ λ°λΌ μ μ ν λ²λ€μ λΆν νλ κ²μ κ°μ νμ΄μ§μμ λΆνμν μ½λ μμ μ€μ λλ€.
μ΄ μ€νμ λ¨μν νμ΄μ§ λ‘λ μ±λ₯μ λΆμ μ μΈ μν₯μ μ€ μ μλμ§ νμΈνκΈ° μν΄ μμ² κ°μλ₯Ό λ³κ²½νλ κ²λ§μ΄ μλλλ€. μ΄ κ²°κ³Όλ maxInitialRequests
κ°μ 25λ‘ μ€μ νλ κ²μ΄ νμ΄μ§ μλ κ°μ μμ΄ μλ°μ€ν¬λ¦½νΈ payload μ¬μ΄μ¦λ₯Ό μ€μΌ μ μκΈ° λλ¬Έμ μ μ νλ€λ κ²μ μ μνκ³ μμ΅λλ€. ν΄λΉ νμ΄μ§λ₯Ό hydrateνκΈ° μν΄ νμν μλ°μ€ν¬λ¦½νΈμ μ΄ μ¬μ΄μ¦λ μ¬μ ν λΉμ·ν κ²λ λ³Ό μ μλλ°, μ΄λ μ μ 체 μ½λ κ·λͺ¨κ° μ€μ΄λ€μ΄λ νμ΄μ§ λ‘λ μλκ° λ°λμ ν¨κ» ν₯μλμ§ μλλ€λ κ±Έ μλ―Έν©λλ€. (μ€μ λ‘ Reactκ° HTMLμ μμ±νκ³ , μ΄λ²€νΈ 리μ€λλ₯Ό λ±λ‘(hydrate)νκΈ° μν΄ νμν μλ°μ€ν¬λ¦½νΈ μ©λμ μ΄ μλ°μ€ν¬λ¦½νΈ payload μ©λμ μ€μ΄λ κ²κ³Ό 무κ΄ν μ μμμ μλ―Ένλ κ² κ°μ΅λλ€)
μΉν©μ chunk λΉ μ΅μ 30KBλ₯Ό κΈ°λ³Έ μ©λμΌλ‘ μ¬μ©νκ³ μμ΅λλ€. νμ§λ§, maxInitialRequests
λ₯Ό 25λ‘ μ€μ ν¨κ³Ό λμμ κΈ°λ³Έ μ©λ μ€μ μ 20KBλ‘ μ€μ νλ κ²μ΄ λ μ’μ μΊμ±μ ν μ μλλ‘ ν©λλ€.
Next.jsλ₯Ό ν¬ν¨ν λ§μ νλ μμν¬λ€μ λͺ¨λ route λ³κ²½ μ,μλ‘μ΄ script νκ·Έ(chunkλ₯Ό λ€μ΄ λ°κΈ° μν script)λ₯Ό μ½μ νκΈ° μν΄ cliet-side routing(CSR)μ μμ‘΄νκ³ μμ΅λΉλ€. νμ§λ§ μ΄λ»κ² λΉλ νμμ 미리 μμκ°κ° λ³νλ chunk μ½μ (dynamic chunk)μ κ²°μ ν μ μμκΉμ?
Next.jsλ server-side build manifest νμΌμ μ¬μ©νμ¬ κ° entry pointμμ μ΄λ€ chunkκ° μ¬μ©λλμ§ κ²°μ νκ³ μμ΅λλ€. μ΄λ° μ 보λ₯Ό clientμλ μ£ΌκΈ° μν΄μ, λͺ¨λ entry pointμ dependencies λ₯Ό 맡ννκΈ° μν΄ μΆμ½λ client-side build manifestκ° μμ±λ©λλ€.
// νΉμ route(κ²½λ‘)μ ν΄λΉνλ dependecies μ 보λ₯Ό κ°μ§ promiseλ₯Ό λ°ννλ€.
getDependencies (route) {
return this.promisedBuildManifest.then(
man => (man[route] && man[route].map(url => `/_next/${url}`)) || []
)
}
granular chunkingμ λμ νλ μ λ΅μ Next.js, Gatsby λ§ μλλΌ μ¬μ§μ΄ μΉν©μκ² λ νΉν κ²μ μλλλ€. λ§μ½ μ΄ν리μΌμ΄μ μ΄ λ²λ€λ¬λ νλ μμν¬λ₯Ό μ¬μ©ν¨μλ νλμ ν° βcommonsβ λ²λ€ μ λ΅μ λ°λ₯΄κ³ μλ€λ©΄, λΉμ μ μ΄ν리μΌμ΄μ chunking μ λ΅μ ν₯μμν€λλ‘ κ³ λ €ν΄ λ΄μΌ ν©λλ€.