เครื่องมือตรวจสอบ
Next.js ไม่มี playground ให้รันใน browser — วิธีเดียวที่จะพิสูจน์ข้อกล่าวอ้างเกี่ยวกับมันคือ project จริง, การรัน tsc/next build จริง, หรือ browser จริง ทุก output ด้านล่างเป็นของจริง ที่บันทึกไว้กับ Next.js 16.3.6 และ React 19.2.8 ไม่มีเครื่องมือไหนซ้อนทับกันสมบูรณ์ และไม่มีตัวไหนใกล้เคียงคำว่าครบถ้วนด้วยตัวเอง — บรรทัด “จับได้โดย” ของแต่ละ 25 entry ใน AI Bug Catalog ระบุไว้ชัดว่าเครื่องมือไหน (ถ้ามี) ในหน้านี้ที่จะจับมันได้
tsc --noEmit — ผ่านรอบแรกที่เร็ว
หัวข้อที่มีชื่อว่า “tsc --noEmit — ผ่านรอบแรกที่เร็ว”การ type-check เพียงอย่างเดียวจับแคตตาล็อกนี้ได้น้อยอย่างน่าประหลาดใจโดยการออกแบบ — กฎส่วนใหญ่ของ Next.js เอง (boundary Server/Client, ความขัดแย้งของ cacheComponents, การเข้าถึงข้อมูลตอน prerender) ถูกบังคับใช้โดยขั้นตอน build ของ framework ไม่ใช่โดยระบบ type ของ TypeScript สิ่งที่ tsc จับได้ จริงๆ: การเปลี่ยน signature จริงอย่าง revalidateTag ที่ตอนนี้ต้องการสอง argument, และการเข้าถึง params/searchParams ที่ข้าม await — แต่เฉพาะเมื่อ prop นั้นถูก type เป็น Promise จริงๆ (object type ที่หลวมและเขียนเองจะทำให้สิ่งนี้เงียบไป; helper PageProps/LayoutProps ที่สร้างจาก next typegen จะถูกต้องเสมอ)
npx tsc --noEmitapp/actions.ts(6,3): error TS2554: Expected 2 arguments, but got 1.การอ่าน output ของ next build
หัวข้อที่มีชื่อว่า “การอ่าน output ของ next build”next build พิมพ์ตาราง route หลัง build สำเร็จทุกครั้ง สัญลักษณ์หนึ่งตัวต่อ route:
legend เต็ม (เอกสารทางการ คำต่อคำ):
○ (Static) Fully prerendered at build time. Served without server rendering.◐ (Partial Prerender) Static shell served immediately. Dynamic content streams in at request time.● (SSG) Prerendered static HTML (from generateStaticParams or getStaticProps).ƒ (Dynamic) Server-rendered on demand for each request.○ Static, ● SSG, และ ƒ Dynamic เป็น mode ที่ Next.js render มาตลอด ทั้ง App Router และ Pages Router — next build output จริง, config เริ่มต้น, ไม่มี cacheComponents, หน้าที่อ่าน cookies():
Route (app)┌ ○ /└ ƒ /catalog/11-cookies-static◐ Partial Prerender จะปรากฏก็ต่อเมื่อ Cache Components เปิดอยู่ (cacheComponents: true) — output จริงจากคู่มือ next build ที่ bundle มา ตอนเพิ่ม generateStaticParams เข้าไปในหน้า product แบบ dynamic:
Route (app)┌ ○ /├ ○ /_not-found└ /products/[id] ├ ◐ /products/[id] # Params known, data still uncached ├ ◐ /products/1 ├ ◐ /products/2 └ ◐ /products/3ถ้าไม่มี Cache Components การอ่านพฤติกรรม cache ของ route หมายถึงการเช็คการเรียก cookies()/headers()/fetch ด้วยตา — ตารางเองแค่พิมพ์ ƒ ทันทีที่มีตัวไหนทำงาน แบบเงียบๆ ตามที่ entry 11 ของ AI Bug Catalog แสดงไว้
warning ที่ควรอ่านต่อจากตาราง route ไม่ใช่แค่ดู exit code ผ่าน/ไม่ผ่าน:
⚠ The Edge Runtime is deprecated. You can use the "nodejs" runtime instead. Learn more: https://nextjs.org/docs/messages/edge-runtime-deprecated⚠ metadataBase property in metadata export is not set for resolving social open graph or twitter images, using "http://localhost:3000".⚠ The "middleware" file convention is deprecated. Please use "proxy" instead.ไม่มีสามอันนี้ทำให้ build พังหรือ exit code ของ CI พัง — ทั้งสามยังพิมพ์ output สำเร็จปกติของ next build และ exit 0 CI pipeline ที่เช็คแค่ exit code จะพลาดทั้งสามอัน
อะไรมาแทนที่ next lint
หัวข้อที่มีชื่อว่า “อะไรมาแทนที่ next lint”ตั้งแต่ Next.js 16 คำสั่ง next lint เองถูกลบไป — next build ไม่รัน linting เป็นขั้นตอน build เลย เอกสาร bundle บอกชัดเจนเรื่องตัวแทน:
The
next lintcommand has been removed. Use Biome or ESLint directly.
สำหรับ ESLint โดยเฉพาะ eslint-config-next ยังคง ship rule เดียวกันของ @next/eslint-plugin-next อยู่ (รวมถึง no-html-link-for-pages, no-img-element, และที่เหลือ) — ตอนนี้คุณต้องรัน ESLint CLI เอง กับ eslint.config.mjs แบบ flat:
npx eslint .codemod ช่วย migrate setup next lint เดิมให้อัตโนมัติ:
npx @next/codemod@canary next-lint-to-eslint-cli .next experimental-analyze — องค์ประกอบ bundle จริง
หัวข้อที่มีชื่อว่า “next experimental-analyze — องค์ประกอบ bundle จริง”เพราะ next build เลิกพิมพ์คอลัมน์ bundle-size ต่อ route แล้ว (v16.0.0) next experimental-analyze (stable ตั้งแต่ v16.1.0) คือเครื่องมือที่แสดงสิ่งที่ถูกส่งไปให้ browser จริงๆ กรองได้ตาม route และตาม client/server:
npx next experimental-analyze --output✓ Running next.config.ts took 19ms Analyzing a production build...✓ Analyze completed in 926ms. Results written to .next/diagnostics/analyze.To explore the analyze results interactively, run `next experimental-analyze` without `--output`.ถ้าไม่ใส่ --output มันจะเปิด local server พร้อม treemap แบบ interactive แทนการเขียนไฟล์ static — มีประโยชน์สำหรับการเปรียบเทียบแบบที่ entry 1 ของ AI Bug Catalog ทำ (chunk 8,967 byte เทียบกับ 287 byte ขึ้นอยู่กับว่า 'use client' อยู่ตรงไหน)
next typegen — type ของ route โดยไม่ต้อง build เต็ม
หัวข้อที่มีชื่อว่า “next typegen — type ของ route โดยไม่ต้อง build เต็ม”next typegen สร้าง helper type PageProps/LayoutProps/RouteContext (และ next-env.d.ts) โดยไม่ต้องรัน next dev หรือ next build เต็มรูปแบบ — มีประโยชน์ใน CI ที่อยากได้แค่ type-check โดยไม่ต้องจ่ายค่า build เต็ม:
npx next typegenGenerating route types...✓ Types generated successfullyรันมันก่อน tsc --noEmit ใน CI job ที่ต้องการแค่ type-check: next typegen && tsc --noEmit ถ้าไม่รันมัน (หรือไม่เคยรัน next dev/next build เลยสักครั้ง) type helper PageProps<'/route'> ที่สร้างขึ้นก็จะยังไม่มีอยู่เลย
next build --debug-prerender — stack trace จริงสำหรับ prerender error
หัวข้อที่มีชื่อว่า “next build --debug-prerender — stack trace จริงสำหรับ prerender error”production build minify server code และข้าม source map โดยค่าเริ่มต้น ดังนั้น stack trace ของ prerender error แบบ Cache-Components จึงแทบไม่มีประโยชน์ด้วยตัวเอง --debug-prerender (stable ตั้งแต่ v15.4.0) ปิด minification, เปิด source map ฝั่ง server, และที่สำคัญคือ ทำงานต่อแม้เจอ prerender failure ครั้งแรก แล้ว เพื่อให้ทุก route ที่ถูกบล็อกโผล่มาในรอบเดียว แทนที่จะต้อง rebuild ทีละอัน:
npx next build --debug-prerenderblocking-prerender error พร้อม --debug-prerender ชี้ตรงไปที่บรรทัดที่มีปัญหา (output จริง จากคู่มือ next build ที่ bundle มา สำหรับการอ่าน params ที่ไม่ห่อ):
Error: Route "/products/[id]": Next.js encountered uncached or runtime data during prerendering. ... at Page (app/products/[id]/page.tsx:2:30) 1 | export default async function Page(props: PageProps<'/products/[id]'>) {> 2 | const { id } = await props.params | ^ 3 | const res = await fetch(`https://api.example.com/products/${id}`)คำเตือน ตรงจาก CLI เลย: ห้าม deploy build ที่สร้างด้วย --debug-prerender เด็ดขาด — มันข้าม optimization ที่ production ต้องการ
การอ่าน RSC payload ใน Network tab
หัวข้อที่มีชื่อว่า “การอ่าน RSC payload ใน Network tab”ทุกการ navigate และการโหลดหน้าครั้งแรกพก React Server Components payload มาด้วย — การ serialize ต้นไม้ที่ server render แล้วแบบกระชับ, streaming, ไม่ใช่ JSON — มองเห็นได้ใน Network tab ของ browser เป็น request document ของหน้าเอง (สำหรับการโหลดครั้งแรก) หรือเป็นการ fetch แบบ text/x-component (สำหรับการ navigate ฝั่ง client) payload จริงชิ้นหนึ่งหน้าตาแบบนี้ (ตัดให้สั้นลง จากการรัน next build + next start จริง):
0:{"P":null,"c":["","bug-public-env"],"q":"","i":false,"f":[[["",{"children":["bug-public-env",{"children":["__PAGE__",{},"$undefined","$undefined",4608]},"$undefined","$undefined",4608]},"$undefined","$undefined",4624], ...ในทางปฏิบัติคุณจะไม่อ่านรูปแบบนี้ทีละตัวอักษร — จุดประสงค์ของการโหลดมันใน Network tab คือยืนยันว่า มี การ fetch RSC จริงเกิดขึ้นตอน navigate หรือเปล่า (เทียบกับ full page reload จาก <a> เดี่ยวๆ ตาม entry 25 ของ AI Bug Catalog) และดูคร่าวๆ ว่ามันใหญ่แค่ไหนเทียบกับเนื้อหาจริงบนหน้าจอ
Playwright smoke test
หัวข้อที่มีชื่อว่า “Playwright smoke test”Playwright test ตัวเดียวที่โหลดแอปที่ build จริงแล้วมาแล้ว assert สถานะ DOM สุดท้าย จับ bug ทั้งกลุ่มที่เครื่องมือ static ในหน้านี้จับไม่ได้: อะไรก็ตามที่ปรากฏหลัง hydration, หลัง client-side navigation, หรือหลัง Server Action round-trip เท่านั้น probe ของคอร์สนี้เองใช้เทคนิคนี้เป๊ะๆ เพื่อพิสูจน์ bug การเข้าถึง NEXT_PUBLIC_ แบบ dynamic ใน entry 19 ของ AI Bug Catalog — การอ่านแบบ static literal ถูกต้องใน HTML ที่ server render ตอนแรก และมีแค่ browser จริง หลัง hydration เท่านั้นที่เผยให้เห็น undefined ของการอ่านแบบ dynamic
// A Playwright test file (tests/smoke.spec.ts) — illustrative, not a// fence this course's own tsc harness compiles, since @playwright/test// isn't a dependency of the type-check probe.import { test, expect } from '@playwright/test'
test('renders the real client-side value, not the SSR one', async ({ page }) => { await page.goto('http://localhost:3000/some-route') await expect(page.locator('body')).toContainText('value: undefined')})instrumentation.ts ตอน development ไม่ใช่แค่ production
หัวข้อที่มีชื่อว่า “instrumentation.ts ตอน development ไม่ใช่แค่ production”register() ใน instrumentation.ts รันครั้งเดียวสำหรับ Next.js server instance ใหม่ทุกตัว — รวมถึง next dev ด้วย — ไม่ใช่แค่ตอน deploy production เดินสาย onRequestError ตั้งแต่เนิ่นๆ แม้จะแค่ console.error ตอน development ในเครื่อง ก็เผย server error ทันทีที่มันเกิดขึ้น แทนที่จะรู้หลังรายงานไปถึง production เท่านั้น ดู Deployment and Self-Hosting สำหรับตัวอย่าง onRequestError จริงเต็มรูปแบบที่คอร์สนี้สร้างไว้กับ observability provider จริง
Harness ของคอร์สนี้เอง: compile ทุก snippet
หัวข้อที่มีชื่อว่า “Harness ของคอร์สนี้เอง: compile ทุก snippet”ทุก code fence ในคอร์สนี้ที่ทำเครื่องหมายด้วยบรรทัดแรก // path.ext เป็นไฟล์จริง ถูกเก็บโดย tools/verify-snippets.mjs ลงไดเรกทอรีแยกตาม namespace ต่อบท แล้ว type-check ด้วย tsc --noEmit รอบเดียวทั้งคอร์ส — หลักการเดียวกับ Playwright smoke test ข้างบน แต่ขยายเป็น “ทุก snippet ที่ผู้อ่านอาจ copy-paste compile ได้จริงไหม” ไม่ใช่แค่ไฟล์เดียวที่คุณกำลังแก้อยู่:
node tools/verify-snippets.mjsno type errors.
Per-module fence summary (collected / skipped-no-path / expect-error): reading-nextjs: 46 / 0 / 10 ...fence ที่ตั้งใจโชว์โค้ดที่พัง (เหมือนตัวอย่าง “โค้ดที่ผิด” ส่วนใหญ่ใน AI Bug Catalog) ขึ้นต้นด้วย // @expect-error <เหตุผล> เพื่อให้ harness ข้ามมันไป — เนื้อหาบทเรียนพก output จริงของ build/tsc ที่บันทึกแยกไว้ต่างหากแทน ไม่ต้องให้ harness reproduce ความล้มเหลวที่ตั้งใจไว้ทุกครั้งที่รัน
Mental Model
หัวข้อที่มีชื่อว่า “Mental Model”flowchart TD
subgraph Static["Static (source/config only)"]
Tsc["tsc --noEmit<br/>real type errors: revalidateTag, typed params"]
Lint["ESLint (eslint-config-next) / Biome<br/>no-html-link-for-pages and friends"]
Verify["tools/verify-snippets.mjs<br/>compiles every tagged fence in the course"]
end
subgraph BuildTime["Build-time (needs a real next build)"]
Build["next build route table<br/>○ ● ◐ ƒ, plus warnings that don't fail CI"]
Debug["next build --debug-prerender<br/>real stack traces for prerender errors"]
Analyze["next experimental-analyze<br/>real bundle composition per route"]
Typegen["next typegen<br/>PageProps/LayoutProps without a full build"]
end
subgraph Runtime["Runtime (needs a real browser or server)"]
Rsc["RSC payload in the Network tab"]
Pw["Playwright smoke test<br/>post-hydration, post-navigation state"]
Instr["instrumentation.ts onRequestError<br/>in next dev, not just production"]
end