การดึงข้อมูลล่วงหน้า (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 มากเกินไป ลองเอาไปใช้ดูนะครับ
Views: 11