การใช้งาน Prefetching ใน Next.js

การดึงข้อมูลล่วงหน้า (Prefetching) ช่วยให้การนำทางระหว่างเส้นทาง (routes) ต่าง ๆ ภายในแอปพลิเคชันให้ความรู้สึกเหมือนเป็นแบบทันที

.

Next.js จะพยายามดึงข้อมูลล่วงหน้าอย่างชาญฉลาดโดยอัตโนมัติ โดยอิงจากลิงก์ที่ใช้ในโค้ดแอปพลิเคชันของเรา

.

การทำงานของ Prefetching คืออะไร?

เมื่อมีการนำทางระหว่างเส้นทาง (routes) เบราว์เซอร์จะร้องขอ asset ที่จำเป็นสำหรับหน้า เช่น ไฟล์ HTML และ JavaScript การ prefetching คือกระบวนการร้องขอ resource เหล่านี้ล่วงหน้า ก่อน ที่จะมีการนำทางไปยัง route ใหม่

.

Next.js จะแบ่งแอปพลิเคชันออกเป็น JavaScript chunks ขนาดเล็กตามเส้นทาง แทนที่จะโหลดโค้ดทั้งหมดตั้งแต่ต้นเหมือน SPA แบบเดิม ๆ จะโหลดเฉพาะโค้ดที่จำเป็นสำหรับ route ปัจจุบันเท่านั้น ซึ่งจะช่วยลดเวลาโหลดเริ่มต้น ในขณะที่ส่วนอื่นของแอปจะถูกโหลดแบบเบื้องหลัง เมื่อผู้ใช้คลิกลิงก์ resource สำหรับเส้นทางใหม่ก็ถูกโหลดไว้ในแคชของเบราว์เซอร์แล้ว

.

ดังนั้น เมื่อมีการนำทางไปยังหน้าใหม่ จะไม่มีการ reload หน้าแบบเต็มหรือเห็น loading spinner ของเบราว์เซอร์ Next.js จะทำการเปลี่ยนหน้าแบบ client-side ซึ่งให้ประสบการณ์การใช้งานที่รวดเร็วเหมือนเป็นแอปพลิเคชันแบบ SPA

.

ในระหว่างการนำทางครั้งแรก เบราว์เซอร์จะโหลด HTML, JavaScript และ React Server Components (RSC) Payload สำหรับการนำทางถัดไป เบราว์เซอร์จะโหลดเฉพาะ RSC Payload ของ Server Components และ JS bundle ของ Client Components

.

การทำงานของ prefetching ที่ใช้งานที่บ่อยๆ ได้แก่

.

1. การ prefetch อัตโนมัติ (Automatic prefetch)

ตัวอย่าง:

import Link from ‘next/link’

export default function NavLink() {

return <Link href=”/about”>About</Link>

}

.

Context: หากไม่มี loading.js จะ prefetch ทั้งหน้า

Prefetched payload: ทั้งหน้า

Client Cache TTL: จนกว่าแอปจะ reload ใหม่

หากมี loading.js จะ prefetch เฉพาะ layout จนถึง loading boundary แรก (ค่า TTL 30 วินาที – สามารถปรับแต่งได้)

.

หมายเหตุ:

Automatic prefetching จะทำงานเฉพาะในโหมด production เท่านั้น สามารถปิดด้วย prefetch={false} หรือใช้ wrapper

.

2. การ prefetch แบบกำหนดเอง (Manual prefetch)

สามารถเรียกใช้ router.prefetch() เพื่อสร้างเส้นทางที่อยู่นอก viewport หรือเพื่อตอบสนองต่อ event ต่าง ๆ เช่น การวิเคราะห์ข้อมูล, การ hover, การ scroll เป็นต้น

ตัวอย่าง:

‘use client’

import { useRouter } from ‘next/navigation’

const router = useRouter()

router.prefetch(‘/pricing’)

.

3. Prefetch เมื่อ hover (Hover-triggered prefetch)

การขยาย Link ด้วยตัวเองหมายถึงเราต้องรับผิดชอบในเรื่อง prefetching, การ invalidation ของ cache และการเข้าถึงสำหรับผู้ใช้ (Accessibility) ควรใช้เฉพาะกรณีที่ default ยังไม่เพียงพอ

ตัวอย่างเมื่อเราต้องการ prefetch เมื่อ hover (แทนที่จะ prefetch เมื่อเข้า viewport ตามปกติ):

‘use client’

import Link from ‘next/link’

import { useState } from ‘react’

export function HoverPrefetchLink({

href,

children,

}: {

href: string

children: React.ReactNode

}) {

const [active, setActive] = useState(false)

return (

<Link

href={href}

prefetch={active ? null : false}

onMouseEnter={() => setActive(true)}

>

{children}

</Link>

)

}

prefetch={null} จะคืนค่า default prefetching (แบบ static) เมื่อตรวจพบ user intent (เมื่อ hover)

.

4. การขยายหรือปรับแต่ง Link (Extending or ejecting link)

เราสามารถขยาย <Link> เพื่อกำหนดกลยุทธ์ prefetching ของเราเอง เช่น ใช้ไลบรารีอย่าง ForesightJS เพื่อ prefetch link โดยทำนายทิศทางของเมาส์

หรือใช้ useRouter เพื่อสร้างพฤติกรรมบางอย่างของ <Link> เอง (แต่เราจะต้องรับผิดชอบเรื่อง prefetch และ cache ด้วยตัวเอง):

‘use client’

import { useRouter } from ‘next/navigation’

import { useEffect } from ‘react’

function ManualPrefetchLink({

href,

children,

}: {

href: string

children: React.ReactNode

}) {

const router = useRouter()

useEffect(() => {

let cancelled = false

const poll = () => {

if (!cancelled) router.prefetch(href, { onInvalidate: poll })

}

poll()

return () => {

cancelled = true

}

}, [href, router])

return (

<a

href={href}

onClick={(event) => {

event.preventDefault()

router.push(href)

}}

>

{children}

</a>

)

}

.

onInvalidate จะถูกเรียกเมื่อ Next.js พบว่า cached data เริ่มหมดอายุ เราสามารถ refresh prefetch ได้

หมายเหตุ: การใช้ <a> tag จะทำให้เกิดการนำทางหน้าใหม่เต็มรูปแบบ เว้นแต่เราจะใช้ event.preventDefault() และ router.push() แทน

.

5. การปิด prefetch (Disabled prefetch)

เราสามารถปิด prefetch สำหรับบาง route ได้ เพื่อควบคุมการใช้ resource ให้เหมาะสม

ตัวอย่าง:

‘use client’

import Link, { LinkProps } from ‘next/link’

function NoPrefetchLink({

prefetch,

…rest

}: LinkProps & { children: React.ReactNode }) {

return <Link {…rest} prefetch={false} />

}

.

กรณีนี้เหมาะกับการใช้ลิงก์ใน footer หรือส่วนที่ไม่จำเป็นต้อง prefetch

.

การแก้ไขปัญหา (Troubleshooting)

Side-effect ที่ไม่พึงประสงค์ระหว่าง prefetch

ถ้า layout หรือหน้าเพจของเรามี side-effect (เช่น track analytics) สิ่งเหล่านี้อาจจะถูก trigger ตั้งแต่ตอน prefetch ไม่ใช่ตอนผู้ใช้เข้าจริง

แนวทางแก้ไข:

ควรย้าย side-effect ไปไว้ใน useEffect hook หรือ Server Action ที่ trigger จาก Client Component

ก่อนแก้ไข:

import { trackPageView } from ‘@/lib/analytics’

export default function Layout({ children }: { children: React.ReactNode }) {

// เรียกใช้งานตั้งแต่ตอน prefetch

trackPageView()

return <div>{children}</div>

}

.

หลังแก้ไข:

app/ui/analytics-tracker.tsx

‘use client’

import { useEffect } from ‘react’

import { trackPageView } from ‘@/lib/analytics’

export function AnalyticsTracker() {

useEffect(() => {

trackPageView()

}, [])

return null

}

app/dashboard/layout.tsx

import { AnalyticsTracker } from ‘@/app/ui/analytics-tracker’

export default function Layout({ children }: { children: React.ReactNode }) {

return (

<div>

<AnalyticsTracker />

{children}

</div>

)

}

.

การป้องกัน prefetch ที่มากเกินไป

Next.js จะ prefetch ลิงก์ใน viewport อัตโนมัติเมื่อใช้ <Link> component หากมีลิงก์จำนวนมาก (เช่น infinite scroll table) อาจต้องการปิด prefetch เพื่อลด resource

ตัวอย่าง:

<Link prefetch={false} href={`/blog/${post.id}`}>

{post.title}

</Link>

ในกรณีนี้ route แบบ static จะถูกโหลดเมื่อคลิกเท่านั้น และ dynamic routes จะรอการ render จาก server ก่อนนำทาง

หากต้องการลดการใช้ resource โดยไม่ปิด prefetch ทั้งหมด สามารถใช้แนวทาง prefetch เมื่อ hover (target เฉพาะลิงก์ที่ user มีแนวโน้มจะคลิก):

‘use client’

import Link from ‘next/link’

import { useState } from ‘react’

export function HoverPrefetchLink({

href,

children,

}: {

href: string

children: React.ReactNode

}) {

const [active, setActive] = useState(false)

return (

<Link

href={href}

prefetch={active ? null : false}

onMouseEnter={() => setActive(true)}

>

{children}

</Link>

)

}

.

สรุป Next.js ให้ความยืดหยุ่นในการจัดการ prefetch เพื่อสร้างประสบการณ์ผู้ใช้ที่เร็วและลื่นไหล พร้อมทั้งมีทางเลือกในการปรับแต่งและป้องกันปัญหาที่อาจเกิดจากการ prefetch มากเกินไป ลองเอาไปใช้ดูนะครับ

โค้ชเอก

Principal Technical Coach

ใส่ความเห็น

อีเมลของคุณจะไม่แสดงให้คนอื่นเห็น ช่องข้อมูลจำเป็นถูกทำเครื่องหมาย *

This site uses Akismet to reduce spam. Learn how your comment data is processed.