Optimize Next.js ด้วย Dynamic import

Komkanit
3 min readApr 10, 2018

--

ใน Single page app ทั่วๆไปนั้น จะเกิดจาก Javascript ไฟล์เดียว ในการ generate หน้าเว็ปทั้งหมด ไม่ว่าจะใช้ router หรือไม่ ส่งผลให้เมื่อเว็ปของเราเริ่มใหญ่ขึ้นมากๆ first load จะช้ามาก เพราะต้องโหลด javascript ที่ใหญ่มาก ก่อนมีหน้าแรกโชว์ขึ้นมา แต่ webpack ก็ได้มี feature ที่ชื่อว่าการ chunk file คือการแบ่งจากไฟล์ javascript ขนาดใหญ่ เป็นไฟล์ย่อยๆหลายๆไฟล์เพื่อทำให้เว็ปโหลดได้ไวขึ้น

Next.js กับ chunk file

สำหรับ Next.js นั้น ได้ทำการแบ่ง chunk file มาให้เรียบร้อย โดยจะแยก chunk file ตาม ไฟล์ pages แต่ละหน้าของเรา

เมื่อลองใช้ webpack-bundle-analyzer เช็คไฟล์นั้น จะเห็นว่ามีไฟล์หลักๆสองไฟล์คือ index.js และ about.js ซึ่งทั้งสองไฟล์นั้นโดน layout ครอบอยู่อีกชั้น โค๊ดคร่าวๆเป็นประมาณนี้

const layout = (Component) => () => (
<div>
<Header />
<Component />
<Counter />
</div>
);
class Index extends React.Component {
render () {
return (
<div>
<p>HOME PAGE</p>
</div>
)
}
}
export default layout(Index);

ซึ่งจะเห็นว่าถ้าเราเขียนแบบปกตินั้น layout.js ถูกเรียกทั้งใน index.js และ about.js ทั้งๆที่เป็นไฟล์เดียวกัน ถ้าดูจาก webpack-bundle-analyzer ไฟล์ index.js จะมีขนาด 6.91 KB และ about.js จะมีขนาด 3.79 KB เรามาลองทำให้ไฟล์มีขนาดเล็กลงกว่านี้กันดีกว่า

Dynamic import

Dynamic import คือการ chunk file เพื่อแยกไฟล์ขนาดใหญ่ให้เป็นไฟล์ย่อยๆหลายๆไฟล์แทน ถ้าดูจาก โค๊ด layout ในด้านบนนั้น จะเห็นว่าใน layout จะเรียก Header, Counter ซ้ำ ทั้งใน index และ about เราจะทำการแยกสองไฟล์นี้ออกมาจาก layout

import React from 'react'
import dynamic from 'next/dynamic'
const Header = dynamic(import('../components/Header'))
const Counter = dynamic(import('../components/Counter'))
const layout = (Component) => () => (
<div>
<Header />
<Component />
<Counter />
</div>
)
export default layout

เราจะใช้ function dynamic ครอบ module ที่เราจะ import แทน การ import แบบปกติ เพื่อแยกเจ้า module นี้ออกมาเป็นอีกไฟล์นึง และผลที่ได้ก็คือ

ไฟล์ index.js จะเหลือขนาด 5.13 KB และ about.js จะมีขนาด 2.02 KB เพราะไฟล์ layout.js ไม่มี 2 modules อยู่ข้างในแล้ว ซึ่ง Counter.js มีขนาด 2.32 KB และ Header มีขนาด 512 B ที่จะโดนแยกออกจากไฟล์ใน pages และเมื่อเราลองเข้าจาก browser ดู

เมื่อเข้าหน้า / ใน browser จะโหลด index, Header, Counter ออกมาแยกจากกัน และเมื่อเราลองเข้าหน้า about ดู ได้ผลว่า Header, Counter ไม่ถูกโหลดซ้ำอีกรอบ นอกจากจะทำให้ไฟล์หลักมีขนาดเล็กลง ยังช่วยให้ลดการโหลดข้อมูลซ้ำอีกด้วย!

นอกจากการใช้ Dynamic miport แยกไฟล์ที่ถูกโหลดซ้ำบ่อยๆแล้ว ยังมีความสามารถอื่นๆอีก เช่น

ใส่ loading เมื่อ component กำลังถูกโหลดอยู่

const ComponentWithLoading = dynamic(
import('../components/hello'),
{
loading: () => (<p>loading</p>)
}
)

บังคับให้ โหลด component ใน client side เท่านั้น ซึ่งจะช่วยให้การเข้า browser ครั้งแรกที่ถูก compile โดย server side นั้นทำงานได้ไวขึ้น หรือใน component นั้นมีการใช้ window ก็ไม่มีปัญหา

const ComponentWithNoSSR = dynamic(
import('../components/hello'),
{ ssr: false }
)

สามารถใส่เงื่อนไขในการโหลด component แทนที่จะดัก if ใน render ซึ่งจะช่วยลดขนาดไฟล์ที่ไม่จำเป็นไม่ได้มาก

const DynamicSpecialComponent = dynamic({
modules: (props) => {
const components = {
Hello1: import('../components/hello1')
}
if (props.special) {
components.Hello2 = import('../components/hello2')
}
return components
},
render: (props, { Hello1, Hello2 }) => (
<div>
<Hello1 />
{ Hello2 ? <Hello2 /> : null }
</div>
)
})

เท่านี้เราก็จะสามารถลดขนาดไฟล์และเพิ่ม performance ให้กับ Next.js ได้พอสมควร ใครที่มีทริคดีๆสามารถนำมาแชร์กันได้นะครับ

สามารถอ่านข้อมูลเพิ่มเติม และตัวอย่างได้ที่

--

--