🧰 ของที่ใช้ตรวจในบทนี้ — ทุกผลรันในหน้านี้ก็อปมาจาก terminal จริง
| ของ | เวอร์ชันที่ใช้รัน |
|---|---|
next | 16.3.4 (Turbopack) |
react / react-dom | 19.2.8 |
tailwindcss | 4 (ผ่าน @tailwindcss/postcss) |
typescript | 5 |
node | v24.14.0 |
โปรเจกต์ที่ใช้ทดลองสร้างด้วย
npx create-next-app@latest --ts --tailwind --eslint --app --no-src-dir จริง
ข้อควรรู้สองอย่างเรื่องผลรันในบทนี้
(บอกไว้ตรง ๆ ดีกว่าให้คุณงงตอนรันเองแล้วเลขไม่ตรง):
- พอร์ตในผลรันของผมไม่ใช่ 3000 — โปรเจกต์ทดลองตัวนี้ถูกใช้ร่วมกันหลายบท
ผมเลยต้องรันที่พอร์ต
370(คุณจะเห็นเลขนี้ในผลcurlทุกอัน) ในเครื่องคุณค่าปริยายคือ3000 - ทุกไฟล์ที่ผมสร้างในบทนี้อยู่ใต้
app/t70/เพราะห้ามชนกับบทอื่น เวลาผมเขียนว่า "สร้างapp/about/page.tsxแล้วได้/about" ผลรันจริงของผมจะเป็นapp/t70/about/page.tsx→/t70/about
กฎเดียวกันเป๊ะ แค่มีคำว่าt70คั่นหน้า — ผมจะเทียบให้ดูเป็นตารางในหัวข้อเรื่อง URL
คำสั่งสร้างโปรเจกต์ — และจุดที่สไลด์พิมพ์ผิด
สไลด์หน้า 14 ("Create First NextJS App") ให้คำสั่งมาสามบรรทัด ตามตัวจัดการแพ็กเกจสามเจ้า:
# สามบรรทัดนี้คือสิ่งที่อยู่บนสไลด์หน้า 14 — ก็อปมาตามตัวอักษร
$ npm create-next-app@latest
$ yarn create next-app@latest
$ pnpm create next-app@latest
สังเกตไหมว่าบรรทัดแรกหน้าตาไม่เหมือนอีกสองบรรทัด —
yarn กับ pnpm มีเว้นวรรคระหว่าง create กับ next-app
แต่บรรทัดของ npm เป็น ขีดกลาง
ตอนอ่านบนสไลด์มันดูเป็นเรื่องเล็กมาก แต่ลองรันดูจริง:
$ npm create-next-app@latest
คิดว่า terminal จะตอบว่าอะไร? มันจะเริ่มถามชื่อโปรเจกต์เหมือนในสไลด์ หรือจะพังก่อน?
พังทันที ไม่ได้ต่อเน็ตด้วยซ้ำ นี่คือผลรันจริง:
Unknown command: "create-next-app@latest"
To see a list of supported npm commands, run:
npm help
npm ไม่รู้จักคำสั่งชื่อ create-next-app@latest —
เพราะมันอ่านทั้งก้อนนั้นเป็นชื่อคำสั่งเดียว
ทำไม yarn/pnpm ถูก แต่ npm ผิด ทั้งที่หน้าตาคล้ายกัน?
คำตอบอยู่ในหน้าช่วยเหลือของ npm เอง — ผลรันจริงอีกอัน:
$ npm create --help
Create a package.json file
Usage:
npm init <package-spec> (same as `npx create-<package-spec>`)
npm init <@scope> (same as `npx <@scope>/create`)
บรรทัดที่เป็นกุญแจคือ npm init <package-spec> (same as `npx create-<package-spec>`)
createคือชื่อคำสั่ง (เป็นชื่อเล่นของnpm init)next-app@latestคือของที่ส่งให้คำสั่งนั้น —npmจะเติมcreate-ให้เอง กลายเป็นแพ็กเกจcreate-next-app- เขียนติดกันเป็น
create-next-app@latest= คุณกำลังบอกว่า "รันคำสั่งชื่อนี้" ซึ่งไม่มีอยู่จริง
⚠️ สไลด์หน้า 14 บรรทัดแรกใช้ไม่ได้ — ต้องเติมเว้นวรรค
| เขียนแบบนี้ | ผลจริง |
|---|---|
npm create-next-app@latest (ตามสไลด์) | ❌ Unknown command |
npm create next-app@latest | ✅ มีเว้นวรรค → npm ไปเรียก create-next-app |
npx create-next-app@latest | ✅ ทางที่เอกสารทางการใช้ · ตัวนี้ขีดกลางถูกแล้ว เพราะ npx รับ "ชื่อแพ็กเกจ" ตรง ๆ ไม่ต้องเติมอะไรให้ |
pnpm create next-app@latest | ✅ ตามสไลด์ ถูกอยู่แล้ว |
กฎที่จำได้ยาว: npx = ขีดกลาง · npm create/pnpm create = เว้นวรรค
โปรเจกต์ทดลองของบทนี้สร้างด้วยบรรทัด npx create-next-app@latest จริง ๆ และมันผ่าน
คิดว่า npm คือพนักงานหน้าร้านที่มีใบสั่งซื้อสองช่อง: ช่องซ้ายเขียนว่าจะทำอะไร
(สั่ง / คืน / เช็คสต็อก) ช่องขวาเขียนว่าของชิ้นไหน
คุณเดินไปบอกว่า "สั่ง-กาแฟเย็น" ติดกันเป็นคำเดียว — พนักงานจะเปิดสมุดหาคำสั่งชื่อ "สั่ง-กาแฟเย็น"
แล้วบอกว่า ไม่มีคำสั่งชื่อนี้ในสมุด ทั้งที่เขามีทั้งคำสั่ง "สั่ง" และมีกาแฟเย็นขายอยู่
ที่ไม่เหมือนกัน: พนักงานจริงเดาเจตนาคุณออกแล้วชงกาแฟให้อยู่ดี
แต่ npm ไม่เดาให้เลยสักนิด — และนี่คือเรื่องดี เพราะถ้ามันเดา
วันหนึ่งมันจะเดาผิดแล้วลงแพ็กเกจที่คุณไม่ได้ขอ ซึ่งอันตรายกว่าเยอะ
อีกจุดที่ไม่เหมือน: npx ไม่ได้มีสองช่องแบบนี้ มันรับ "ชื่อของ" ช่องเดียว
ขีดกลางใน npx create-next-app จึงถูกต้องแล้ว ไม่ใช่ข้อยกเว้นมั่ว ๆ
ตอบคำถามตอนติดตั้ง แล้วได้อะไรกลับมาบ้าง
พอคำสั่งรันได้ มันจะถามชื่อโปรเจกต์ก่อน แล้วถามต่อว่าจะใช้ค่าที่แนะนำไหม
สกรีนช็อตบนสไลด์หน้า 14 โชว์ตัวเลือกแรกว่า "Yes, use recommended defaults"
พร้อมรายการที่มันจะเปิดให้:
# ข้อความบนสไลด์หน้า 14 ในช่องตัวเลือกแรก
TypeScript, ESLint, No React Compiler, Tailwind, ... directory, App Router, AGENTS.md
อ่านบรรทัดนี้ให้ออกทีละคำ เพราะมันคือสรุปว่าโปรเจกต์คุณจะหน้าตายังไงไปอีกทั้งคอร์ส:
| คำบนสไลด์ | แปลว่า |
|---|---|
| TypeScript | ไฟล์เป็น .tsx ไม่ใช่ .jsx · ได้ tsconfig.json มาด้วย · นี่คือของที่คุณเรียนมาแล้วในบท 63–68 |
| ESLint | ได้ eslint.config.mjs ติดมาในโปรเจกต์ · ตัวไล่จับโค้ดที่เขียนไม่ดี (ไม่ใช่แค่พิมพ์ผิด) |
| No React Compiler | ตัวคอมไพเลอร์ที่ใส่ memo ให้อัตโนมัติ ปิดอยู่ · จำประโยคนี้ไว้ เพราะมันจะสำคัญมากในบทเรื่อง useMemo |
| Tailwind | ได้ Tailwind มาพร้อมใช้ · แต่เป็นเวอร์ชัน 4 ซึ่งตั้งค่าไม่เหมือนที่คุณเจอในบทความเก่า ๆ (ดูกล่องเตือนข้างล่าง) |
| App Router | ระบบเส้นทางแบบใหม่ที่ใช้โฟลเดอร์ app/ · คือหัวใจของบทนี้ทั้งบท |
| AGENTS.md | ไฟล์คำแนะนำสำหรับ AI ที่มาช่วยเขียนโค้ดในโปรเจกต์นี้ · เป็นของใหม่ที่เพิ่งมีในเวอร์ชันหลัง ๆ |
พอกด Enter เสร็จ นี่คือผลรันจริงในโปรเจกต์ที่ได้ (next 16.3.4) ·
ผมสั่ง ls -1a — เลข 1 แปลว่า "บรรทัดละชื่อ" ซึ่งอ่านง่ายกว่าแบบหลายคอลัมน์
ผมตัดออกสามบรรทัดที่ไม่ได้มาจาก create-next-app —
.next กับ tsconfig.tsbuildinfo ที่เกิดตอนรันโปรเจกต์
และ components ที่ผมสร้างเองทีหลัง
$ ls -1a
.
..
.git
.gitignore
AGENTS.md
CLAUDE.md
README.md
app
eslint.config.mjs
next-env.d.ts
next.config.ts
node_modules
package-lock.json
package.json
postcss.config.mjs
public
tsconfig.json
$ ls -1 app
favicon.ico
globals.css
layout.tsx
page.tsx
สามข้อสังเกตที่ผู้เรียนมักสะดุด:
.git/โผล่มาเอง —create-next-appสั่งgit initให้เลย คุณไม่ต้องสั่งเอง (และไม่ต้องตกใจว่าเผลอไปทำอะไร).next/ยังไม่มีตอนนี้ · มันจะโผล่มาหลังคุณรันเว็บครั้งแรก เป็นที่เก็บของที่ Next.js สร้างขึ้นเอง — อย่าไปแก้ไฟล์ในนั้น และมันอยู่ใน.gitignoreแล้วAGENTS.mdกับCLAUDE.mdติดมาด้วยจริง ๆ ในเวอร์ชันนี้ — สไลด์หน้า 18 ก็โชว์สองไฟล์นี้อยู่ในแถบ EXPLORER เหมือนกัน
⚠️ Tailwind ในโปรเจกต์นี้เป็นเวอร์ชัน 4 — ไม่มี tailwind.config.js ให้แก้แล้ว
ถ้าคุณไปหาบทความหรือถาม AI แล้วมันบอกให้ "แก้ tailwind.config.js"
คุณจะหาไฟล์นั้นไม่เจอ เพราะมันไม่มีจริง ไม่ใช่เพราะคุณลงพลาด
นี่คือ app/globals.css ตัวจริงในโปรเจกต์ ผลรัน cat สองบรรทัดแรก:
@import "tailwindcss";
:root {
--background: #ffffff;
--foreground: #171717;
}
v3 (แบบเก่า) ใช้ @tailwind base; @tailwind components; @tailwind utilities; สามบรรทัด
+ ไฟล์ config แยก · v4 ใช้ @import "tailwindcss"; บรรทัดเดียว
แล้วตั้งค่าสีธีมในไฟล์ CSS เดียวกันนี้เลย · ส่วนตัวต่อกับ build อยู่ใน postcss.config.mjs:
"@tailwindcss/postcss": {}
รันเว็บขึ้นมาดู
สไลด์หน้า 15 ให้สามบรรทัดตามตัวจัดการแพ็กเกจ — อันนี้สไลด์ถูกทั้งสามบรรทัด:
$ npm run dev # หรือ
$ yarn dev # หรือ
$ pnpm dev
ทำไม npm ต้องมีคำว่า run แต่อีกสองตัวไม่ต้อง?
เพราะ dev ไม่ใช่คำสั่งของ npm — มันคือชื่อสคริปต์ที่เขียนไว้ใน
package.json ซึ่ง npm ต้องให้บอกก่อนว่าจะ "run" สคริปต์
เปิด package.json จริงในโปรเจกต์นี้ดูก็จะเห็น:
"scripts": {
"dev": "next dev",
"build": "next build",
"start": "next start",
"lint": "eslint"
}
นี่คือผลรันจริงตอนสั่งรัน (ผมมาสก์เลข IP ในวงแลนออกเป็น x.x):
▲ Next.js 16.3.4 (Turbopack)
- Local: http://localhost:370
- Network: http://192.168.x.x:370
✓ Ready in 156ms
✓ Running next.config.ts took 12ms
- Turbopack ในวงเล็บคือตัวแปลงโค้ดที่ Next 16 ใช้เป็นค่าปริยายแล้ว
— สังเกตเลข
Ready in 156msนั่นคือเหตุผลที่เขาเปลี่ยนมาใช้มัน - Local คือที่อยู่สำหรับเครื่องคุณเอง · Network คือที่อยู่ให้มือถือ ที่ต่อ Wi-Fi ตัวเดียวกันเปิดได้ — มีประโยชน์มากตอนอยากลองบนจอเล็กจริง ๆ
- ผมรันที่พอร์ต
370เพราะโปรเจกต์นี้ใช้ร่วมกันหลายบท · ค่าปริยายของ Next.js คือ3000คุณจะได้http://localhost:3000
หน้าที่ได้จะเป็นหน้าต้อนรับที่มีข้อความ "To get started, edit the page.tsx file." (ตรงกับสกรีนช็อตบนสไลด์หน้า 15 เป๊ะ) — ประโยคนี้ไม่ได้เขียนเล่น ๆ มันกำลังบอกคุณว่าไฟล์ไหนคือหน้าแรก ซึ่งเป็นเรื่องของหัวข้อถัดไป
- scaffold
- การปั๊มโครงโปรเจกต์เปล่า ๆ ขึ้นมาให้ทั้งชุด —
create-next-appคือตัวทำ scaffold · คำนี้ใช้กับทุกภาษา ไม่ใช่แค่ Next.js · ถาม senior ได้ว่า “ทีมเรา scaffold โปรเจกต์ใหม่ด้วยอะไร มีเทมเพลตของทีมไหม” — หลายทีมมีตัวปั๊มของตัวเองที่ใส่ config ของบริษัทมาให้แล้ว จะได้ไม่ต้องนั่งตั้งใหม่ - dev server
- เซิร์ฟเวอร์ที่รันเฉพาะตอนพัฒนา (จาก
npm run dev) — มันคอยดูไฟล์ ถ้าคุณแก้แล้วเซฟ หน้าเว็บจะอัปเดตให้เอง · คนละตัวกับnpm startที่เป็นเซิร์ฟเวอร์ของของจริง ซึ่งต้องnpm run buildก่อนถึงจะรันได้ · เจอคำถาม “ทำไมของบนเซิร์ฟเวอร์จริงช้ากว่า/ต่างจากในเครื่อง” ให้ถามกลับก่อนเลยว่าเทียบกับ dev หรือ build - Turbopack
- ตัวแปลงและรวมโค้ดที่ Next 16 ใช้เป็นค่าปริยาย (แทน webpack ของเดิม) —
คือเหตุผลที่ผลรันข้างบนขึ้น
Ready in 156ms· พิมพ์บอก AI ได้ว่า “โปรเจกต์นี้เป็น Next 16 ที่ใช้ Turbopack” เพราะคำแนะนำเรื่องปรับ config ของยุค webpack หลายอย่างใช้กับมันไม่ได้แล้ว
plugin ที่แนะนำ — และอันไหนที่ "ลงแล้วไม่พอ"
สไลด์หน้า 16 แนะนำสี่ตัว ผมเพิ่มคอลัมน์ว่ามันทำอะไรให้จริง ๆ:
| ส่วนขยาย VS Code | ได้อะไร | ต้องมีอะไรในโปรเจกต์ด้วยไหม |
|---|---|---|
| React Snippets (Simple React Snippets / ES7+) |
พิมพ์ตัวย่อสั้น ๆ แล้วมันกางโครง component ให้ · ประหยัดการพิมพ์ซ้ำ | ไม่ต้อง เป็นเรื่องของ editor ล้วน ๆ |
| ESLint | ขีดเส้นหยักใต้โค้ดที่ผิดกฎ — ไม่ใช่แค่พิมพ์ผิด แต่รวมถึง "เขียนแบบนี้แล้วจะมีปัญหาทีหลัง" | ต้องมี · ส่วนขยายแค่เอากฎมาแสดงบนจอ กฎตัวจริงอยู่ใน eslint.config.mjs ในโปรเจกต์ ซึ่ง create-next-app ใส่มาให้แล้ว |
| Prettier | จัดหน้าโค้ดให้สวยเท่ากันทั้งทีม — เว้นวรรค ขึ้นบรรทัด วงเล็บ | ไม่บังคับ แต่ทีมส่วนใหญ่จะมีไฟล์ตั้งค่าไว้ให้เหมือนกันทุกเครื่อง |
| Tailwind CSS IntelliSense | เดาชื่อคลาส Tailwind ให้ตอนพิมพ์ + โชว์ว่าคลาสนั้นแปลเป็น CSS อะไร | ต้องมี Tailwind ในโปรเจกต์จริง มันถึงจะรู้ว่าจะเดาอะไรให้ |
ประเด็นที่สไลด์ไม่ได้พูด แต่ทำให้คนใหม่งงบ่อยที่สุด:
ส่วนขยายใน editor กับเครื่องมือในโปรเจกต์ เป็นคนละอย่างกัน
ลง ESLint ใน VS Code แล้วเส้นหยักไม่ขึ้น ไม่ได้แปลว่าลงพลาด —
ให้ไปดูก่อนว่าโปรเจกต์มี eslint.config.mjs ไหม
และกลับกัน: โปรเจกต์ที่ไม่ได้ลงส่วนขยายเลยก็ยัง lint ได้ ด้วยคำสั่ง
npm run lint ใน terminal · ส่วนขยายทำแค่ย้ายผลมาไว้ตรงหน้าคุณเร็วขึ้น
เรื่องใหญ่ที่สุดของบทนี้ — ชื่อโฟลเดอร์คือ URL
ในบท 38 คุณเขียน route ของ Gin ด้วยมือ ทีละบรรทัด แบบนี้:
// Gin — คุณเป็นคนบอกเองว่า path ไหนไปเรียกฟังก์ชันไหน
r.GET("/users/:id", getUserHandler)
ใน Next.js บรรทัดแบบนี้ไม่มี — และไม่มีที่ให้เขียนด้วย
Next.js ไปไล่อ่านโฟลเดอร์ใต้ app/ แล้วสร้าง route ให้เองจาก
ชื่อโฟลเดอร์ · กฎมีสองข้อเท่านั้น:
- ชื่อโฟลเดอร์ = ชิ้นส่วนหนึ่งของ URL · ซ้อนโฟลเดอร์ = ซ้อน path
- โฟลเดอร์จะกลายเป็นหน้าเว็บได้ ก็ต่อเมื่อในนั้นมีไฟล์ชื่อ
page.tsx· โฟลเดอร์เปล่า ๆ หรือมีไฟล์ชื่ออื่น ไม่ใช่หน้าเว็บ
ผมสร้างของจริงในโปรเจกต์ทดลองสี่โฟลเดอร์ แล้วรันดู นี่คือสิ่งที่สร้าง:
app/
└── t70/ ← ชื่อโฟลเดอร์ที่ผมใช้กันชนกับบทอื่น
├── layout.tsx ← ไม่ใช่หน้าเว็บ (หัวข้อถัดไปจะพูดถึง)
├── page.tsx ← หน้าเว็บ → /t70
├── about/
│ └── page.tsx ← หน้าเว็บ → /t70/about
├── blog/ ← ไม่มี page.tsx ของตัวเอง เป็นแค่ทางผ่าน → ???
│ └── hello/
│ └── page.tsx ← หน้าเว็บ → /t70/blog/hello
└── secret/
└── note.tsx ← ไฟล์ธรรมดา ชื่อไม่ใช่ page.tsx → ???
เนื้อในแต่ละ page.tsx สั้นมาก แค่พอให้แยกออกว่าอันไหนเป็นอันไหน —
นี่คือไฟล์ app/t70/about/page.tsx ทั้งไฟล์:
export default function About() {
return <h1>PAGE: /t70/about</h1>;
}
สองอย่างที่ page.tsx ต้องมี ห้ามขาด:
ต้อง export default (ไม่ใช่ export เฉย ๆ)
และของที่ export ต้องเป็นฟังก์ชันที่คืน JSX
ชื่อฟังก์ชัน (About) จะตั้งว่าอะไรก็ได้ Next.js ไม่ได้ดูชื่อ มันดูว่า
"ตัวที่ export default ออกมา" คืออะไร
# รันเว็บแล้วยิงทั้งสี่ที่อยู่ดู
$ curl -o /dev/null -w "HTTP %{http_code}\n" http://localhost:370/t70
$ curl -o /dev/null -w "HTTP %{http_code}\n" http://localhost:370/t70/about
$ curl -o /dev/null -w "HTTP %{http_code}\n" http://localhost:370/t70/blog/hello
$ curl -o /dev/null -w "HTTP %{http_code}\n" http://localhost:370/t70/secret
$ curl -o /dev/null -w "HTTP %{http_code}\n" http://localhost:370/t70/blog
สามอันแรกคง 200 อยู่แล้ว — แต่สองอันท้ายล่ะ?
secret/ มีโฟลเดอร์จริง มีไฟล์ .tsx อยู่ข้างในจริง แค่ชื่อไม่ใช่ page.tsx
blog/ ก็มีโฟลเดอร์จริง แถมข้างในมี hello/page.tsx ที่เปิดได้อยู่ด้วย —
แล้วตัว /t70/blog เองล่ะ เปิดได้ไหม?
ผลรันจริง — ทั้งสองอันได้ 404 และไม่มี error ใด ๆ ใน terminal ด้วย:
/t70 HTTP 200
/t70/about HTTP 200
/t70/blog/hello HTTP 200
/t70/secret HTTP 404
/t70/blog HTTP 404
หน้าที่ได้จาก /t70/secret คือหน้า 404 มาตรฐานของ Next.js
(ในเนื้อหน้ามีข้อความ This page could not be found)
/t70/blog ได้ 404 คือจุดที่คนพลาดกันมากที่สุด —
โฟลเดอร์ blog/ เป็นแค่ทางผ่าน มันไม่มี page.tsx ของตัวเอง
ถ้าอยากให้ /t70/blog เปิดได้ด้วย ก็แค่เพิ่ม app/t70/blog/page.tsx เข้าไปอีกไฟล์ จบ
สำคัญ: ทั้งสองกรณีไม่ใช่ error · การวางไฟล์ .tsx อื่น ๆ ไว้ในโฟลเดอร์ของ route
เป็นเรื่องที่ทำได้และทำกันปกติ (เช่นวาง component ที่ใช้เฉพาะหน้านั้น) ·
โฟลเดอร์ที่เป็นทางผ่านเฉย ๆ ก็ปกติ — Next.js แค่ไม่นับสองอย่างนี้เป็นหน้าเว็บ
อีกหลักฐานที่หนักแน่นกว่า curl คือ ตารางที่ next build พิมพ์ออกมาเอง
ตอนจบ · ผลรันจริง (ผมตัดบรรทัดของบทอื่นที่อยู่ในโปรเจกต์เดียวกันออก เหลือเฉพาะของบทนี้):
$ npx next build
✓ Compiled successfully in 647ms
Running TypeScript ...
✓ Generating static pages using 9 workers (30/30) in 138ms
Route (app)
┌ ○ /
├ ○ /_not-found
├ ○ /t70
├ ○ /t70/about
├ ○ /t70/blog/hello
└ ...
○ (Static) prerendered as static content
ƒ (Dynamic) server-rendered on demand
/t70/secret ไม่มีอยู่ในตารางนี้ — Next.js ไม่รู้จักมันตั้งแต่ตอน build แล้ว
ไม่ใช่ไปพังตอนมีคนกดเข้ามา · ตารางนี้แหละคือ "รายการ route" ของโปรเจกต์คุณ
ถ้าอยากรู้ว่าเว็บมีหน้าอะไรบ้าง สั่ง npx next build แล้วอ่านตารางนี้
บรรทัด /_not-found ไม่ใช่ของที่คุณสร้าง — Next.js ใส่มาให้เองทุกโปรเจกต์
มันคือหน้า 404 มาตรฐานที่คุณเพิ่งเห็นตอน curl /t70/secret นั่นแหละ
ตอนรันเองแล้วเจอบรรทัดนี้โผล่มาในตาราง อย่าเพิ่งคิดว่าตัวเองทำอะไรพลาด ·
ถ้าอยากได้หน้า 404 หน้าตาของตัวเอง ให้สร้างไฟล์ app/not-found.tsx แล้วมันจะมาแทนที่อันนี้
ตารางเทียบ: path ของไฟล์ ↔ URL ที่ได้
แถวที่มี ✅ คือของที่ผม curl จริงแล้วได้ 200 ·
คอลัมน์ขวาสุดคือหน้าตาที่คุณจะเจอในโปรเจกต์ของคุณเอง ที่ไม่มีชั้น t70 คั่น
| ไฟล์ในโปรเจกต์ทดลอง | URL ที่ได้จริง | ในโปรเจกต์คุณจะเขียนแค่ |
|---|---|---|
app/page.tsx | ✅ / | เหมือนกัน — นี่คือหน้าแรก |
app/t70/page.tsx | ✅ /t70 | app/dashboard/page.tsx → /dashboard |
app/t70/about/page.tsx | ✅ /t70/about | app/about/page.tsx → /about |
app/t70/blog/hello/page.tsx | ✅ /t70/blog/hello | app/blog/hello/page.tsx → /blog/hello |
app/t70/blog/ (ไม่มี page.tsx ของตัวเอง) | ❌ 404 | โฟลเดอร์ที่เป็นแค่ทางผ่าน ไม่ใช่หน้า |
app/t70/secret/note.tsx | ❌ 404 | ไฟล์ที่ไม่ได้ชื่อ page.tsx ไม่เป็น URL |
app/t70/layout.tsx | — ไม่เป็น URL ของตัวเอง | มันไปครอบหน้าอื่นแทน (หัวข้อถัดไป) |
เทียบกับ Gin ที่เพิ่งเรียนมา
| เรื่อง | Gin (บท 38) | Next.js App Router |
|---|---|---|
| บอก path ยังไง | พิมพ์เป็นสตริง r.GET("/users/:id", h) |
ตั้งชื่อโฟลเดอร์ — ไม่มีสตริงให้พิมพ์ |
| อยากรู้ว่าเว็บมี route อะไรบ้าง | อ่านไฟล์ที่รวม r.GET/r.POST ไว้ |
ดูโครงโฟลเดอร์ หรืออ่านตารางจาก next build |
| พิมพ์ path ผิด (เช่นตกตัวอักษร) | คอมไพล์ผ่าน แต่ยิงไปแล้วได้ 404 ตอนรัน | ก็ได้ 404 เหมือนกัน — แต่คุณเห็นมันตั้งแต่ตอนตั้งชื่อโฟลเดอร์ใน editor |
| มีสอง route ชื่อชนกัน | เป็นไปได้ — ต้องระวังลำดับที่ลงทะเบียน | เป็นไปไม่ได้ — ระบบไฟล์ไม่ยอมให้มีสองโฟลเดอร์ชื่อเดียวกันในที่เดียวกันอยู่แล้ว |
| ย้าย route ไป path ใหม่ | แก้สตริงในโค้ด | เปลี่ยนชื่อโฟลเดอร์ · ไม่ต้องแตะโค้ดข้างในเลยสักบรรทัด |
Gin เหมือนตึกที่มีสมุดสารบัญเล่มเดียวแขวนไว้ที่ล็อบบี้ —
เขียนไว้ว่า "ห้อง 301 = ฝ่ายบัญชี" · ใครย้ายห้องต้องกลับมาแก้สมุด
ถ้าลืมแก้ สมุดกับความจริงจะไม่ตรงกัน แล้วคนจะเดินหลง
Next.js เหมือนตึกที่ไม่มีสมุดสารบัญเลย แต่ทุกห้องมีป้ายติดหน้าประตู —
อยากรู้ว่ามีห้องอะไรบ้าง เดินดูเอา · ย้ายฝ่ายบัญชีไปชั้นสี่ = ยกป้ายไปติดที่ประตูใหม่ จบ
ไม่มีสมุดให้ลืมแก้
ที่ไม่เหมือนกัน: ป้ายหน้าห้องจริง ๆ ใครก็แขวนมั่วได้
แต่ Next.js บังคับว่าห้องจะ "มีอยู่" ก็ต่อเมื่อข้างในมีเฟอร์นิเจอร์ชื่อ page.tsx
— โฟลเดอร์เปล่าจึงไม่ใช่หน้าเว็บ ซึ่งไม่มีอะไรในโลกจริงเทียบได้ตรง ๆ
อีกจุด: สารบัญของ Gin เขียนอะไรก็ได้ที่คุณคิดออก (เช่นเงื่อนไขแปลก ๆ)
ส่วนป้ายหน้าห้องต้องเล่นตามกฎการตั้งชื่อโฟลเดอร์ — ยืดหยุ่นน้อยกว่า แลกกับความคาดเดาได้
- file-system routing
- ระบบที่ใช้ตำแหน่งของไฟล์เป็นตัวกำหนด URL แทนการเขียนตารางเส้นทางด้วยมือ — ทั้งหัวข้อนี้อธิบายคำนี้คำเดียว · ถาม senior ได้ว่า "โปรเจกต์นี้ใช้ file-system routing หรือประกาศ route เอง" เป็นคำถามแรกที่ควรถามเวลาเข้าโปรเจกต์ frontend ใหม่ เพราะคำตอบเปลี่ยนวิธีหาไฟล์ทั้งหมด
- route segment
- ชิ้นส่วนหนึ่งของ URL ที่คั่นด้วย
/—/blog/helloมีสอง segment และในโปรเจกต์ก็คือสองชั้นโฟลเดอร์พอดี · พิมพ์บอก AI ได้ว่า "ช่วยเพิ่ม route segmentsettingsใต้dashboardให้หน่อย" แล้วมันจะรู้ทันทีว่าให้สร้างapp/dashboard/settings/page.tsx - App Router
- ชื่อของระบบเส้นทางแบบที่ใช้โฟลเดอร์
app/ตัวนี้ · Next.js เคยมีอีกแบบชื่อ Pages Router (ใช้โฟลเดอร์pages/) ซึ่งกฎต่างกันพอสมควร · เวลาค้นหาคำตอบให้พิมพ์คำว่า "App Router" ต่อท้ายเสมอ ไม่งั้นคุณจะเจอคำตอบของระบบเก่าปนมาแล้วทำตามไม่ได้
layout.tsx ต่างจาก page.tsx ยังไง
สไลด์หน้า 19 สรุปไว้สองบรรทัดสั้น ๆ:
"layout.tsx: root layout, wraps every page" และ
"page.tsx: page level to render interface"
ถูกทั้งคู่ แต่สั้นเกินกว่าจะเอาไปใช้จริง ผมขอแตกให้ครบ:
page.tsx | layout.tsx | |
|---|---|---|
| เป็น URL ของตัวเองไหม | ✅ ใช่ — มันคือ "หน้า" | ❌ ไม่ · มันคือ "กรอบ" |
| รับอะไรเข้ามา | ไม่ต้องรับอะไรก็ได้ | รับ children = หน้าที่จะมาอยู่ข้างใน |
| ครอบใครบ้าง | — | ทุกหน้าในโฟลเดอร์เดียวกันและโฟลเดอร์ลูกทั้งหมด |
| ตอนเปลี่ยนหน้า | ถูกแทนที่ด้วยหน้าใหม่ | อยู่เฉย ๆ ไม่ถูกวาดใหม่ |
| เอาไปใส่อะไร | เนื้อหาของหน้านั้น ๆ | เมนู · แถบบน · footer · ของที่ต้องอยู่ทุกหน้า |
นี่คือ app/t70/layout.tsx ตัวจริงที่ผมใช้ทดลอง ตัดให้เหลือแก่น:
export default function T70Layout({ children }: { children: React.ReactNode }) {
return (
<div>
<header>
<strong>HEADER-FROM-LAYOUT</strong>
<LayoutCounter />
</header>
<main>{children}</main> {/* ← หน้าเว็บมาโผล่ตรงนี้ */}
<footer>FOOTER-FROM-LAYOUT</footer>
</div>
);
}
{children} คือช่องว่างที่บอกว่า "เอาหน้ามาวางตรงนี้" —
ไม่มีบรรทัดนี้ หน้าเว็บจะหายไปเฉย ๆ
ลอง curl หน้า /t70/blog/hello ดู นี่คือ HTML จริงที่ได้กลับมา
(ผมตัดเฉพาะช่วงที่ layout สร้าง):
$ curl -s http://localhost:370/t70/blog/hello
<div><header><strong>HEADER-FROM-LAYOUT</strong>
<button id="cnt">layout counter = <!-- -->0</button>
<nav><a href="/t70">home</a> <a href="/t70/about">about</a>
<a href="/t70/blog/hello">blog/hello</a></nav></header>
<main><h1>PAGE: /t70/blog/hello</h1></main>
<footer>FOOTER-FROM-LAYOUT</footer></div>
HEADER-FROM-LAYOUT กับ FOOTER-FROM-LAYOUT โผล่มาด้วย
ทั้งที่ไฟล์ app/t70/blog/hello/page.tsx มีแค่ <h1> บรรทัดเดียว
— และ hello อยู่ลึกลงไปสองชั้นจากตัว layout ด้วยซ้ำ
นี่คือความหมายของคำว่า "ครอบทุกหน้า": ครอบลูกหลานทุกชั้น ไม่ใช่แค่ชั้นเดียว
✅ layout ซ้อน layout ได้ และมันซ้อนกันจริง —
หลักฐาน: หน้า /t70/about ของผมยังได้แท็กนี้มาด้วย
<html lang="en" class="... h-full antialiased">
และ <title>Create Next App</title>
ทั้งสองอย่างนี้ไม่ได้อยู่ใน app/t70/layout.tsx ของผมเลย —
มันมาจาก app/layout.tsx ซึ่งเป็น root layout ของทั้งโปรเจกต์
แปลว่าหน้าหนึ่งหน้าถูกครอบด้วย layout ทุกชั้นที่อยู่เหนือมัน เรียงจากนอกเข้าใน
ข้อที่คนพลาดบ่อยที่สุด: layout ไม่ถูกวาดใหม่ตอนเปลี่ยนหน้า
ประโยคนี้พูดกันเยอะแต่ไม่ค่อยมีใครพิสูจน์ให้ดู ผมพิสูจน์ให้:
ผมใส่ปุ่มนับเลขไว้ใน layout (ไฟล์ components/t70/LayoutCounter.tsx)
ปุ่มนี้จำเลขไว้ในตัวมันเอง · ถ้า layout ถูกวาดใหม่เมื่อไหร่ เลขจะกลับไปเป็น 0
// ลำดับที่ทำในเบราว์เซอร์จริง
1. เปิด /t70 → กดปุ่มจนเลขขึ้นเป็น 2
2. กดลิงก์ "about" ใน layout → URL เปลี่ยนเป็น /t70/about
3. อ่านเลขบนปุ่มอีกครั้ง
ข้อ 3 จะได้เลขอะไร? — 0 (เพราะเปลี่ยนหน้าแล้วทุกอย่างเริ่มใหม่)
หรือ 2 (เพราะ layout ไม่ได้ถูกแตะ)?
แล้วถ้าแทนที่จะกดลิงก์ เรากด F5 โหลดหน้าใหม่ทั้งหน้า ล่ะ จะได้เลขเดียวกันไหม?
ผลรันจริงจากเบราว์เซอร์ ตอนกดลิงก์:
{
"path": "/t70/about",
"h1": "PAGE: /t70/about",
"counter": "layout counter = 2",
"sameButtonNode": true
}
ได้ 2 — และบรรทัด sameButtonNode: true คือหมัดเด็ด:
ผมเก็บตัวปุ่มไว้ก่อนเปลี่ยนหน้า แล้วเทียบกับปุ่มหลังเปลี่ยนหน้า
มันคือปุ่มก้อนเดียวกันในหน้าเว็บ ไม่ใช่ปุ่มใหม่ที่หน้าตาเหมือนกัน
ส่วน h1 เปลี่ยนจาก PAGE: /t70 เป็น PAGE: /t70/about เรียบร้อย
→ เนื้อหาเปลี่ยน กรอบไม่เปลี่ยน
แต่พอสั่งโหลดหน้าใหม่ทั้งหน้า (เท่ากับกด F5):
{
"path": "/t70/about",
"counter": "layout counter = 0"
}
กลับเป็น 0 · เพราะการโหลดใหม่ทั้งหน้าคือการโยนทุกอย่างทิ้งแล้วเริ่มใหม่จากศูนย์
— layout กันการวาดใหม่ได้เฉพาะตอนเปลี่ยนหน้าผ่านลิงก์ในเว็บ ไม่ใช่กัน F5
นี่คือเหตุผลว่าทำไมเมนูใน Next.js ถึงควรใช้ <Link> ไม่ใช่ <a> ธรรมดา
(จะลงรายละเอียดในบทเรื่อง navigation)
layout.tsx คือกรอบรูปที่แขวนติดผนัง ·
page.tsx คือรูปที่ใส่อยู่ในกรอบ
เปลี่ยนรูป = ถอดรูปเก่าออก ใส่รูปใหม่เข้าไป · กรอบไม่ได้ถูกถอดลงจากผนังเลย
ตะปูก็ยังอยู่ที่เดิม ฝุ่นบนกรอบก็ยังอยู่ที่เดิม
ปุ่มนับเลขในการทดลองข้างบนคือ "ฝุ่นบนกรอบ" นั่นแหละ — เลข 2 ไม่หายเพราะกรอบไม่ได้ถูกถอด
ที่ไม่เหมือนกัน: กรอบรูปจริงมีอันเดียวชั้นเดียว
แต่ layout ของ Next.js ซ้อนกันหลายชั้นได้ (root layout ครอบ layout ของ t70 ครอบหน้าอีกที)
เหมือนกรอบซ้อนกรอบซ้อนกรอบ
อีกจุดที่ต่าง: การรื้อบ้านใหม่ทั้งหลัง (กด F5) กรอบก็หลุดด้วย —
ความ "ไม่ถูกวาดใหม่" มีผลแค่ในช่วงที่ผู้ใช้เดินอยู่ในเว็บเท่านั้น
- root layout
app/layout.tsx— layout ชั้นนอกสุดที่ครอบทุกหน้าในเว็บ · เป็นที่เดียวที่มีแท็ก<html>กับ<body>และ Next.js บังคับให้ต้องมี · ถาม senior ได้ว่า "ของที่ต้องมีทุกหน้าจริง ๆ ควรไปอยู่ root layout หรือ layout ย่อย" — คำตอบมักคือ "ให้ต่ำที่สุดเท่าที่ทำได้" เพื่อไม่ให้หน้าที่ไม่เกี่ยวต้องแบกไปด้วย- nested layout
- layout ที่วางไว้ในโฟลเดอร์ย่อย แล้วมันไปซ้อนอยู่ข้างใน layout ของชั้นบน ·
พิมพ์บอก AI ได้ว่า "ทำ nested layout ใต้
app/dashboardให้มีเมนูซ้ายทุกหน้าในนั้น" แล้วมันจะรู้ว่าต้องสร้างapp/dashboard/layout.tsxไม่ใช่ไปยุ่งกับ root layout children- ชื่อของ "ของที่ถูกครอบ" ที่ layout รับเข้ามา · เอาไปวางตรงไหนในโครงก็ได้ ตรงนั้นแหละที่หน้าจะไปโผล่ ·
ถ้าเปิดหน้าแล้วเจอแต่เมนูแต่เนื้อหาหาย ให้เช็คก่อนเลยว่า layout ลืมเขียน
{children}หรือเปล่า — เป็นบั๊กที่หาไม่เจอง่าย ๆ เพราะ Next.js ไม่ฟ้องอะไรทั้งนั้น
แล้ว component ควรไปอยู่ไหน
สไลด์หน้า 20 เสนอโครงเพิ่มอีกสองโฟลเดอร์ ซึ่งไม่ใช่กฎของ Next.js แต่เป็นข้อตกลงของทีม
(Next.js ไม่รู้จักชื่อพวกนี้เลย มันสนใจแค่ page.tsx/layout.tsx):
| โฟลเดอร์ | สไลด์บอกว่า | แปลเป็นภาษาคน |
|---|---|---|
components/ |
pure and presentational of component | ชิ้นส่วนที่ไม่จำอะไรเอง — ได้ข้อมูลอะไรมาก็วาดตามนั้น เช่นปุ่ม การ์ด ป้าย |
containers/ |
Stateful composition of components by business requirement | ชิ้นส่วนที่จำสถานะเองและเอา component เล็ก ๆ มาประกอบตามงานจริง เช่น "ฟอร์มสมัครสมาชิก" |
สไลด์ยังมีกล่อง Tips ว่า "Component and container in the app/ folder are only used in themselves."
— แปลว่า ถ้าชิ้นส่วนนั้นใช้ที่เดียว ให้วางไว้ข้าง ๆ หน้าที่ใช้มันเลย (ใน app/)
แต่ถ้าใช้หลายที่ ให้ยกออกมาไว้ที่ components/ ระดับบนสุด
ในบทนี้ผมทำตามข้อตกลงนั้นจริง — ปุ่มนับเลขที่ใช้พิสูจน์เรื่อง layout
ผมวางไว้ที่ components/t70/LayoutCounter.tsx (นอก app/)
แล้ว import เข้ามาด้วย @/components/t70/LayoutCounter
เครื่องหมาย @/ คือทางลัดที่ชี้ไปรากโปรเจกต์ ตั้งไว้ใน tsconfig.json:
"paths": { "@/*": ["./*"] } — มีมาให้ตั้งแต่ create-next-app แล้ว
ประโยชน์: ไฟล์อยู่ลึกแค่ไหนก็เขียน import เหมือนกันหมด ไม่ต้องนับ ../../..
เช็คความเข้าใจ
ในโปรเจกต์ Next.js App Router คุณสร้างไฟล์ app/dashboard/settings/page.tsx · URL ที่เปิดได้คืออะไร
ตัวเลือกที่ 2 ถูก · โฟลเดอร์ทุกชั้นใต้ app/ ต่อกันเป็น URL —
ในบทนี้ผม curl พิสูจน์แบบสามชั้นมาแล้วด้วยซ้ำ
(app/t70/blog/hello/page.tsx → /t70/blog/hello → HTTP 200)
ตัวเลือกที่ 3 ผิดเพราะ app/ เป็น "จุดตั้งต้น" ไม่ได้นับเป็นชิ้นส่วนของ URL ·
ตัวเลือกที่ 4 คือวิธีคิดแบบ Gin (บท 38) ซึ่งไม่มีใน Next.js —
ไม่มีไฟล์ config ให้ลงทะเบียน route
โฟลเดอร์ app/reports/ มีอยู่จริง ข้างในมีไฟล์ ReportTable.tsx ไฟล์เดียว ไม่มี page.tsx · เปิด /reports จะได้อะไร
ตัวเลือกที่ 3 ถูก · ผมทดลองด้วยของจริงในบทนี้แล้ว:
app/t70/secret/note.tsx (มีไฟล์ ไม่มี page.tsx) →
curl ได้ HTTP 404
และมันไม่ใช่ error — next build ก็ไม่บ่นสักคำ ตารางเส้นทางแค่ไม่มีบรรทัดนี้เท่านั้น ·
การวางไฟล์ component ปนไว้ในโฟลเดอร์ route แบบนี้ทำได้ตามปกติ
ตัวเลือกที่ 4 ผิดชัดเจน: Next.js ดูที่ชื่อไฟล์ ไม่ได้ดูว่าในโฟลเดอร์มีกี่ไฟล์
ใน app/shop/layout.tsx มีปุ่มที่จำเลขไว้ในตัวเอง · ผู้ใช้กดจนเลขเป็น 5 แล้วกด <Link> ไปหน้า /shop/cart · เลขบนปุ่มจะเป็นเท่าไร
ตัวเลือกที่ 1 ถูก · ผลรันจริงในบทนี้: กดจนเลขเป็น 2 → กดลิงก์ไป /t70/about →
เลขยังเป็น 2 และตัวปุ่มเป็นก้อนเดิมในหน้าเว็บ (sameButtonNode: true)
ส่วน <h1> ของหน้าเปลี่ยนไปแล้ว
แต่มีข้อยกเว้นที่ต้องจำคู่กันเสมอ: ถ้าโหลดหน้าใหม่ทั้งหน้า (F5 หรือพิมพ์ URL แล้ว Enter)
เลขกลับเป็น 0 — ผมรันพิสูจน์ไว้ในกล่องเฉลยเรื่อง layout แล้วเหมือนกัน
ตัวเลือกที่ 4 ผิด: เรื่องนี้ไม่เกี่ยวกับ dev หรือ production เลย
พิมพ์ npm create-next-app@latest แล้วได้ Unknown command: "create-next-app@latest" · สาเหตุคืออะไร
ตัวเลือกที่ 4 ถูก · หลักฐานคือหน้าช่วยเหลือของ npm เอง:
npm init <package-spec> (same as `npx create-<package-spec>`)
— create คือชื่อคำสั่ง ส่วน next-app@latest คือของที่ส่งให้มัน
เขียนติดกันจึงกลายเป็นการเรียกคำสั่งชื่อ create-next-app@latest ที่ไม่มีอยู่ ·
ที่ถูกคือ npm create next-app@latest (เว้นวรรค) หรือ
npx create-next-app@latest (ขีดกลาง เพราะ npx รับชื่อแพ็กเกจตรง ๆ)
ตัวเลือกที่ 2 เป็นความเชื่อที่พบบ่อยแต่ผิด — npx/npm create
โหลดมาใช้ให้เองอยู่แล้ว ไม่ต้องลงถาวร
🛠 ภารกิจ — สร้างสองหน้าแล้วพิสูจน์ด้วยผลรัน ไม่ใช่แค่ดูในเบราว์เซอร์แล้วพอใจ
ทำตามลำดับ ห้ามข้ามข้อ 5 กับ 6 เพราะสองข้อนั้นคือ "หลักฐาน" ส่วนข้อ 1–4 คือแค่ "รู้สึกว่าน่าจะได้"
- สร้างโปรเจกต์ใหม่ด้วย
npx create-next-app@latestเลือก "Yes, use recommended defaults" · แล้วnpm run devให้ขึ้นที่localhost:3000 - สร้าง
app/about/page.tsxที่คืน<h1>ABOUT-PAGE</h1> - สร้าง
app/blog/first-post/page.tsxที่คืน<h1>FIRST-POST</h1> - สร้าง
app/about/layout.tsxที่ห่อ{children}ไว้ใน<div>และมี<footer>MY-LAYOUT</footer>ต่อท้าย - พิสูจน์เรื่อง URL ด้วย
curl— รันสี่บรรทัดนี้แล้วจดผลไว้:
คุณต้องอธิบายให้ได้ว่าทำไมบรรทัดที่ 3 ถึงได้เลขนั้น ก่อนไปข้อต่อไปcurl -o /dev/null -w "%{http_code}\n" http://localhost:3000/about curl -o /dev/null -w "%{http_code}\n" http://localhost:3000/blog/first-post curl -o /dev/null -w "%{http_code}\n" http://localhost:3000/blog curl -s http://localhost:3000/about | grep -o "MY-LAYOUT" - พิสูจน์เรื่อง layout ครอบใครบ้าง — รัน
curl -s http://localhost:3000/blog/first-post | grep -c "MY-LAYOUT"
ทำนายก่อนกด Enter ว่าจะได้0หรือ1แล้วค่อยรัน - ปิด dev server แล้วสั่ง
npx next build· เทียบตาราง Route ที่มันพิมพ์ออกมา กับรายชื่อโฟลเดอร์ที่คุณสร้าง ว่าตรงกันทุกบรรทัดไหม
ข้อ 5 — /about ได้ 200 · /blog/first-post ได้ 200
แต่ /blog ต้องได้ 404 · เพราะโฟลเดอร์ app/blog/
ไม่มี page.tsx ของตัวเอง มีแต่โฟลเดอร์ลูก
— โฟลเดอร์กลาง ๆ ที่เป็นแค่ทางผ่านไม่ใช่หน้าเว็บ
บรรทัดที่ 4 ต้องพิมพ์คำว่า MY-LAYOUT ออกมา (layout ครอบหน้า /about อยู่)
ข้อ 6 — ต้องได้ 0 · เพราะ app/about/layout.tsx
ครอบเฉพาะสิ่งที่อยู่ใต้โฟลเดอร์ about/ เท่านั้น ·
blog/first-post เป็นคนละสาย มันจึงไม่โดนครอบ
ถ้าอยากให้ครอบทั้งคู่ ต้องเลื่อน layout ขึ้นไปที่ app/layout.tsx
— นี่คือความหมายจริงของคำว่า "ครอบทุกหน้า" ที่สไลด์เขียนสั้น ๆ ไว้: ครอบทุกหน้าที่อยู่ใต้ตัวมัน
ข้อ 7 — ตาราง Route ควรมี / · /about · /blog/first-post
และไม่มี /blog ซึ่งตรงกับผล curl ในข้อ 5 พอดี
จะมีอีกบรรทัดที่คุณไม่ได้สร้างคือ /_not-found — อันนั้นถูกแล้ว
Next.js แถมมาให้เองทุกโปรเจกต์ (ดูกล่องอธิบายในหัวข้อเรื่อง URL)
💬 ติดตรงไหนถามได้เลย — โดยเฉพาะสามคำถามนี้ที่ผู้เรียนถามซ้ำทุกรุ่น:
“ผมสร้างโฟลเดอร์แล้วแต่เปิดไม่เจอหน้า เพราะอะไร” ·
“ทำไม layout ของผมไม่ครอบหน้าที่ผมอยากให้ครอบ” ·
“ตอนนี้จะเอาเมนูไปไว้ตรงไหนดี root layout หรือ layout ย่อย”
ถามพร้อมโครงโฟลเดอร์ที่คุณสร้างไว้จริง (พิมพ์ tree app หรือถ่ายรูปแถบ EXPLORER มา)
จะตอบได้ตรงจุดกว่าเยอะ เพราะปัญหาเกือบทั้งหมดของบทนี้อ่านออกจากโครงโฟลเดอร์
สรุปที่เอาไปใช้พรุ่งนี้ได้
npx create-next-app@latest(ขีดกลาง) หรือnpm create next-app@latest(เว้นวรรค) — สไลด์หน้า 14 บรรทัดแรกใช้ไม่ได้npm run dev→localhost:3000·npx next build→ ได้ตาราง route ทั้งเว็บ- โฟลเดอร์ = URL ·
page.tsx= สิ่งที่ทำให้โฟลเดอร์นั้นกลายเป็นหน้าจริง layout.tsxครอบทุกหน้าที่อยู่ใต้มัน · ซ้อนกันหลายชั้นได้ · ไม่ถูกวาดใหม่ตอนเปลี่ยนหน้าผ่านลิงก์ แต่หายหมดเมื่อโหลดใหม่ทั้งหน้า- Tailwind ในโปรเจกต์นี้เป็น v4 —
@import "tailwindcss";ในglobals.cssและไม่มีtailwind.config.js
อ้างอิง
- Next.js Docs — Installation —
อ่านหัวข้อ Automatic installation อย่างเดียวพอ
จะเห็นว่าเอกสารทางการใช้
npx create-next-app@latest(ขีดกลาง) ทุกที่ ซึ่งยืนยันสิ่งที่บทนี้รันให้ดู · ส่วน Manual installation ข้ามไปก่อน ยังไม่ต้องใช้ - Next.js Docs — Project Structure —
ดูตารางหัวข้อ Routing Files ซึ่งลิสต์ชื่อไฟล์พิเศษทั้งหมดที่ Next.js จองไว้
(
page·layout·loading·error·not-found…) บทนี้พูดถึงแค่สองตัวแรก · รู้ไว้ว่ารายการนี้มีอยู่ จะช่วยตอนเจอชื่อไฟล์แปลก ๆ ในโปรเจกต์คนอื่น - Next.js Docs —
layout.js— อ่านหัวข้อ Good to know ให้จบ มีข้อที่บทนี้พิสูจน์ไว้แล้วสองข้อ (root layout เป็นที่เดียวที่มี<html>/<body>· layout ไม่ re-render ตอน navigate) และมีอีกข้อที่บทนี้ยังไม่แตะ: layout รับsearchParamsไม่ได้ — จำไว้ เดี๋ยวจะไปงงเอาตอนทำฟิลเตอร์ - npm Docs —
npm init— อ่านย่อหน้า Description ที่บอกว่าnpm createเป็นชื่อเล่นของnpm initนี่คือต้นตอทั้งหมดของกับดักในหัวข้อแรก · อ่านแล้วคุณจะเดาได้เองว่าทำไมnpm create vite@latestถึงต้องเว้นวรรคเหมือนกัน - Tailwind CSS — Upgrade Guide (v3 → v4) —
อ่านแค่หัวข้อ Using the upgrade tool กับตารางแรกที่เทียบ v3 กับ v4
จะเห็นทันทีว่าทำไมโปรเจกต์นี้ถึงไม่มี
tailwind.config.js· มีประโยชน์มากเวลาก็อปโค้ดจากบทความเก่าแล้วมันไม่ทำงาน