Givepo
บล็อก

พัฒนาเว็บยุคใหม่ด้วย Next.js, React และ TypeScript: แนวทางปฏิบัติที่ดีที่สุด

Server Components กลยุทธ์การเรนเดอร์ ข้อมูลที่ปลอดภัยด้วยไทป์ และ Core Web Vitals คู่มือปฏิบัติจริงสำหรับสร้างเว็บแอปที่เร็วและดูแลง่าย ด้วยชุดเทคโนโลยีที่ทีมส่วนใหญ่เลือกใช้ในวันนี้

ทีมงาน Givepo
อ่าน 6 นาที
Illustration of a code editor showing page.tsx next to a React component tree and a TypeScript badgeพัฒนาเว็บ

React, Next.js และ TypeScript กลายเป็นชุดเทคโนโลยีมาตรฐานสำหรับเว็บแอปพลิเคชันระดับมืออาชีพ React ให้โมเดลแบบคอมโพเนนต์ Next.js เพิ่มระบบ routing การเรนเดอร์ และการดึงข้อมูล ส่วน TypeScript จับข้อผิดพลาดได้ทั้งกลุ่มก่อนที่โค้ดจะรันเสียอีก หากใช้อย่างถูกต้อง จะได้เว็บที่เร็วสำหรับผู้เข้าชมและดูแลง่ายสำหรับนักพัฒนา

แต่ถ้าใช้อย่างไม่ระวัง ชุดเทคโนโลยีเดียวกันนี้ก็สร้างหน้าเว็บที่ช้าและเต็มไปด้วย JavaScript หลายเมกะไบต์ บทความนี้อธิบายแนวคิดที่สร้างความแตกต่าง พร้อมโค้ดที่นำไปใช้ในโปรเจกต์ Next.js App Router ได้ทันที

ทำไมชุดเทคโนโลยีนี้จึงกลายเป็นมาตรฐาน

  • React อธิบายหน้าจอเป็นต้นไม้ของคอมโพเนนต์ที่นำกลับมาใช้ซ้ำได้ ปุ่ม การ์ด หรือทั้งหน้าเป็นฟังก์ชันของข้อมูล ทำให้หน้าจอคาดเดาได้และทดสอบง่าย
  • Next.js เปลี่ยน React ให้เป็นเฟรมเวิร์กเต็มรูปแบบ ทั้ง routing ตามไฟล์ การเรนเดอร์ฝั่งเซิร์ฟเวอร์ การแคช การปรับแต่งรูปภาพและฟอนต์ และกระบวนการ build สำหรับ production
  • TypeScript เพิ่ม static type ให้ JavaScript เอดิเตอร์รู้โครงสร้างของทุกอ็อบเจกต์ การ refactor ปลอดภัย และข้อผิดพลาดตอนรันจำนวนมากกลายเป็นข้อผิดพลาดตอนคอมไพล์

เมื่อรวมกัน ทั้งสามครอบคลุมทุกอย่างตั้งแต่เว็บไซต์การตลาดไปจนถึงแดชบอร์ดที่ซับซ้อน ด้วยภาษาเดียวทั้งฝั่ง frontend และเซิร์ฟเวอร์

Server Components เปลี่ยนค่าเริ่มต้น

ใน Next.js App Router ทุกคอมโพเนนต์เป็น React Server Component เว้นแต่คุณจะเลือกเป็นอย่างอื่น Server Components ทำงานบนเซิร์ฟเวอร์เท่านั้น อ่านข้อมูลจากฐานข้อมูลหรือเรียก API ภายในได้โดยตรง และ JavaScript ของมันไม่เคยถูกส่งไปที่เบราว์เซอร์ มีเพียงผลลัพธ์ที่เรนเดอร์แล้วเท่านั้น

คุณใส่ directive 'use client' เฉพาะคอมโพเนนต์ที่ต้องมีการโต้ตอบ เช่น state, effect, event handler หรือ API ของเบราว์เซอร์ แนวทางที่ดีที่สุดคือทำให้ client component มีขนาดเล็ก และวางไว้ที่ปลายกิ่งของต้นไม้:

TSX
// app/blog/page.tsx: a Server Component (the default in the App Router)
import { getPosts } from '@/lib/posts'
import { LikeButton } from './like-button'

export default async function BlogPage() {
  const posts = await getPosts() // runs on the server, so no extra API route is needed

  return (
    <ul>
      {posts.map((post) => (
        <li key={post.slug}>
          <a href={`/blog/${post.slug}`}>{post.title}</a>
          <LikeButton />
        </li>
      ))}
    </ul>
  )
}
TSX
// app/blog/like-button.tsx: only this small component ships JavaScript
'use client'

import { useState } from 'react'

export function LikeButton() {
  const [liked, setLiked] = useState(false)

  return (
    <button type="button" aria-pressed={liked} onClick={() => setLiked((value) => !value)}>
      {liked ? 'Liked' : 'Like'}
    </button>
  )
}

รายการ การเข้าถึงข้อมูล และ markup ไม่เพิ่มภาระให้เบราว์เซอร์เลย มีแค่ปุ่มที่ต้อง hydrate สำหรับหน้าที่เน้นเนื้อหา วิธีนี้มักตัด JavaScript ฝั่ง client ออกไปได้เกือบทั้งหมด

เลือกกลยุทธ์การเรนเดอร์ให้ถูกต้อง

Next.js ให้คุณกำหนดได้ในแต่ละ route ว่าจะสร้าง HTML เมื่อไร การเลือกให้ถูกคือการตัดสินใจด้านประสิทธิภาพที่สำคัญที่สุดที่คุณทำ:

  • Static rendering: สร้าง HTML ตอน build แล้วเสิร์ฟจากแคชหรือ CDN เหมาะกับหน้าการตลาดและเอกสาร
  • Incremental Static Regeneration (ISR): หน้า static ที่รีเฟรชเมื่อครบเวลาที่กำหนดหรือเมื่อสั่งด้วย revalidatePath() เหมาะกับบล็อก แคตตาล็อก และหน้าที่ดึงเนื้อหาจาก CMS
  • Dynamic rendering: สร้าง HTML ทุกครั้งที่มีคำขอ จำเป็นสำหรับหน้าที่ปรับตามผู้ใช้ แดชบอร์ด และทุกอย่างที่ขึ้นกับ cookie
  • Streaming: ครอบส่วนที่ช้าของหน้าด้วย <Suspense> เพื่อให้โครงหน้าแสดงทันที และข้อมูลที่ช้าค่อยๆ สตรีมตามมาเมื่อพร้อม

route บล็อกแบบ dynamic ที่ถูกแคชและรีเฟรชทุกห้านาที พร้อม metadata ด้าน SEO แยกตามบทความ มีหน้าตาแบบนี้ โปรดสังเกตว่าใน Next.js เวอร์ชันปัจจุบัน params เป็น Promise:

TSX
// app/blog/[slug]/page.tsx
import type { Metadata } from 'next'
import { notFound } from 'next/navigation'
import { getPost } from '@/lib/posts'

type Props = { params: Promise<{ slug: string }> }

// Serve the cached page, regenerating it at most every five minutes
export const revalidate = 300

export async function generateMetadata({ params }: Props): Promise<Metadata> {
  const { slug } = await params
  const post = await getPost(slug)
  if (!post) return {}
  return {
    title: post.title,
    description: post.excerpt,
    openGraph: { type: 'article', title: post.title, images: [post.coverUrl] },
  }
}

export default async function PostPage({ params }: Props) {
  const { slug } = await params
  const post = await getPost(slug)
  if (!post) notFound() // renders the 404 page with the correct status code

  return (
    <article>
      <h1>{post.title}</h1>
      {/* … */}
    </article>
  )
}

TypeScript ที่คุ้มค่ากับการลงทุน

เปิดโหมด strict ตั้งแต่ commit แรก เพราะการมาเปิดภายหลังเจ็บปวดมาก noUncheckedIndexedAccess ก็ควรเปิดเช่นกัน เพราะมันบังคับให้คุณจัดการกรณีที่สมาชิกของอาร์เรย์หรือรายการใน map ไม่มีอยู่จริง:

JSON
{
  "compilerOptions": {
    "strict": true,
    "noUncheckedIndexedAccess": true,
    "noImplicitOverride": true
  }
}

อย่าลืมว่าไทป์จะหายไปตอนรัน ข้อมูลที่ข้ามขอบเขตของระบบ (อินพุตจากฟอร์ม ผลตอบกลับจาก API พารามิเตอร์ใน URL ตัวแปรสภาพแวดล้อม) ต้องถูกตรวจสอบความถูกต้อง ไม่ใช่แค่กำหนดไทป์ ไลบรารี schema อย่าง zod ให้ทั้งสองอย่างจากนิยามเดียว:

TypeScript
import { z } from 'zod'

export const PostSchema = z.object({
  slug: z.string().regex(/^[a-z0-9-]+$/),
  title: z.string().min(1).max(200),
  publishedAt: z.coerce.date(),
  tags: z.array(z.string()).default([]),
})

// One source of truth for the runtime check and the compile-time type
export type Post = z.infer<typeof PostSchema>

export function parsePost(input: unknown): Post {
  return PostSchema.parse(input) // throws a descriptive error for invalid data
}

สร้างโมเดลของสถานะอย่างชัดเจนด้วย discriminated union เช่น { status: 'loading' } | { status: 'error'; message: string } | { status: 'ready'; data: Post[] } แล้วคอมไพเลอร์จะบังคับให้ทุกคอมโพเนนต์จัดการสถานะกำลังโหลดและสถานะข้อผิดพลาด แทนที่จะคิดว่าทุกอย่างราบรื่นเสมอ

แก้ไขข้อมูลด้วย Server Actions

Server Actions ให้ฟอร์มเรียกฟังก์ชันบนเซิร์ฟเวอร์ได้โดยตรง โดยไม่ต้องเขียน API route เอง สะดวกก็จริง แต่เบื้องหลังมันคือ HTTP endpoint สาธารณะ จงปฏิบัติกับทุก action เหมือน API: ตรวจสอบอินพุต ตรวจสอบการยืนยันตัวตนและสิทธิ์บนเซิร์ฟเวอร์ และส่งข้อผิดพลาดที่มีโครงสร้างกลับไปให้ฟอร์มแสดงผล การซ่อนปุ่มบนหน้าจอไม่ใช่ความปลอดภัย

เช็กลิสต์ด้านประสิทธิภาพและการเข้าถึง

  • ใช้ next/image พร้อมแอตทริบิวต์ sizes ที่ถูกต้อง เพื่อไม่ให้มือถือโหลดรูปขนาดเดสก์ท็อป และกำหนด priority ให้เฉพาะรูปหลักที่อยู่ส่วนบนของหน้าจอ
  • โหลดฟอนต์ด้วย next/font เพื่อโฮสต์ฟอนต์เองและหลีกเลี่ยงการขยับของเลย์เอาต์
  • ดึงข้อมูลที่ไม่ขึ้นต่อกันแบบขนานด้วย Promise.all แทนการ await ต่อกันทีละขั้น
  • ทำให้ client component เล็กและอยู่ปลายกิ่ง และโหลดวิดเจ็ตหนักๆ อย่างแผนที่ กราฟ และเอดิเตอร์แบบ lazy
  • ติดตาม Core Web Vitals บน production ระดับ "ดี" หมายถึง Largest Contentful Paint ต่ำกว่า 2.5 วินาที Interaction to Next Paint ต่ำกว่า 200 มิลลิวินาที และ Cumulative Layout Shift ต่ำกว่า 0.1 ที่เปอร์เซ็นไทล์ที่ 75
  • ใช้ HTML เชิงความหมาย (<nav>, <main>, <article>, <button> จริงๆ) ใส่ label ให้ทุกช่องในฟอร์ม ทำให้สถานะโฟกัสมองเห็นได้เสมอ และตรวจสอบคอนทราสต์ของสี

ประเด็นสำคัญ

  • ใช้ Server Components เป็นค่าเริ่มต้น และเพิ่ม 'use client' เฉพาะจุดที่ต้องโต้ตอบ
  • เลือกกลยุทธ์การเรนเดอร์ราย route: static เมื่อทำได้ ISR สำหรับเนื้อหา และ dynamic เมื่อจำเป็นเท่านั้น
  • ใช้ TypeScript แบบ strict ควบคู่กับการตรวจสอบข้อมูลตอนรันที่ทุกขอบเขตของระบบ
  • วัดประสิทธิภาพจากผู้ใช้จริง และถือว่าการเข้าถึงได้เป็นส่วนหนึ่งของนิยามคำว่า "เสร็จ"

สรุป

ชุดเทคโนโลยี React ยุคใหม่ให้รางวัลกับทีมที่เข้าใจว่าโค้ดของตนทำงานอยู่ที่ไหน การเรนเดอร์บนเซิร์ฟเวอร์เป็นค่าเริ่มต้น การส่ง JavaScript เฉพาะสำหรับการโต้ตอบจริง และการบังคับใช้ไทป์กับการตรวจสอบข้อมูลที่ขอบเขตของระบบ จะทำให้แอปของคุณโหลดเร็ว ติดอันดับดี และยังแก้ไขได้ง่ายแม้ระบบจะเติบโตขึ้น

อ่านเพิ่มเติม

  • #Next.js
  • #React
  • #TypeScript
  • #Web Performance
เขียนโดย
ทีมงาน Givepo

Givepo คือบริษัทไอทีที่พัฒนาเว็บไซต์ แอปมือถือ และซอฟต์แวร์เฉพาะทาง เพื่อผลักดันธุรกิจของคุณให้ก้าวไปข้างหน้า

แชร์บทความนี้

ปรึกษาฟรี 30 นาที

พูดคุยเรื่องไอเดีย ขอบเขตงาน และงบประมาณกับทีมของเรา

นัดหมายปรึกษา

บทความอื่นๆ

บล็อก
พัฒนาเว็บ
·อ่าน 1 นาที

เว็บไซต์ หรือ เว็บแอป: ธุรกิจของคุณควรเลือกแบบไหน?

คำตอบนี้ส่งผลต่องบประมาณ ไทม์ไลน์ และเทคโนโลยี มาดูวิธีเลือกให้เหมาะกับธุรกิจของคุณก่อนเริ่มพัฒนา

ดูรายละเอียด
แบ็กเอนด์และ API
·อ่าน 7 นาที

อธิบายสถาปัตยกรรมแบ็กเอนด์: REST API, Microservices และฐานข้อมูลที่ขยายได้

วิธีออกแบบ REST API ที่นักพัฒนาชอบใช้ เมื่อไรที่ Microservices คุ้มค่ากับต้นทุน วิธีออกแบบฐานข้อมูลที่รับโหลดหนักได้ และรูปแบบที่ทำให้ระบบแบ็กเอนด์เร็วและเสถียร

ดูรายละเอียด
วิศวกรรมซอฟต์แวร์
·อ่าน 7 นาที

Clean Architecture และหลักการ SOLID: เขียนซอฟต์แวร์ที่ดูแลง่ายในระยะยาว

ทำไมโค้ดเบสจึงช้าลงเมื่อโตขึ้น และ Clean Architecture กฎการพึ่งพา และหลักการ SOLID ช่วยให้ตรรกะทางธุรกิจทดสอบง่ายและแก้ไขง่ายได้อย่างไร พร้อมตัวอย่าง TypeScript ที่สมบูรณ์

ดูรายละเอียด