Devahoy Logo

ตอนที่ 6 - การทำ Dynamic Routes

Published on

เขียนวันที่ : Jul 16, 2022

สอนเขียนโปรแกรมด้วย Next.js พร้อม Mini Workshop

Discord

ปกติแล้ว Routing ใน Next.js จะเป็นแบบ file-system เพียงแค่เราสร้างไฟล์ในโฟลเดอร์ pages เราก็จะได้หน้าเว็บนั้นๆ เช่น สร้างไฟล์ about.js url เราก็จะเป็น /about

ทีนี้ถ้าเรามีเว็บ ที่เป็น Blog หรือระบบสินค้า ที่ต้องมี Product สมมติ มี 50ตัว ต้องมาสร้างไฟล์ 50 ไฟล์ ไว้ใน pages ก็ดูจะเป็นงานที่ไม่น่าทำนัก โชคดีที่ Next.js รองรับการทำ Dynamic Route ครับ

Dynamic Page

คือการกำหนด Pattern ให้ Route ของเราเช่น /blog/:id หรือ /products/:id ซึ่ง ตัว :id จะเป็นอะไรก็ได้ แล้วแต่เรา ข้อดีคือ เราใช้แค่ไฟล์เดียว

โดยตัว :id เราเรียกมันว่า param ฉะนั้น สำหรับ Next.js เราก็แค่สร้างไฟล์ชื่อว่า [id].js ตัวอย่างเช่น ผมสร้างไฟล์ products/[id].js ใน pages

pages/blog/[id].js
import { useRouter } from 'next/router'

const Blog = () => {
  const router = useRouter()
  const { id } = router.query

  return <p>Blog ID : {id}</p>
}

export default Blog 

ทีนี้ไม่ว่า url เราจะเป็น /blog/1, /blog/100 หรืออะไรก็แล้วแต่ เราก็จะได้หน้าเว็บที่แสดง Blog ID ที่แตกต่างกัน ตามที่เราต้องการ ตัว Next.js สามารถอ่าน query.id (ชื่อเดียวกับไฟล์ ที่ตั้งด้วย [])

Catch-all Routes

ตัว Dynamic routes เรายังสามารถ ดักจับ path ทั้งหมด ได้ด้วยการใช้ 3จุด (...) ข้างใน [] ตัวอย่างเช่น

  • pages/products/[...id].js จะ match ทั้ง /products/1, /products/1/2/3, /products/1/detail อะไรก็แล้วแต่ เรียกได้ว่า เข้าเงื่อนไขหมด

ทดลองสร้างไฟล์ขึ้นมา สมมติชื่อ [...id].js ไว้ในโฟลเดอร์ products ข้างใน pages

pages/products/[...id].js
import { useRouter } from 'next/router'

const Product = () => {
  const router = useRouter()
  const { id } = router.query

  return <p>Product ID : {id?.join(' - ')}</p>
}

export default Product

ซึ่งถ้าเราเรียก url /products/1/2 ตัว id เราก็จะมีค่า

id: ['1', '2']

อ่านเพิ่มเติมเรื่อง Dynamimc Routes

Fetch API + Dynamic Routes

ตัวอย่าง ลองทำ Dynamic Routes และแต่ละหน้า ก็ทำการ fetch api ที่แตกต่างกัน เช่น

  • /posts/1 ก็ให้ fetch api https://jsonplaceholder.typicode.com/posts/1
  • /posts/2 ก็ fetch https://jsonplaceholder.typicode.com/posts/2

โดยที่เราไม่ต้องมาเขียนโค๊ดทุกๆไฟล์

เริ่มต้น สร้างไฟล์ posts/[id.js] ขึ้นมา

pages/posts/[id.js]
const Post = ({ post }) => {
  return (
    <div>
      <h2>{post.title}</h2>
      <p>{post.body}</p>
    </div>
  )
}

export async function getStaticProps({ params }) {
  const id = params.id

  const res = await fetch(`https://jsonplaceholder.typicode.com/posts/${id}`)
  const post = await res.json()

  return {
    props: {
      post,
    },
  }
}

export default Post

จากโค๊ด จะเห็นว่าเราใช้ params จาก getStaticProps เพื่อ fetch /posts/1, /posts/2 ขึ้นอยู่กับ url ทีนี้เมื่อเราลองรัน yarn dev เราจะเจอ error ประมาณนี้

Server Error
Error: getStaticPaths is required for dynamic SSG pages and is missing for '/posts/[id]'.
Read more: https://nextjs.org/docs/messages/invalid-getstaticpaths-value

คือโหมด SSG เราจำเป็นต้องใช้ getStaticPaths

getStaticPaths

getStaticPaths ต้อง return object เพื่อบอกว่าจะให้ path (url) ไหน ที่ทำการ pre-render บ้าง ซึ่ง object ก็จะอยู่ในรูปแบบนี้

return {
  paths: [{ params: { id: 1 }}, { params: { id: 2 }}],
  fallback: false
}

ซึ่งถ้า getStaticPaths return paths มาแบบนี้ ตัว Next.js ก็จะรู้ละว่ามี params 1 กับ 2 ก็จะทำการ generate /posts/1 และ /posts/2 ให้ เวลาเรา next build โดยใช้ Page component ไฟล์ /pages/posts/[id].js นั่นเอง ถ้า page เราชื่อ /pages/posts/[postId].js ตัวที่ return ก็ต้องเป็น path: [{ params: { postId: 1 }}]

  • fallback: false - เป็นการบอกว่า ถ้ามันไม่เจอ path เช่น /posts/1000 มันก็จะทำการ return 404 Page

กลับไปที่ก่อนหน้านี้ ที่ไฟล์ posts/[id.js] ทำการเพิ่ม getStaticPaths ให้มัน แบบนี้

pages/posts/[id].js
export async function getStaticPaths() {
  const url = `https://jsonplaceholder.typicode.com/posts`
  const res = await fetch(url)
  const posts = await res.json()

  const paths = posts.map((post) => {
    return {
      params: { id: String(post.id) },
    }
  })

  return {
    paths,
    fallback: false,
  }
}

จะเห็นว่า เราไม่ต้องมากำหนด paths: params ทีละตัวครับ เราใช้วิธี fetch data เพื่อได้ posts ทั้งหมด จากนั้น loop เอาแค่ id มา เมื่อได้ id ก็กลายเป็น params: {id} นั่นเอง (สังเกตผมใช้ String(id) เพื่อเปลี่ยนจาก number เป็น string)

ทดสอบรัน yarn dev และลองเข้าหน้าเว็บดูใหม่

จบแล้วสำหรับเรื่อง Dynamic Routes ตอนต่อไป เป็นเรื่อง API Routs ต่อนะครับ

คำถาม

  • fallback ใน getStaticPaths มีกี่แบบ?
  • ถ้าเรากำหนด catch-all routes [...id].js เราจะใช้ getStaticPaths ตัว params จะเป็นแบบไหน? number, string, array?
  • catch-all routes เป็น optional ได้มั้ย?
Discord