Convert Static to Next.js
Day 2 - ชั่วโมงที่ 2: ย้าย Static Prototype เข้า Next.js
เป้าหมายของชั่วโมงนี้
หลังจบชั่วโมงที่สองของ Day 2 ผู้เรียนควรสามารถ:
- เปลี่ยนโครงสร้าง HTML จาก Day 1 ให้เป็น TSX ได้
- ใช้
className,htmlForและ TSX Comment ได้ถูกต้อง - ย้าย Header, Form, Issue List และ Footer เข้า
app/page.tsxได้ - ย้าย CSS เดิมเข้า
app/globals.cssได้ - ตรวจว่า Static Prototype แสดงผลใน Next.js ได้เหมือนเดิม
ไฟล์ที่ใช้ในชั่วโมงนี้
app/layout.tsx
app/page.tsx
app/globals.cssเปิด Development Server ด้วย npm run dev ระหว่างทำงาน
โครงสร้างเวลา 60 นาที
| เวลา | หัวข้อ | รูปแบบ |
|---|---|---|
| 0-10 นาที | วางแผนการย้าย HTML/CSS | อธิบาย |
| 10-20 นาที | รู้จักความต่างระหว่าง HTML กับ TSX | ทำทีละขั้นตอน |
| 20-40 นาที | ย้ายโครงหน้า, Form และ Issue List | ทำทีละขั้นตอน |
| 40-50 นาที | ย้าย CSS เข้า globals.css | ทำทีละขั้นตอน |
| 50-60 นาที | ตรวจผลลัพธ์ใน Browser | ตรวจความพร้อม |
Slide 1: ย้ายหน้าเดิมโดยยังไม่เพิ่ม Feature
Hour ที่แล้วเราสร้าง Next.js Project และเปิดหน้าแรกได้แล้ว
Hour นี้เราจะนำ Static Prototype จาก Day 1 เข้ามาอยู่ใน Project ใหม่:
- Header
- Form แจ้งปัญหา
- Issue List และ Status Badge
- Footer
- Responsive CSS
ข้อมูลใน Table ยังเป็น Mock Data และ Form ยังไม่บันทึกข้อมูล เป้าหมายตอนนี้คือย้ายหน้าตาเดิมให้ทำงานใน Next.js ก่อน
Slide 2: ไฟล์เดิมจะย้ายไปที่ไหน
| จาก Day 1 | ไปยัง Next.js | สิ่งที่ย้าย |
|---|---|---|
<html> และ <body> | app/layout.tsx | ภาษาและกรอบหลัก |
เนื้อหาภายใน <body> | app/page.tsx | Header, Main และ Footer |
style.css | app/globals.css | CSS เดิมทั้งหมด |
เราไม่ย้าย <!doctype html>, <head> หรือ <link rel="stylesheet"> เข้า page.tsx เพราะ Next.js จัดการ Document และการโหลด CSS ผ่าน layout.tsx
Slide 3: TSX คืออะไร
TSX คือ Syntax ที่ทำให้เราเขียนโครงสร้าง UI คล้าย HTML อยู่ภายใน Code TypeScript ได้
ไฟล์ที่มีทั้ง TypeScript และ UI แบบนี้จึงใช้นามสกุล .tsx เช่น app/page.tsx
const title = "ระบบแจ้งปัญหา IT";
export default function Home() {
return <h1>{title}</h1>;
}<h1>...</h1>คือโครงสร้าง UI ที่มีหน้าตาคล้าย HTMLtitleคือค่าที่สร้างด้วย TypeScript{title}นำค่านั้นมาแสดงภายใน UI
TSX ยังไม่ใช่ HTML ที่ Browser ใช้โดยตรง Next.js และ React จะนำ Code นี้ไปสร้าง Element บนหน้าเว็บให้เรา
Slide 4: กฎสำคัญเมื่อเปลี่ยน HTML เป็น TSX
| HTML | TSX |
|---|---|
class="form-group" | className="form-group" |
<label for="title"> | <label htmlFor="title"> |
<input required> | <input required /> |
rows="5" | rows={5} |
<!-- เพิ่ม Form --> | {/* เพิ่ม Form */} |
ชื่ออย่าง id, name, type, required และ aria-labelledby ยังใช้เหมือน HTML
ถ้าพบ Error หลัง Copy Code ให้เริ่มตรวจจากห้าจุดในตารางนี้ก่อน
Slide 5: เปลี่ยนภาษาหลักของหน้าเป็นภาษาไทย
แก้บางส่วน: app/layout.tsx
ค้นหา <html lang="en"> แล้วเปลี่ยนเฉพาะค่า lang:
<html lang="th">เก็บ Import, Font, Class และ {children} ที่ Next.js สร้างมาไว้เหมือนเดิม
lang="th" ช่วยให้ Browser และ Screen Reader ทราบว่าภาษาหลักของหน้าเป็นภาษาไทย
Slide 6: สร้างโครงหลักของ app/page.tsx
แทน Code ทั้งไฟล์: app/page.tsx
export default function Home() {
return (
<>
<header>
<h1>ระบบแจ้งปัญหา IT</h1>
<p>แจ้งและติดตามปัญหาการใช้งานระบบภายใน</p>
</header>
<main>
<section>
<h2>แจ้งปัญหาใหม่</h2>
{/* เพิ่ม Form ตรงนี้ */}
</section>
<section aria-labelledby="issue-list-title">
<h2 id="issue-list-title">รายการปัญหาล่าสุด</h2>
<p>ตัวอย่างรายการปัญหาที่ถูกแจ้งเข้ามาในระบบ</p>
<div className="table-wrapper">{/* เพิ่ม Table ตรงนี้ */}</div>
</section>
</main>
<footer>
<p>ฝ่ายเทคโนโลยีสารสนเทศ</p>
</footer>
</>
);
}<>...</> เรียกว่า Fragment ใช้ครอบ Element หลายตัวโดยไม่สร้าง Tag เพิ่มบนหน้าเว็บ
Slide 7: ย้ายข้อมูลผู้แจ้งเข้า Form
แทน Comment {/* เพิ่ม Form ตรงนี้ */} ใน app/page.tsx
<form>
<fieldset>
<legend>ข้อมูลปัญหา</legend>
<div className="form-row">
<div className="form-group">
<label htmlFor="reporterName">ชื่อผู้แจ้ง</label>
<input id="reporterName" name="reporterName" type="text" required />
</div>
<div className="form-group">
<label htmlFor="reporterEmail">อีเมลผู้แจ้ง</label>
<input id="reporterEmail" name="reporterEmail" type="email" required />
</div>
</div>
{/* เพิ่มข้อมูลปัญหาตรงนี้ */}
</fieldset>
</form>Form ยังอยู่ใน Component Home เหมือนเดิม เพียงเปลี่ยน class เป็น className และ for เป็น htmlFor
Slide 8: เติมข้อมูลปัญหาและปุ่ม Submit
แทน Comment {/* เพิ่มข้อมูลปัญหาตรงนี้ */} ใน Form
<div className="form-group">
<label htmlFor="title">หัวข้อปัญหา</label>
<input id="title" name="title" type="text" required />
</div>
<div className="form-group">
<label htmlFor="description">รายละเอียดปัญหา</label>
<textarea id="description" name="description" rows={5} required />
</div>
<button type="submit">ส่งข้อมูล</button>ชื่อ Field ยังคงเป็น reporterName, reporterEmail, title และ description เพื่อให้ตรงกับข้อมูลที่จะสร้างในบทเรียนถัดไป
Slide 9: สร้างโครง Table ใน Issue List
แทน Comment {/* เพิ่ม Table ตรงนี้ */} ภายใน table-wrapper
<table>
<thead>
<tr>
<th>รหัส</th>
<th>หัวข้อ</th>
<th>ผู้แจ้ง</th>
<th>สถานะ</th>
</tr>
</thead>
<tbody>{/* เพิ่ม Rows ตรงนี้ */}</tbody>
</table>โครงสร้าง Table ยังเหมือน HTML เดิม ส่วนที่เปลี่ยนเป็น TSX ชัดที่สุดคือ Comment ซึ่งต้องเขียนอยู่ภายใน {/* ... */}
Slide 10: เพิ่ม Mock Rows กลับเข้า Table
แทน Comment {/* เพิ่ม Rows ตรงนี้ */} ภายใน <tbody>
<tr>
<td>#001</td>
<td>Login เข้าระบบไม่ได้</td>
<td>Anan</td>
<td><span className="status status-open">OPEN</span></td>
</tr>
<tr>
<td>#002</td>
<td>ส่งแบบฟอร์มไม่ได้</td>
<td>Mali</td>
<td><span className="status status-progress">IN_PROGRESS</span></td>
</tr>
<tr>
<td>#003</td>
<td>ขอสิทธิ์เข้า Dashboard</td>
<td>Kanda</td>
<td><span className="status status-done">DONE</span></td>
</tr>แต่ละ Badge ใช้ Class พื้นฐาน status ร่วมกัน และใช้ Class ตัวที่สองกำหนดสีเฉพาะสถานะ
Slide 11: ย้าย CSS เดิมเข้า globals.css
เปิด app/globals.css แล้วทำตามลำดับ:
- ลบ CSS เริ่มต้นทั้งหมด รวมถึงบรรทัด
@import "tailwindcss"; - Copy เนื้อหาทั้งหมดจาก
style.cssของ Day 1 - Paste ลงใน
app/globals.css
โครงไฟล์หลังย้าย:
/* CSS ทั้งหมดจาก style.css ของ Day 1 */ไม่ต้อง Copy <link rel="stylesheet"> เพราะบรรทัดนั้นเป็น HTML และ globals.css ถูก Import จาก layout.tsx อยู่แล้ว
Slide 12: ทำไม Hour นี้ยังไม่ใช้ Tailwind
Project มี Tailwind Package จาก create-next-app อยู่แล้ว แต่บรรทัดนี้เป็นตัวเปิดใช้งาน Tailwind และ Preflight:
@import "tailwindcss";Preflight คือ Style พื้นฐานที่ Tailwind ใส่ให้กับ Tag ต่าง ๆ ก่อนที่เราจะเริ่มใช้ Utility Class
ใน Hour นี้เรายังใช้ Custom CSS จาก Day 1 ซึ่งมี Selector ของ Tag เช่น main, section, button และ table อยู่แล้ว หากเปิด Tailwind พร้อมกัน ผลลัพธ์อาจเปลี่ยนจาก Cascade ของ CSS สองชุดและทำให้ Debug ยากขึ้น
การลบ @import ไม่ได้ถอน Tailwind ออกจาก Project เพียงยังไม่โหลด Tailwind ในหน้านี้เท่านั้น Day 3 เราจะใส่บรรทัดนี้กลับมา แล้วค่อย ๆ เปลี่ยน Custom CSS เป็น Tailwind Utility Class
Slide 13: Media Query ของ Custom CSS ยังทำงาน
Media Query ที่ Copy มาจาก Day 1 ยังคงอยู่ใน app/globals.css:
@media (min-width: 720px) {
.form-row {
display: grid;
grid-template-columns: 1fr 1fr;
gap: 16px;
}
}เงื่อนไขนี้จะทำงานเมื่อ Viewport กว้างตั้งแต่ 720px ขึ้นไป เท่านั้น:
| ความกว้าง Viewport | ผลลัพธ์ของ Form |
|---|---|
ต่ำกว่า 720px | ชื่อและอีเมลเรียงลงมา 1 คอลัมน์ |
ตั้งแต่ 720px ขึ้นไป | ชื่อและอีเมลเรียงเป็น 2 คอลัมน์ |
ตัวอย่างเช่น Viewport กว้าง 631px ยังไม่ถึง 720px Form จึงแสดง 1 คอลัมน์ ซึ่งเป็นผลลัพธ์ที่ถูกต้อง
Slide 14: ตรวจหน้าใน Browser
เปิด URL ของ Development Server แล้วตรวจ:
- Header, Form, Issue List และ Footer แสดงครบ
- Form มี Field ทั้ง 4 ช่อง
- คลิก Label แล้ว Focus ไปยัง Field ที่ตรงกัน
- Table แสดง Mock Data 3 รายการ
- Status Badge มีทั้งสีและข้อความ
- เมื่อ Viewport แคบกว่า
720pxช่องชื่อและอีเมลเรียงลงมา 1 คอลัมน์ - เมื่อ Viewport กว้างตั้งแต่
720pxช่องชื่อและอีเมลเปลี่ยนเป็น 2 คอลัมน์ - เมื่อพื้นที่ของ Table แคบกว่า
680pxสามารถเลื่อน Table แนวนอนได้ - Scrollbar แนวนอนอยู่ใต้ Table เท่านั้น และไม่มี Scrollbar ใต้ทั้งหน้า
- Terminal และ Browser ไม่มี Error
เมื่อกด Submit ข้อมูลยังไม่ถูกเพิ่มใน Table เพราะเรายังไม่ได้เขียน Logic หรือเชื่อม Database
Slide 15: ตอนนี้ Static Prototype อยู่ใน Next.js แล้ว
สิ่งที่เปลี่ยนใน Hour นี้:
HTML structure → TSX ใน app/page.tsx
Document lang → app/layout.tsx
CSS เดิม → app/globals.cssสิ่งที่ยังเหมือนเดิม:
- Mock Data ยังเขียนอยู่ใน Table
- Form ยังไม่บันทึกข้อมูล
- UI ยังไม่ได้แยกเป็น Component
- Tailwind Package ยังอยู่ใน Project แต่ยังไม่ถูกเปิดใช้งานใน
globals.css
Hour ถัดไปเราจะสร้าง TypeScript Data Model และเปลี่ยน Static Rows ให้สร้างจากข้อมูลด้วย .map()