🧰 ของที่ใช้ตรวจในบทนี้
next 16.3.4
react 19.2.8
react-dom 19.2.8
typescript 5.9.3
node v24.14.0
# โปรเจกต์สร้างด้วย: npx create-next-app@latest --ts --tailwind --eslint --app --no-src-dir
# คำสั่งที่ใช้ตรวจ: npx tsc --noEmit · npx next build · npx next dev · curl
ทุก error code (TS2503 · TS2339) และทุกก้อน HTML ที่โชว์ในบทนี้
ก็อปมาจากผลรันจริง ไม่ได้พิมพ์จากความจำ
คุณ copy โค้ดไปรันซ้ำเองได้ทุกก้อน — ถ้าได้ผลไม่ตรง มาบอกผมได้เลย
1 · React ทำอะไร แล้ว Next.js ทำอะไรเพิ่ม
เริ่มจากของที่คุณรู้จักอยู่แล้ว: เบราว์เซอร์รู้จักแค่ HTML · CSS · JavaScript
ตอนเรียน HTML/CSS คุณเขียนหน้าเว็บด้วยมือทุกแท็ก อยากได้ปุ่ม 20 ปุ่มก็ก็อป
<button> ยี่สิบรอบ แก้ทีต้องไล่แก้ยี่สิบที่
React คือไลบรารีที่แก้ปัญหานั้นข้อเดียว — มันให้คุณเขียน "ของ" หนึ่งชิ้น
แล้วเอาไปวางกี่ที่ก็ได้ พร้อมกับคอยอัปเดตหน้าจอให้เองเมื่อข้อมูลเปลี่ยน
แต่ React ทำแค่นั้นจริง ๆ · React ไม่รู้จักคำว่า "หน้าเว็บ" ไม่รู้จัก URL
ไม่รู้ว่าจะรวมไฟล์ยังไงให้เบราว์เซอร์โหลดได้ ไม่รู้ว่าจะเอา HTML ออกมาจากเซิร์ฟเวอร์ยังไง
ของพวกนั้นคุณต้องหามาต่อเอง
Next.js คือกล่องที่เอา React มาใส่ไว้ แล้วแถมของที่ขาดมาให้ครบ
สไลด์หน้า 6 เขียนไว้ตรง ๆ ว่า "A popular web development framework built on top of React"
และหน้าแรกของ nextjs.org พาดหัวว่า "The React Framework for the Web"
คำสำคัญคือ built on top of — Next.js ไม่ได้มาแทน React
โค้ด React ที่คุณเขียนใน Next.js ก็คือโค้ด React อันเดิม แค่มีคนจัดโต๊ะให้เรียบร้อยแล้ว
React คือเครื่องยนต์ · มันหมุนได้จริง แรงจริง แต่เครื่องยนต์เปล่า ๆ วางบนพื้น
คุณขับไปไหนไม่ได้ ต้องหาแชสซี พวงมาลัย ล้อ ถังน้ำมัน ไฟหน้า มาประกอบเอง
และแต่ละชิ้นก็ต้องเลือกยี่ห้อเองว่าจะเข้ากันไหม
Next.js คือรถทั้งคันที่ประกอบมาแล้ว โดยใช้เครื่องยนต์ตัวนั้นแหละ
เปิดประตูขึ้นนั่งแล้วขับได้เลย
ที่ไม่เหมือนกัน: ซื้อรถมาแล้วคุณถอดเครื่องออกไปใช้เดี่ยว ๆ ไม่ได้
แต่ในโลกโค้ด React ยังอยู่ตรงนั้นให้คุณเรียกใช้ตรง ๆ ได้ทุกบรรทัด
— ใน Next.js คุณ import { useState } from 'react' ได้ตามปกติ
ไม่มีอะไรถูกซ่อน · และอีกจุด: รถประกอบมาแล้วเปลี่ยนล้อได้ง่าย ๆ
แต่ Next.js มีความเห็น ว่าโฟลเดอร์ต้องชื่ออะไร ไฟล์ต้องชื่ออะไร
ถ้าคุณอยากทำนอกกรอบนั้น คุณจะเหนื่อยกว่าเขียน React เปล่า ๆ
ตารางที่ต้องจำจากบทนี้ — อะไรต้องทำเอง อะไรได้ฟรี
นี่คือใจความทั้งหมดของบท ถ้าจำได้ตารางเดียวก็ถือว่าคุ้มแล้ว
คอลัมน์ซ้ายคือ "ถ้าคุณตั้งโปรเจกต์ React เปล่า ๆ เอง" คอลัมน์ขวาคือ "ถ้าคุณใช้ Next.js"
| งานที่หน้าเว็บหนึ่งหน้าต้องมี | React เปล่า ๆ | Next.js |
|---|---|---|
| วาด UI จากข้อมูล · อัปเดตหน้าจอเมื่อข้อมูลเปลี่ยน | ✅ ได้ฟรี — นี่คืองานของ React แท้ ๆ | ✅ ได้ฟรี — เพราะมันคือ React ตัวเดียวกัน |
Routing — URL /about ต้องไปโผล่หน้าไหน |
❌ ต้องลงไลบรารีเพิ่มเอง (เช่น React Router) แล้วเขียนตารางเส้นทางเอง | ✅ ได้จากชื่อโฟลเดอร์ — สร้าง app/about/page.tsx แล้ว /about ใช้ได้ทันที ไม่ต้องประกาศที่ไหนอีก |
Bundling — รวมไฟล์ .tsx หลายสิบไฟล์ให้เบราว์เซอร์โหลดได้ |
❌ ต้องเลือกและตั้งค่า bundler เอง (Vite / webpack) + ตั้งค่า TypeScript เอง | ✅ มาให้แล้ว (เวอร์ชันนี้ใช้ Turbopack) · npx next dev คำสั่งเดียวจบ |
| Server rendering — ให้ HTML ที่มีเนื้อหาจริงออกจากเซิร์ฟเวอร์ | ❌ เบราว์เซอร์ได้ <div id="root"></div> เปล่า ๆ แล้วรอ JS วาดทีหลัง |
✅ เป็นค่าเริ่มต้น — พิสูจน์ในหัวข้อ 5 ด้วย curl |
| Route Handler — เขียน API endpoint ในโปรเจกต์เดียวกัน | ❌ ต้องแยกโปรเจกต์ backend ต่างหาก (แบบ Go ที่คุณเขียนมา) | ✅ วางไฟล์ route.ts ในโฟลเดอร์ ก็เป็น endpoint แล้ว (สไลด์หน้า 7) |
| ปรับรูป · ฟอนต์ · สคริปต์ ให้โหลดเร็ว | ❌ ต้องหา plugin มาต่อเอง หรือทำมือ | ✅ สไลด์หน้า 7 เรียกว่า Build-in Optimizations — มีมาให้ในกล่อง |
อ่านตารางนี้ยังไงให้ถูก — ช่อง ❌ ไม่ได้แปลว่า React ทำไม่ได้
ทุกช่องทำได้หมด แค่คุณต้องเป็นคนเลือกเครื่องมือและต่อสายเอง
นี่คือความต่างระหว่างคำว่า library กับ framework ซึ่งจะติดป้ายให้ในกล่องถัดไป
- library (ไลบรารี)
- กองเครื่องมือที่คุณเป็นคนเรียกใช้ — คุณคุมลำดับ คุณคุมโครงสร้างโปรเจกต์
· React คือ library
ถาม senior ได้ว่า "ที่นี่ใช้ React เปล่า ๆ หรือใช้ framework ครอบอยู่ครับ" - framework (เฟรมเวิร์ก)
- กรอบที่มันเป็นคนเรียกโค้ดคุณ — มันกำหนดว่าไฟล์ต้องอยู่ตรงไหน ชื่ออะไร
แล้วมันไปหยิบมาใช้เอง · Next.js คือ framework
พิมพ์บอก AI ได้ว่า "อธิบายให้หน่อยว่า Next.js เป็น framework ต่างจาก React ที่เป็น library ตรงไหนบ้าง ขอเป็นข้อ ๆ" - App Router
- ระบบจัดเส้นทางแบบใหม่ของ Next.js ที่อ่านโครงโฟลเดอร์
app/เป็นแผนที่ URL (ของเก่าชื่อ Pages Router ใช้โฟลเดอร์pages/— เจอในบทความเก่า ๆ เยอะมาก)
ถาม senior ได้ว่า "โปรเจกต์นี้เป็น App Router หรือ Pages Router ครับ" — คำตอบเปลี่ยนวิธีเขียนแทบทุกอย่าง ถามก่อนเสมอ - SSR / Server-Side Rendering
- การให้ เซิร์ฟเวอร์วาด HTML เสร็จก่อนส่งให้เบราว์เซอร์
แทนที่จะส่งหน้าเปล่าไปให้ JavaScript วาดเอาเอง
พิมพ์บอก AI ได้ว่า "หน้านี้ควรเป็น server render หรือ client render ดี เพราะอะไร"
2 · "component" คืออะไร เทียบกับ func ที่คุณเขียนใน Go
สไลด์หน้า 10 นิยามไว้ประโยคเดียว: "It takes types props, manages typed state, and returns JSX."
ตัดศัพท์ออกให้หมดแล้วเหลือแค่นี้ — component คือฟังก์ชันธรรมดา ๆ ที่คืน "หน้าตา" แทนที่จะคืนข้อมูล
เทียบกับของที่คุณเขียนมาแล้วเก้าวัน:
| ฟังก์ชันใน Go | component ใน React | |
|---|---|---|
| รับอะไรเข้า | พารามิเตอร์ที่มี type | props — ก็คือพารามิเตอร์ที่มี type เหมือนกัน |
| คืนอะไรออก | ข้อมูล — string, error, struct | JSX — คำอธิบายหน้าตาของ UI |
| ใครเรียกมัน | คุณเรียกเอง Greeting("DevPool") | React เรียกให้ คุณแค่วางแท็ก <Greeting name="DevPool" /> |
| เรียกกี่ครั้ง | เท่าที่คุณเขียนไว้ | เรียกใหม่ทุกครั้งที่ข้อมูลเปลี่ยน — จุดนี้ต่างที่สุด |
แถวสุดท้ายคือแถวที่ทำให้คนมาจากฝั่ง backend งงที่สุด
ใน Go คุณเรียก CalculateBill() หนึ่งครั้ง ได้ผลลัพธ์ แล้วจบ
ใน React ฟังก์ชันเดียวกันนั้นถูกเรียกซ้ำแล้วซ้ำอีก ทุกครั้งที่ข้อมูลขยับ
แล้ว React ก็เอาผลลัพธ์รอบใหม่ไปเทียบกับรอบก่อน แล้วแก้หน้าจอเฉพาะจุดที่ต่าง
เพราะแบบนี้ component จึงต้องให้ผลเหมือนเดิมเสมอเมื่อ input เหมือนเดิม —
ซึ่งก็คือ pure function ที่คุณเพิ่งเรียนไปในบท 65–67 นั่นแหละ ไม่ใช่ของใหม่
โค้ดในสไลด์หน้า 10 — และเหตุผลที่มันคอมไพล์ไม่ผ่านกับของจริง
สไลด์เขียนโค้ดนี้ไว้ (ผมพิมพ์ตามที่เห็นในสกรีนช็อตทุกตัวอักษร):
// components/t69/Greeting.tsx — ตามสไลด์หน้า 10 เป๊ะ ๆ
function Greeting(
{ name }: { name: string }
): JSX.Element {
return <h1>Hello, {name}</h1>;
}
โปรเจกต์ที่เราใช้เป็น React 19.2.8 + TypeScript 5.9.3
รัน npx tsc --noEmit แล้วคิดว่าได้อะไร?
ไม่ผ่าน ได้ error จริงบรรทัดนี้:
components/t69/Greeting.tsx(3,4): error TS2503: Cannot find namespace 'JSX'.
สาเหตุ: React 19 เอา JSX ที่เป็นชื่อระดับ global ออกไปแล้ว
สมัย React 17–18 แค่ import React เข้ามา ชื่อ JSX ก็ลอยอยู่ให้ใช้ทั่วไฟล์
พอ React 19 ย้ายมันไปอยู่ใต้ React ชื่อลอย ๆ ตัวนั้นก็หายไป
TypeScript เลยหาไม่เจอ
วิธีแก้ที่สั้นที่สุด — import ชื่อนั้นเข้ามาเอง (ผมรันแล้ว ผ่านจริง exit=0):
import type { JSX } from "react"; // ← บรรทัดที่สไลด์ไม่มี
function Greeting(
{ name }: { name: string }
): JSX.Element {
return <h1>Hello, {name}</h1>;
}
export default Greeting;
หรือจะลบ : JSX.Element ทิ้งไปเลยก็ได้ — และนี่คือสิ่งที่คนส่วนใหญ่ทำจริง
เพราะ TypeScript เดา return type ให้เองอยู่แล้วจากตัว return
เขียนกำกับไว้ก็แค่ให้คนอ่านสบายใจ ไม่ได้เพิ่มความปลอดภัยอะไร
⚠️ สไลด์หน้า 10 กับของจริงไม่ตรงกัน — และจะเจอแบบนี้อีกเรื่อย ๆ
สไลด์เขียนถูกตามยุคของมัน แต่โปรเจกต์ที่ create-next-app สร้างให้วันนี้ได้ React 19
ซึ่งย้ายของชิ้นนี้ไปแล้ว
บทเรียนที่แพงกว่าตัว error: เวลาก็อปโค้ดจากสไลด์ · บล็อก · หรือคำตอบของ AI มาแล้วมันแดง
คำถามแรกที่ควรถามคือ "อันนี้เขียนไว้ตอน React เวอร์ชันอะไร" ไม่ใช่ "ผมพิมพ์ผิดตรงไหน"
เช็คของตัวเองได้ด้วย node -e "console.log(require('react/package.json').version)"
- component (คอมโพเนนต์)
- ฟังก์ชันที่คืน UI · ชื่อต้องขึ้นต้นด้วยตัวพิมพ์ใหญ่ (เหตุผลอยู่ในหัวข้อ 6)
ถาม senior ได้ว่า "อันนี้ควรแตกเป็น component ใหม่ หรือรับ prop เพิ่มดีครับ" — เป็นคำถามที่ senior ตอบได้ทันทีและสอนอะไรได้เยอะ - props
- ย่อจาก properties · คือพารามิเตอร์ของ component ส่งเข้าไปทางแอตทริบิวต์ของแท็ก
· ในฝั่ง component มันมาถึงเป็น object ก้อนเดียว เลยเห็นเขียน
{ name }แกะออกมาแบบนั้น
พิมพ์บอก AI ได้ว่า "ช่วยเขียน type ของ props ให้ component นี้หน่อย" - JSX
- ไวยากรณ์ที่ให้เขียนแท็กแบบ HTML ปนลงไปในไฟล์ TypeScript ได้
· มันไม่ใช่ HTML และไม่ใช่ string — ตอน build มันถูกแปลงเป็นการเรียกฟังก์ชันธรรมดา
ถาม senior ได้ว่า "JSX ตรงนี้กลายเป็นโค้ดอะไรตอน build ครับ" - type inference
- ความสามารถของ TypeScript ที่เดาชนิดข้อมูลให้เองโดยไม่ต้องเขียนกำกับ
— เหตุผลที่ลบ
: JSX.Elementทิ้งได้แล้วยังปลอดภัยเท่าเดิม
(คำนี้คุณเจอมาแล้วในบท 63 · ที่นี่มันกลับมาใช้ได้พอดี)
3 · ทำไม component ถึงเอากลับมาใช้ซ้ำได้
คำตอบสั้นมาก: เพราะมันเป็นฟังก์ชัน · ฟังก์ชันเรียกซ้ำได้ด้วยอาร์กิวเมนต์คนละชุด component ก็เหมือนกันเป๊ะ แค่ "อาร์กิวเมนต์" เขียนอยู่ในแท็ก
ผมเขียนไฟล์นี้ไว้จริงในโปรเจกต์ — สิบสี่บรรทัด เขียนครั้งเดียว:
// components/t69/PriceTag.tsx
import type { JSX } from "react";
type PriceTagProps = {
label: string;
baht: number;
};
export default function PriceTag({ label, baht }: PriceTagProps): JSX.Element {
return (
<p>
{label}: {baht.toLocaleString("th-TH")} บาท
</p>
);
}
แล้วเรียกใช้สามครั้งด้วยข้อมูลคนละชุด:
// app/t69/page.tsx
import Greeting from "@/components/t69/Greeting";
import PriceTag from "@/components/t69/PriceTag";
export default function Page() {
return (
<main>
<Greeting name="DevPool" />
<PriceTag label="ค่าไฟเดือนนี้" baht={1250} />
<PriceTag label="ค่าน้ำเดือนนี้" baht={340} />
<PriceTag label="ค่าเน็ตเดือนนี้" baht={599} />
</main>
);
}
HTML ที่ออกมาจริง (ผม curl มาแล้วตัดเอาเฉพาะ <main>)
ของจริงเป็นบรรทัดเดียวยาว ๆ ผมแค่กด Enter คั่นให้อ่านง่าย
ไม่ได้ตัดตัวอักษรตัวไหนออกเลย — รวมทั้งของแปลก ๆ ที่คุณกำลังจะเห็น:
<main><h1>Hello, <!-- -->DevPool</h1>
<p>ค่าไฟเดือนนี้<!-- -->: <!-- -->1,250<!-- --> บาท</p>
<p>ค่าน้ำเดือนนี้<!-- -->: <!-- -->340<!-- --> บาท</p>
<p>ค่าเน็ตเดือนนี้<!-- -->: <!-- -->599<!-- --> บาท</p></main>
⚠️ อะไรคือ <!-- --> ที่โผล่มาเต็มไปหมด
ผมเกือบลบมันทิ้งเพื่อให้บทอ่านง่าย แล้วนึกได้ว่าถ้าลบ คุณจะรันตามแล้วเห็นไม่ตรงกับบท
มันคือ คอมเมนต์ HTML ว่าง ๆ ที่ React ใส่เองตอน server render
ทุกครั้งที่ข้อความสองก้อนมาต่อกัน — ในที่นี้คือ {label} · : ·
{baht...} · บาท สี่ก้อนติดกัน
มันมีไว้บอกเบราว์เซอร์ว่า "ตรงนี้เป็นข้อความคนละก้อนนะ อย่าเผลอเชื่อมกัน"
เพื่อให้ตอน JavaScript โหลดเสร็จแล้วมาต่อยอด (hydrate) React นับก้อนได้ตรงกับตอนที่มันวาดไว้
คนอ่านมองไม่เห็นมันเลย เพราะคอมเมนต์ไม่แสดงผล ·
ถ้าคุณ curl แล้วไม่เจอ แปลว่าหน้านั้นไม่ได้ผ่านมือ React
— ตัวมันเองก็เลยเป็นหลักฐานอย่างหนึ่ง
สังเกตเครื่องหมายจุลภาคใน 1,250 — ผมไม่ได้พิมพ์มันลงไป
toLocaleString("th-TH") เป็นคนใส่ให้ · แปลว่าโค้ดจัดรูปแบบตัวเลข
เขียนที่เดียวใน PriceTag.tsx แล้วมีผลกับทั้งสามบรรทัด
ถ้าพรุ่งนี้อยากเปลี่ยนเป็น ฿1,250 คุณแก้ไฟล์เดียว ไม่ต้องไล่หาสามที่
— นี่คือ "ใช้ซ้ำได้" ที่แปลว่าอะไรจริง ๆ ไม่ใช่แค่ประหยัดการพิมพ์
"Everything is component"
สไลด์หน้า 11 ทั้งหน้ามีข้อความเดียว: "Everything is component" แล้วหน้า 12 ก็เอาสกรีนช็อต Outlook มาตีกรอบสีทับทุกส่วน — แถบบนหนึ่งกรอบ · เมนูซ้ายหนึ่งกรอบ · รายการเมลหนึ่งกรอบ · เมลแต่ละฉบับในรายการอีกกรอบซ้อนเข้าไป
หน้าเว็บหนึ่งหน้า ไม่ได้มองเป็น "หน้า" แต่มองเป็นต้นไม้ของกล่อง
<Page>
├── <TopBar /> ← กล่องเดียว ใช้ทุกหน้า
├── <Sidebar>
│ ├── <FolderItem name="Inbox" count={517} /> ┐
│ ├── <FolderItem name="Junk Email" count={2} /> ├ ไฟล์เดียวกัน
│ └── <FolderItem name="Drafts" count={0} /> ┘ ใช้ 3 ครั้ง
└── <MailList>
├── <MailRow ... /> ┐
├── <MailRow ... /> ├ ไฟล์เดียวกัน จะมี 5 หรือ 500 แถวก็ได้
└── <MailRow ... /> ┘
ประโยคนี้ฟังดูเวอร์ แต่มันเป็นวิธีมองงาน ไม่ใช่คำโฆษณา
เวลาได้ดีไซน์มาแผ่นหนึ่ง งานแรกไม่ใช่ลงมือเขียน HTML —
งานแรกคือลากกรอบทับดีไซน์ว่าอะไรซ้ำบ้าง ของที่ซ้ำคือ component
ส่วนที่ต่างกันระหว่างของซ้ำ ๆ นั้นคือ props
ในตัวอย่างข้างบน FolderItem ต่างกันแค่ชื่อกับตัวเลข — สองอย่างนั้นจึงเป็น props
4 · Routing มาจากโฟลเดอร์ — พิสูจน์
สไลด์หน้า 7 เรียกว่า "Create routes using the file system"
แปลว่า ไม่มีไฟล์ตารางเส้นทางให้แก้ · ที่อยู่ของไฟล์คือ URL
ผมสร้างแค่โฟลเดอร์ app/t69/ แล้ววางไฟล์ page.tsx ลงไปไฟล์เดียว
ไม่ได้ไปประกาศอะไรเพิ่มที่ไหนเลย แล้วยิงคำสั่งนี้:
$ curl -s -o /dev/null -w "%{http_code}\n" http://localhost:PORT/t69
200
$ curl -s -o /dev/null -w "%{http_code}\n" http://localhost:PORT/t69/settings
404
/t69 ได้ 200 เพราะมีโฟลเดอร์ · /t69/settings ได้ 404
เพราะไม่มีโฟลเดอร์ settings ข้างใน · แค่นั้นเลย ไม่มีความลับอะไรมากกว่านี้
ตอนสั่ง npx next build Next.js พิมพ์ตารางเส้นทางที่มันหาเจอออกมาให้ดูด้วย:
▲ Next.js 16.3.4 (Turbopack)
✓ Compiled successfully in 358ms
Route (app)
┌ ○ /
├ ○ /_not-found
├ ○ /t69
└ ...
○ (Static) prerendered as static content
หมายเหตุความซื่อสัตย์ — โปรเจกต์ทดสอบนี้มีบทอื่นใช้ร่วมกันอยู่
ตารางจริงจึงยาวกว่านี้ (มี /t70, /t71, … ของบทถัด ๆ ไป)
ผมตัดเหลือเฉพาะบรรทัดที่เกี่ยวกับบทนี้ ไม่ได้แต่งบรรทัดขึ้นมาใหม่
ด้วยเหตุผลเดียวกัน เลขพอร์ตในคำสั่ง curl ข้างบนผมเขียนเป็น PORT —
เพราะพอร์ตจริงตอนผมรันถูกจองไว้โดยเซิร์ฟเวอร์ของบทอื่น ตัวเลขนั้นไม่มีความหมายกับคุณ
5 · "Server rendering" คืออะไร — พิสูจน์ด้วยไฟล์ที่มันทิ้งไว้
บรรทัดสุดท้ายของตารางข้างบนเขียนว่า ○ (Static) prerendered as static content
คำว่า prerendered แปลว่า "วาดเสร็จไว้ล่วงหน้าแล้ว"
และมันไม่ใช่คำโฆษณา — มันทิ้งไฟล์ไว้ให้ดูจริง ๆ
$ ls .next/server/app/ | grep t69
t69
t69.html
t69.meta
t69.rsc
t69.segments
$ cat .next/server/app/t69.html # (ตัดเอาเฉพาะ <main>)
<main><h1>Hello, <!-- -->DevPool</h1><p>ค่าไฟเดือนนี้<!-- -->: <!-- -->1,250<!-- --> บาท</p>…</main>
หยุดตรงนี้แล้วดูให้ดี: ข้อความ "Hello, DevPool" อยู่ในไฟล์ .html จริง ๆ
ตั้งแต่ตอน build เสร็จ ก่อนที่จะมีเบราว์เซอร์ตัวไหนเปิดหน้านี้ด้วยซ้ำ
ถ้าเป็น React เปล่า ๆ ตามท่ามาตรฐาน สิ่งที่อยู่ในไฟล์ HTML จะเป็น
<div id="root"></div> ว่าง ๆ แล้วรอ JavaScript โหลดเสร็จค่อยวาด
Next.js = ร้านข้าวแกง · กับข้าวทำเสร็จตั้งถาดรอไว้ตั้งแต่เช้า
ลูกค้าเดินเข้ามาปุ๊บ ตักใส่จานเสร็จใน 10 วินาที — เห็นของทันที
React เปล่า ๆ = ร้านตามสั่ง · ลูกค้าเข้ามาเจอครัวว่างเปล่า
สั่งแล้วนั่งรอให้ผัดให้ดู — ได้ของร้อนกว่า แต่ช่วงแรกลูกค้ามองเห็นแค่จานเปล่า
คนที่รอไม่ไหวคือ Google — บอตของมันมักไม่นั่งรอให้ผัดเสร็จ ก็เลยเก็บได้แต่จานเปล่าไปทำ SEO
ที่ไม่เหมือนกัน: ข้าวแกงตั้งไว้นานแล้วเย็น แต่หน้าเว็บที่ prerender ไว้
ไม่ได้ค้างอยู่แบบนั้นถาวร — Next.js มีวิธีสั่งให้ทำใหม่ตามรอบเวลา
(สไลด์หน้า 7 เรียกว่า Incremental Static Regeneration / ISR)
และที่สำคัญกว่านั้น: มันไม่ได้เลือกอย่างใดอย่างหนึ่งทั้งหน้า —
หน้าเดียวกันผสมได้ ส่วนที่นิ่งก็ตั้งถาดไว้ ส่วนที่ต้องกดได้ก็ผัดสด
ตรงนี้คือเรื่องของ 'use client' ซึ่งจะเจอเต็ม ๆ ในบทถัด ๆ ไป
— บทนี้แค่ให้รู้ว่ามันมีอยู่
6 · กับดักแรกที่จะเจอ: ตัวพิมพ์ใหญ่ตัวเดียว
ในเมื่อ component เป็นแค่ฟังก์ชัน แล้ว React รู้ได้ยังไงว่า <PriceTag />
คือ component ของเรา ส่วน <p> คือแท็ก HTML?
คำตอบคือมันดูตัวอักษรตัวแรก ตัวพิมพ์ใหญ่ = component ของคุณ ·
ตัวพิมพ์เล็ก = แท็ก HTML ที่เบราว์เซอร์รู้จัก
// app/t69/page.tsx — ผมเปลี่ยนแค่ตัว P เป็นตัวเล็ก
import PriceTag from "@/components/t69/PriceTag";
const priceTag = PriceTag; // ← ของอันเดียวกันเป๊ะ แค่ชื่อขึ้นต้นตัวเล็ก
export default function Page() {
return (
<main>
<priceTag label="ค่าไฟเดือนนี้" baht={1250} />
</main>
);
}
ตัวแปร priceTag ชี้ไปที่ฟังก์ชันเดียวกันเป๊ะ ไม่มีอะไรเปลี่ยนเลยนอกจากตัวอักษรตัวเดียว
รัน npx tsc --noEmit แล้วคิดว่าได้อะไร?
พังทันที — นี่คือผลรันจริง:
app/t69/page.tsx(9,7): error TS2339: Property 'priceTag' does not exist on type 'JSX.IntrinsicElements'.
อ่านคำว่า JSX.IntrinsicElements ให้ออกแล้วจะเข้าใจทั้งหมด —
มันคือรายชื่อแท็ก HTML ที่มีอยู่จริงในโลก (div, p, h1, …)
พอเจอชื่อขึ้นต้นด้วยตัวเล็ก JSX ไม่ได้มองว่านี่คือตัวแปรของคุณ
มันไปเปิดรายชื่อแท็ก HTML แทน แล้วรายงานว่าไม่มีแท็กชื่อ priceTag ในโลกนี้
ทำไมต้องรู้เรื่องนี้ตั้งแต่วันแรก: ข้อความ error ไม่มีคำว่า
"ตัวพิมพ์ใหญ่" อยู่เลยสักคำ · ถ้าไม่เคยเจอมาก่อน คุณจะไปนั่งไล่ดู props
ไล่ดู import ไล่ดู type ทั้งที่คำตอบคือกด Shift ตัวเดียว
และถ้าเขียนเป็น .jsx ธรรมดาที่ไม่มี TypeScript คอยจับให้
มันจะไม่ error เลยด้วยซ้ำ — เบราว์เซอร์จะได้แท็กประหลาดที่ไม่แสดงผลอะไร
แล้วคุณจะนั่งงงว่าทำไมหน้าว่าง · TypeScript ช่วยคุณไว้ตรงนี้
✅ กฎที่ปลอดภัยเสมอ — ตั้งชื่อไฟล์ component ให้ตรงกับชื่อ component
และขึ้นต้นด้วยตัวพิมพ์ใหญ่ทั้งคู่: PriceTag.tsx ข้างในมี PriceTag
ทำแบบนี้แล้วคุณจะไม่มีวันเจอ TS2339 ตัวนี้ เพราะชื่อที่ import มาก็เป็นตัวใหญ่อยู่แล้วโดยอัตโนมัติ
7 · สรุปเป็นสามประโยค
React = วิธีเขียน UI เป็นชิ้น ๆ ที่ประกอบกันได้ (ไลบรารี) Next.js = React + routing + rendering + bundling (เฟรมเวิร์ก) component = ฟังก์ชันที่รับ props แล้วคืน JSX (ไม่ใช่ของใหม่ — มันคือฟังก์ชัน)
บทหน้าเราจะลงมือติดตั้งจริง แล้วกางโครงโฟลเดอร์ที่ create-next-app สร้างให้ดูทีละไฟล์
ว่าอันไหนแตะได้ อันไหนอย่าเพิ่งแตะ
บทนี้ยังไม่ต้องติดตั้งอะไร ภารกิจจึงแบ่งเป็นสองส่วน: ส่วนที่ทำได้เดี๋ยวนี้ กับ ส่วนที่ทำหลังจบบท 70
-
ทำได้เดี๋ยวนี้ — ฝึกสายตา: เปิดเว็บที่คุณใช้ทุกวัน (Facebook · YouTube ·
หรือหน้า index ของคอร์สนี้เอง) แล้วเขียนลงกระดาษว่ามันมีกี่ component
ตอบสามข้อนี้ให้ได้: (ก) อันไหนที่ปรากฏซ้ำมากกว่า 3 ครั้งในหน้าเดียว · (ข) ของซ้ำนั้นอะไรที่ต่างกัน (นั่นคือ props) · (ค) อันไหนที่ปรากฏทุกหน้าไม่ว่าคุณจะกดไปไหน (นั่นคือของที่จะไปอยู่ใน layout — เจอในบท 70) -
ทำได้เดี๋ยวนี้ — ตรวจเวอร์ชันเป็น: ถ้าเครื่องคุณมี Node อยู่แล้ว รัน
node --versionแล้วจดไว้
นิสัยนี้จะช่วยคุณตลอดคอร์ส — เพราะทุกครั้งที่โค้ดจากอินเทอร์เน็ตพัง คำถามแรกคือ "เวอร์ชันตรงกันไหม" อย่างที่เห็นในหัวข้อ 2 -
หลังจบบท 70 (ติดตั้งเสร็จแล้ว) — พิสูจน์ตารางในหัวข้อ 1 ด้วยมือตัวเอง:
ก. สร้างcomponents/PriceTag.tsxตามโค้ดในหัวข้อ 3 แล้วเรียกใช้ 3 ครั้ง ด้วยข้อมูลคนละชุด
ข. ตรวจ type ก่อน:npx tsc --noEmitต้องได้ผลว่าไม่มี error
ค. พิสูจน์ว่า routing มาจากโฟลเดอร์จริง: รันnpx next devแล้วcurl -o /dev/null -w "%{http_code}\n"ยิงไปที่ path ที่มีโฟลเดอร์ กับ path ที่ไม่มี — ต้องได้200กับ404
ง. พิสูจน์ว่ามัน render ที่ server จริง: รันnpx next buildแล้วเปิด.next/server/app/<ชื่อ route>.htmlขึ้นมาอ่าน
เกณฑ์ผ่าน: คุณต้องเห็นข้อความภาษาไทยของคุณเองอยู่ในไฟล์.htmlนั้น ถ้าเห็นแล้ว แปลว่าคุณพิสูจน์แถว "Server rendering" ในตารางหัวข้อ 1 ด้วยตัวเองเรียบร้อย
จ. ทำให้มันพังเพื่อพิสูจน์ว่าตัวตรวจทำงาน: เปลี่ยน<PriceTag>เป็น<priceTag>หนึ่งที่ แล้วรันnpx tsc --noEmitซ้ำ — ต้องได้TS2339· ถ้าไม่ได้ error แปลว่าคุณตรวจผิดไฟล์ ให้ย้อนไปดูใหม่
เช็คความเข้าใจ
1. เพื่อนบอกว่า "โปรเจกต์นี้ใช้ Next.js เลยไม่ได้ใช้ React แล้ว" — ควรตอบว่าอะไร?
สไลด์หน้า 6 เขียนไว้ตรง ๆ ว่า built on top of React และ nextjs.org พาดหัวว่า
"The React Framework for the Web" — Next.js คือ React ที่มีคนจัดโต๊ะให้
ไม่ใช่ของที่มาแทน
ในโปรเจกต์จริงคุณ import { useState } from 'react' ได้ตามปกติ
และ package.json ของ sandbox ที่ใช้ตรวจบทนี้ก็มี react: 19.2.8 อยู่ในรายการ dependency ตรง ๆ
ตัวเลือกที่ 1 ผิดสองชั้น — Next.js ไม่ใช่ภาษา (มันคือ package บน npm) และไม่ได้มาแทน React
ตัวเลือกที่ 3 ผิดตรง "ไม่เกี่ยวกับตอนทำงานจริง" — Next.js ทำงานตอนรันจริงด้วย
มันคือตัวที่ render หน้าเว็บฝั่ง server ให้ ไม่ใช่แค่เครื่องมือ build
ตัวเลือกที่ 4 ผิดตรงไวยากรณ์ — โค้ดในบทนี้ทุกก้อนเขียนด้วย JSX ของ React มาตรฐาน
2. ในโปรเจกต์ Next.js ที่ใช้ App Router อยากให้ URL /report เปิดได้ ต้องทำอะไร?
สไลด์หน้า 7 เรียกความสามารถนี้ว่า "Create routes using the file system" —
ที่อยู่ของไฟล์คือ URL ไม่มีตารางเส้นทางให้แก้
ในบทนี้ผมสร้างแค่โฟลเดอร์ app/t69/ กับไฟล์ page.tsx ไฟล์เดียว
แล้ว curl /t69 ได้ 200 ส่วน curl /t69/settings ได้ 404
เพราะไม่มีโฟลเดอร์ settings — ไม่ได้ประกาศอะไรเพิ่มเลยสักบรรทัด
ตัวเลือกที่ 1 ผิด — next.config.ts ของโปรเจกต์นี้ไม่ได้ตั้งค่าอะไรไว้เลย
ทั้งไฟล์มีแค่ const nextConfig: NextConfig = { /* config options here */ };
แล้ว export default ออกไป — ไม่มีตารางเส้นทางสักบรรทัด แต่ /t69 ก็ยังใช้ได้
ตัวเลือกที่ 2 คือท่าของ React เปล่า ๆ ตามคอลัมน์กลางของตารางในหัวข้อ 1 —
ถูกสำหรับ React แต่ผิดสำหรับ Next.js
ตัวเลือกที่ 4 ได้ไฟล์ HTML นิ่ง ๆ ที่ไม่ใช่ component และไม่มี React อยู่ในนั้นเลย
3. เอาโค้ดจากสไลด์มาวางแล้วได้ TS2503: Cannot find namespace 'JSX' — ควรทำอะไรก่อน?
error ตัวนี้ผมเจอจริงตอนรัน npx tsc --noEmit กับโค้ดสไลด์หน้า 10 แบบไม่แก้อะไรเลย
สาเหตุคือ React 19 ย้ายชื่อ JSX ออกจาก global ไปไว้ใต้ React
โค้ดที่เขียนไว้สมัย React 17–18 จึงหาไม่เจอ
แก้ได้ด้วยการเติม import type { JSX } from "react"; หรือลบ : JSX.Element ทิ้งไปเลย
ตัวเลือกที่ 1 คือการปิดตัวช่วย — และทำให้แย่ลงจริง ๆ
เพราะกับดักตัวพิมพ์เล็กในหัวข้อ 6 จะเงียบสนิทถ้าไม่มี TypeScript
ตัวเลือกที่ 2 คือกับดักที่กินเวลามากที่สุด — สไลด์กับเครื่องคุณคนละเวอร์ชันกันได้เสมอ
ตัวเลือกที่ 3 เป็นท่าที่คนชอบทำเวลาจนตรอก แต่ที่นี่ node_modules ไม่ได้พัง
มันติดตั้งเวอร์ชันที่ถูกต้องตามที่ขอไว้แล้ว — ลบแล้วลงใหม่ก็ได้ error เดิม เสียเวลาเปล่า
4. curl ไปที่หน้า Next.js แล้วเห็นข้อความจริงอยู่ใน HTML ที่ได้กลับมา แปลว่าอะไร?
curl ไม่รัน JavaScript — มันขอไฟล์มาแล้วพิมพ์ตามที่ได้ ตรงไปตรงมา
ดังนั้นอะไรที่เห็นใน curl คือสิ่งที่เซิร์ฟเวอร์ส่งมาจริง ก่อน JS ทำงาน
ในบทนี้ผม curl แล้วได้ข้อความ Hello, DevPool ในแท็ก <h1>
มาเต็ม ๆ (มี <!-- --> ของ React คั่นอยู่ตามที่เห็นในหัวข้อ 3)
และไฟล์ .next/server/app/t69.html ที่ build ทิ้งไว้ก็มีข้อความเดียวกัน
ตัวเลือกที่ 2 ผิดที่ใจกลาง — ถ้า curl รัน JS ได้ การทดสอบนี้ก็จะพิสูจน์อะไรไม่ได้เลย
ความที่มันไม่รัน JS นั่นแหละคือเหตุผลที่ใช้มันเป็นหลักฐานได้
ตัวเลือกที่ 3 กลับหัวกลับหาง — HTML ที่ว่างเปล่าคือลักษณะของ React เปล่า ๆ ที่ไม่มี server rendering
ส่วนหน้านี้ใช้ React และมีเนื้อหาครบ เพราะ Next.js วาดให้ที่ server
ตัวเลือกที่ 4 ผิด — โค้ดต้นทางคือ <PriceTag> สามตัวที่รับ props คนละชุด
แถวที่ออกมาเป็น <p> ก็เพราะ component วาดให้ ไม่มีใครพิมพ์ HTML นั้นด้วยมือ
💬 ติดตรงไหนถามได้ — ถ้ารันตามในบทแล้วได้ผลไม่ตรงกับที่เขียนไว้
ให้ส่งมาทั้งโค้ดที่รัน · คำสั่งที่ใช้ · ข้อความที่ขึ้นเต็ม ๆ
และคราวนี้เพิ่มอีกอย่างคือเวอร์ชันของ next กับ react
(node -e "console.log(require('react/package.json').version)")
เพราะอย่างที่เห็นในหัวข้อ 2 — ครึ่งหนึ่งของปัญหาในโลก React คือเรื่องเวอร์ชันล้วน ๆ
และคำถามที่คุ้มที่สุดที่ถามได้กับบทนี้คือ
"หน้านี้ควรให้ server วาด หรือให้เบราว์เซอร์วาดดี" — มันไม่มีคำตอบเดียว
และมันคือคำถามที่คุณจะต้องตอบซ้ำ ๆ ตลอดกลุ่มนี้
อ้างอิง
- Next.js Docs — หน้าแรกของเอกสาร — อ่านหัวข้อ What is Next.js? สามย่อหน้าอย่างเดียวพอ โดยเฉพาะประโยคที่บอกว่ามัน "automatically configures lower-level tools like bundlers and compilers" — นั่นคือแถว Bundling ในตารางหัวข้อ 1 ของบทนี้พูดด้วยคำของเจ้าของเอง
- Next.js Docs — Layouts and Pages —
อ่านหัวข้อ Creating a page ซึ่งอธิบายเรื่องเดียวกับหัวข้อ 4 ของบทนี้
คือไฟล์
page.tsxในโฟลเดอร์ไหน ก็กลายเป็น URL นั้น · เลื่อนดูตัวอย่าง nested folder ต่ออีกนิดเพื่อเดาให้ออกว่าบท 70 กำลังจะพาไปไหน - React Docs — Your First Component —
อ่านหัวข้อ Step 2: Define the function และ Pitfall กล่องสีที่อยู่ถัดลงไป
ซึ่งพูดเรื่องตัวพิมพ์ใหญ่ตัวแรกโดยตรง — เป็นที่มาของ
TS2339ในหัวข้อ 6 - React Docs — Passing Props to a Component —
อ่านย่อหน้าเปิด แล้วข้ามไปหัวข้อ Step 2: Read props inside the child component
เพื่อดูว่าทำไมถึงเขียน
{ label, baht }แกะออกมาแบบนั้นได้ (มันคือ destructuring ตัวเดียวกับที่คุณใช้ในบท 66) - React 19 Upgrade Guide —
อ่านหัวข้อ The JSX namespace in TypeScript
ซึ่งอธิบายว่าทำไมชื่อ
JSXแบบ global ถึงถูกย้ายไปไว้ใต้React.JSX— นี่คือเอกสารต้นทางของTS2503ที่คุณเจอในหัวข้อ 2