Turbopack และ Build Performance
ไอเดียหลักในหนึ่งประโยค
หัวข้อที่มีชื่อว่า “ไอเดียหลักในหนึ่งประโยค”Turbopack คือ bundler ของ Next.js ที่เขียนด้วย Rust ตอนนี้เป็น default ทั้ง next dev และ next build และเร่ง feedback loop ด้วยการ compile แบบ incremental แทนที่จะ rebuild ทุกอย่างใหม่
Turbopack เป็น default แล้ว
หัวข้อที่มีชื่อว่า “Turbopack เป็น default แล้ว”ใน Next.js ปัจจุบัน (เวอร์ชัน 16 ณ ตอนที่เขียน) Turbopack เป็น bundler default ทั้ง next dev และ next build ฝั่ง development กับ Turbopack นิ่งมาระยะหนึ่งแล้ว ส่วน path ของ production build เป็นชิ้นที่ใหม่กว่า ควรเช็กกับ docs ของเวอร์ชันที่คุณใช้จริง คุณไม่ต้องใส่ flag อะไรเพื่อเปิดใช้ — script ธรรมดาก็พอ
{ "scripts": { "dev": "next dev", "build": "next build", "start": "next start" }}ถ้าเจอปัญหาแล้วต้องกลับไปใช้ bundler เดิม คุณ opt กลับไปใช้ webpack ได้เป็นราย command
{ "scripts": { "dev": "next dev", "build": "next build --webpack", "start": "next start" }}next.config ที่มีฟังก์ชัน webpack() แบบ custom แต่ไม่มี key turbopack จะทำให้ next build แบบ default fail แทนที่จะเงียบ ๆ ข้ามไป — ใส่ --webpack เพื่อใช้ webpack ต่อ หรือ --turbopack เพื่อให้ข้าม webpack config ไป
Turbopack เร่งอะไรบ้าง
หัวข้อที่มีชื่อว่า “Turbopack เร่งอะไรบ้าง”แก่นคือ incremental compilation Turbopack สร้าง dependency graph แล้วคำนวณใหม่เฉพาะส่วนที่ได้รับผลจากการเปลี่ยนแปลง cold start จึงเร็วขึ้นและ Hot Module Replacement ยังเกือบทันทีแม้แอปจะโตขึ้น เมื่อคุณ save ไฟล์ Turbopack จะ recompile เฉพาะ module นั้นกับตัวที่ขึ้นกับ module นั้น ไม่ใช่ทั้ง route
ย้าย webpack loader: resolveAlias และ rules
หัวข้อที่มีชื่อว่า “ย้าย webpack loader: resolveAlias และ rules”webpack คือ JavaScript bundler ที่สุกงอมและมี plugin ecosystem มหาศาล แต่แก่น JavaScript และแนวโน้มที่จะ rebuild ทั้งก้อนกลายเป็นคอขวดในแอปใหญ่ Turbopack เขียนด้วย Rust ออกแบบมาให้ทำงาน multi-core และถือว่างาน incremental แบบละเอียดเป็น default ไม่ใช่ของเสริม Turbopack ไม่รองรับ webpack plugin เลย — รองรับแค่ loader ของ webpack ที่ทดสอบแล้วบางส่วนเท่านั้น — และคุณตั้งค่ามันใต้ key turbopack ระดับบนสุด (ชื่อเดิมคือ experimental.turbo ยังใช้ได้เป็น alias แต่ config ใหม่ควรใช้ key ระดับบนสุด)
สอง option ที่ครอบคลุม webpack resolve config ที่คนใช้จริงเกือบทุกกรณี:
resolveAlias— เทียบเท่าresolve.aliasของ webpack ตรง ๆ สำหรับ map import specifier ไปยัง module อื่นrules— เทียบเท่าmodule.rulesของ webpack สำหรับใช้ loader (เช่น@svgr/webpack,yaml-loader,raw-loader) กับไฟล์ที่ตรงกับ glob
// next.config.ts — Turbopack-specific configurationimport type { NextConfig } from 'next'
const nextConfig: NextConfig = { turbopack: { resolveAlias: { underscore: 'lodash', // same shape as webpack's resolve.alias }, resolveExtensions: ['.mdx', '.tsx', '.ts', '.jsx', '.js', '.json'], // rules: { '*.svg': { loaders: ['@svgr/webpack'], as: '*.js' } } },}
export default nextConfigช่องว่างที่ต้องระวังตอนย้าย: loader runner ของ Turbopack (loader-runner) รองรับ API ของ loader มาตรฐานส่วนใหญ่ แต่ไม่ทั้งหมด — this.importModule/this.loadModule ไม่รองรับเลย, this.emitFile ไม่รองรับเลย และ this.fs รองรับแค่ readFile เท่านั้น loader ที่พึ่งพา API พวกนี้ต้องหาทางเลือกที่เข้ากับ Turbopack ได้ ไม่ใช่แค่เพิ่ม entry ใน rules
React Compiler: ต้นทุนจริง และต้องมี Babel plugin หรือเปล่า
หัวข้อที่มีชื่อว่า “React Compiler: ต้นทุนจริง และต้องมี Babel plugin หรือเปล่า”reactCompiler: true เปิด automatic memoization ตัดงาน useMemo/useCallback ด้วยมือไปเยอะ จุดที่ docs พูดตรง ๆ คือ: compiler รันผ่าน Babel plugin — Next.js ใส่ SWC pass แบบ custom มาก่อนเพื่อจำกัดว่าไฟล์ไหนจะถูกส่งเข้า Babel บ้าง แต่คุณก็ยังจ่ายต้นทุนตอน build จริง แม้จะจำกัดขอบเขต และต้องติดตั้ง babel-plugin-react-compiler เอง
import type { NextConfig } from 'next'
const nextConfig: NextConfig = { reactCompiler: true, // requires: npm install -D babel-plugin-react-compiler}
export default nextConfigNext.js 16.3 เพิ่มทางออกจากต้นทุน Babel นั้น: experimental.turbopackRustReactCompiler รัน native Rust port ของ compiler ข้างใน Turbopack ตรง ๆ — ไม่ต้องผ่าน Babel pass บน Node.js และไม่ต้องติดตั้ง babel-plugin-react-compiler เลย มันยังต้องมี reactCompiler: true ตั้งไว้อยู่ดี (มันแค่เลือกว่า implementation ไหน จะรัน) และใช้ได้เฉพาะกับ Turbopack เท่านั้น
// next.config.ts — 16.3+: skip the Babel plugin entirelyimport type { NextConfig } from 'next'
const nextConfig: NextConfig = { reactCompiler: true, experimental: { turbopackRustReactCompiler: true, // native Rust port, Turbopack-only, no babel-plugin-react-compiler needed },}
export default nextConfigdebug build: —debug-prerender กับ —profile
หัวข้อที่มีชื่อว่า “debug build: —debug-prerender กับ —profile”flag ของ next build สองตัว ทั้งคู่มีจริงและอยู่ใน Next.js เวอร์ชันนี้เป๊ะ (npx next build --help):
--debug-prerender— “Enables debug mode for prerendering. Not for production use!” — สำหรับ trace ว่าเกิดอะไรขึ้นจริง ๆ ตอน route ถูก prerender เมื่อการแบ่ง static/dynamic ไม่ทำงานตามที่คาด--profile— “Enables production profiling for React” สำหรับจับ React profiler trace ของ production build
next build --debug-prerender # trace prerendering decisions, dev-only diagnosticnext build --profile # React production profilingnext build --experimental-analyze ก็มีอยู่จริงเป็น flag ตอน build เทียบเท่ากับ command แยก next experimental-analyze ที่พูดถึงในบทก่อนหน้า
build caching
หัวข้อที่มีชื่อว่า “build caching”Turbopack เก็บงานลง disk ได้ การ start dev หรือ build ครั้งถัดไปจึงใช้ผล compile เดิมซ้ำแทนที่จะเริ่มจากศูนย์ cache ทั้งสองตัวเปิดโดย default (dev ตั้งแต่ 16.1, build ตั้งแต่ 16.3) แม้ option จะยังอยู่ใต้ experimental ก็ตาม cache ฝั่ง build อยู่ที่ .next/cache/turbopack จึงช่วยได้ก็ต่อเมื่อ CI ของคุณ restore .next/cache ระหว่าง build — เซ็ต option เป็น false เพื่อ opt out
// next.config.ts — both caches are on by default; opt out per commandimport type { NextConfig } from 'next'
const nextConfig: NextConfig = { experimental: { turbopackFileSystemCacheForDev: true, // default: true (16.1+) turbopackFileSystemCacheForBuild: false, // default: true (16.3+); false if CI never restores .next/cache },}
export default nextConfigflowchart LR Save[Save a file] --> Graph[Dependency graph] Graph --> Changed[Recompile changed module + dependents] Graph --> Cache[Reuse unchanged from cache] Changed --> HMR[Fast HMR / build] Cache --> HMR Compiler["reactCompiler: true"] -->|Babel plugin, default| Slow[Localized build cost] Compiler -->|turbopackRustReactCompiler: true, 16.3+| Fast[Native Rust, no babel-plugin-react-compiler]
Mental Model
หัวข้อที่มีชื่อว่า “Mental Model”rules/resolveAlias ของ Turbopack คือชั้นแปลภาษา ไม่ใช่การเขียนใหม่ทั้งหมด — คุณกำลังบอก Turbopack ว่า “นี่คือ webpack loader ที่คุณเชื่อถืออยู่แล้ว รันมันแบบที่เคยรัน” นั่นคือเหตุผลที่ขอบเขต API ของ loader (ไม่ใช่พฤติกรรมแกนของ bundler) คือจุดที่การย้ายมักพังจริง การแยก React Compiler (Babel เทียบกับ Rust port ใต้ turbopackRustReactCompiler) ก็เป็นทางเลือกรูปแบบเดียวกับ Turbopack เทียบกับ webpack เอง: ทางหนึ่งสุกงอมใช้ได้ทุกที่แต่ต้องมี dependency เพิ่ม ทางใหม่ตัด dependency นั้นออกแต่จำกัดแค่ Turbopack เท่านั้น
ข้อแลกเปลี่ยน
หัวข้อที่มีชื่อว่า “ข้อแลกเปลี่ยน”| ได้อะไร | ประโยชน์ | ต้นทุน |
|---|---|---|
Turbopack default (next dev/next build) | cold start เร็วขึ้น HMR เกือบทันที | webpack plugin ใช้ไม่ได้เลย รองรับแค่ loader บางส่วนที่ทดสอบแล้ว |
turbopack.resolveAlias/rules | เทียบเท่า resolve.alias/loader ของ webpack ตรง ๆ | loader ที่ใช้ importModule, emitFile หรือ this.fs ส่วนใหญ่ต้องหาวิธีอื่น |
reactCompiler: true (Babel, ทาง default) | automatic memoization ตัดงาน useMemo/useCallback ด้วยมือ | ต้นทุน build จริงจาก Babel plugin ต้องติดตั้ง babel-plugin-react-compiler เอง |
+ turbopackRustReactCompiler: true (16.3+) | ไม่ต้องผ่าน Babel ไม่ต้องติดตั้ง dependency เพิ่ม | ยัง experimental และใช้ได้เฉพาะ Turbopack |
--debug-prerender / --profile | diagnostic จริงสำหรับ prerendering และ React profiling | --debug-prerender ระบุตรง ๆ ว่า “not for production use” |
| Turbopack FS cache (เปิด default) | run dev/build ซ้ำเร็วขึ้น | ช่วยใน CI ได้ก็ต่อเมื่อ .next/cache ถูก restore จริงระหว่าง run |
ความเข้าใจผิดที่พบบ่อย
หัวข้อที่มีชื่อว่า “ความเข้าใจผิดที่พบบ่อย”- “Turbopack รองรับ webpack plugin เหมือนที่รองรับ loader” ไม่จริง — plugin ไม่รองรับเลย รองรับแค่ loader บางส่วนที่ทดสอบแล้วผ่าน
turbopack.rules - “
reactCompiler: trueมีแค่นี้ก็พอ Next.js รันแบบ native ให้เอง” โดย default มันยังรันผ่าน Babel plugin ที่คุณต้องติดตั้งเอง (babel-plugin-react-compiler) มีแค่turbopackRustReactCompiler: true(16.3+, Turbopack เท่านั้น) เท่านั้นที่ข้าม dependency นั้นได้ - “
--debug-prerenderปลอดภัยที่จะเปิดค้างไว้เพื่อ troubleshoot production build” help text ของ CLI เองบอกตรง ๆ ว่า “Not for production use!” - “Turbopack filesystem cache เร่ง CI build ทุกครั้งอัตโนมัติ” มันช่วยได้ก็ต่อเมื่อ CI ของคุณ restore
.next/cacheจริงระหว่าง run — ไม่งั้นทุก build ก็ยังเริ่มจากศูนย์เหมือนเดิมไม่ว่า flag จะเปิด default อยู่หรือไม่
อ่านโค้ดที่ AI เขียน
หัวข้อที่มีชื่อว่า “อ่านโค้ดที่ AI เขียน”เมื่อโค้ดที่ AI สร้างแตะ Turbopack หรือ build configuration เช็กสิ่งเหล่านี้ก่อนเชื่อ:
- มันตั้งค่า webpack plugin ใต้
turbopack.rulesหรือเปล่า? ที่นั่นรองรับแค่ loader เท่านั้น — plugin ไม่มีตัวเทียบเท่าผ่าน key นี้ - มันเปิด
reactCompiler: trueโดยไม่ทั้งติดตั้งbabel-plugin-react-compilerและไม่ทั้งตั้งturbopackRustReactCompiler: trueหรือเปล่า? ต้องมีอย่างใดอย่างหนึ่ง - มันแนะนำ
--debug-prerenderสำหรับ production deployment แทนที่จะเป็นการ debug บนเครื่อง local หรือเปล่า? - มีฟังก์ชัน
webpack()แบบ custom อยู่โดยไม่มี keyturbopackแล้วสมมติว่าnext builddefault จะเมินมันไปเฉย ๆ หรือเปล่า? ความจริงมันทำให้ build fail - มันสมมติว่า build cache ช่วย CI ได้โดยที่ pipeline ไม่ได้ restore
.next/cacheระหว่าง run จริง ๆ หรือเปล่า?
🐛 หา bug ในโค้ดนี้
import type { NextConfig } from 'next'
const nextConfig: NextConfig = {reactCompiler: true,}
export default nextConfigเฉลย
บรรทัดนี้เปิด React Compiler แต่โดย default มันรันผ่าน Babel plugin ที่ไม่ได้ bundle มากับ Next.js — คุณต้อง npm install -D babel-plugin-react-compiler แยกต่างหาก ไม่งั้น build จะ fail เพราะหา package นั้นไม่เจอ วิธีแก้มีสองทาง: ติดตั้ง Babel plugin เอง หรือ (Next.js 16.3+, Turbopack เท่านั้น) เพิ่ม experimental: { turbopackRustReactCompiler: true } ไว้ข้าง reactCompiler: true เพื่อใช้ native Rust port แทน ซึ่งไม่ต้องมี dependency เพิ่มเลย