ข้ามไปยังเนื้อหา

เครื่องมือตรวจสอบ

Next.js ไม่มี playground ให้รันใน browser — วิธีเดียวที่จะพิสูจน์ข้อกล่าวอ้างเกี่ยวกับมันคือ project จริง, การรัน tsc/next build จริง, หรือ browser จริง ทุก output ด้านล่างเป็นของจริง ที่บันทึกไว้กับ Next.js 16.3.6 และ React 19.2.8 ไม่มีเครื่องมือไหนซ้อนทับกันสมบูรณ์ และไม่มีตัวไหนใกล้เคียงคำว่าครบถ้วนด้วยตัวเอง — บรรทัด “จับได้โดย” ของแต่ละ 25 entry ใน AI Bug Catalog ระบุไว้ชัดว่าเครื่องมือไหน (ถ้ามี) ในหน้านี้ที่จะจับมันได้

การ 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 จะถูกต้องเสมอ)

Terminal window
npx tsc --noEmit
app/actions.ts(6,3): error TS2554: Expected 2 arguments, but got 1.

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.js 16 คำสั่ง next lint เองถูกลบไป — next build ไม่รัน linting เป็นขั้นตอน build เลย เอกสาร bundle บอกชัดเจนเรื่องตัวแทน:

The next lint command 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:

Terminal window
npx eslint .

codemod ช่วย migrate setup next lint เดิมให้อัตโนมัติ:

Terminal window
npx @next/codemod@canary next-lint-to-eslint-cli .

เพราะ next build เลิกพิมพ์คอลัมน์ bundle-size ต่อ route แล้ว (v16.0.0) next experimental-analyze (stable ตั้งแต่ v16.1.0) คือเครื่องมือที่แสดงสิ่งที่ถูกส่งไปให้ browser จริงๆ กรองได้ตาม route และตาม client/server:

Terminal window
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 สร้าง helper type PageProps/LayoutProps/RouteContext (และ next-env.d.ts) โดยไม่ต้องรัน next dev หรือ next build เต็มรูปแบบ — มีประโยชน์ใน CI ที่อยากได้แค่ type-check โดยไม่ต้องจ่ายค่า build เต็ม:

Terminal window
npx next typegen
Generating route types...
✓ Types generated successfully

รันมันก่อน tsc --noEmit ใน CI job ที่ต้องการแค่ type-check: next typegen && tsc --noEmit ถ้าไม่รันมัน (หรือไม่เคยรัน next dev/next build เลยสักครั้ง) type helper PageProps<'/route'> ที่สร้างขึ้นก็จะยังไม่มีอยู่เลย

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 ทีละอัน:

Terminal window
npx next build --debug-prerender

blocking-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 ต้องการ

ทุกการ 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 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')
})

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 จริง

ทุก code fence ในคอร์สนี้ที่ทำเครื่องหมายด้วยบรรทัดแรก // path.ext เป็นไฟล์จริง ถูกเก็บโดย tools/verify-snippets.mjs ลงไดเรกทอรีแยกตาม namespace ต่อบท แล้ว type-check ด้วย tsc --noEmit รอบเดียวทั้งคอร์ส — หลักการเดียวกับ Playwright smoke test ข้างบน แต่ขยายเป็น “ทุก snippet ที่ผู้อ่านอาจ copy-paste compile ได้จริงไหม” ไม่ใช่แค่ไฟล์เดียวที่คุณกำลังแก้อยู่:

Terminal window
node tools/verify-snippets.mjs
no 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 ความล้มเหลวที่ตั้งใจไว้ทุกครั้งที่รัน

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
เครื่องมือ static พิสูจน์จาก source หรือ config; เครื่องมือ dynamic ต้องการแอปที่รันอยู่หรือ browser จริง
บรรทัดนี้มาจากคำสั่งไหน และมาจาก fence ไหนใน AI Bug Catalog
app/actions.ts(6,3): error TS2554: Expected 2 arguments, but got 1.
CI pipeline รัน next build แล้ว exit 0 อะไรยังเป็นไปได้อยู่บ้าง
ทำไม next typegen ถึงสำคัญสำหรับการจับ bug 'การเข้าถึง params แบบ synchronous' โดยเฉพาะ
ทำไม --debug-prerender ถึงถูกระบุไว้ชัดเจนว่าไม่ปลอดภัยที่จะ deploy
Playwright smoke test จับอะไรได้ที่ tsc --noEmit กับ next build รวมกันจับไม่ได้
ทำไม fence นี้ถึงขึ้นต้นด้วย @expect-error แทนที่จะเป็นแค่ path comment
// @expect-error real Turbopack build error, not a type error — see prose
// app/catalog/07-router-push/page.tsx
import { useRouter } from 'next/navigation'
ทำไม harness ของคอร์สนี้ถึงทำเครื่องหมาย fence 'โค้ดที่ผิด' บางอันด้วย // @expect-error <เหตุผล> แทนที่จะให้ path comment จริง