在前面的章節已經介紹了 Next.js 是 file-based routing 的架構,在路由至其他頁面時,通常會使用到 <Link />
這個 component,這個 component 提供不少的 props 可以針對不同的情況做設定,今天我們要了解 prefetch
這個 props 的功用,它可以讓切換頁面更有效率。
要注意的是
prefetch
只在 production 環境有用!
也許你會需要修改 global.css: https://gist.github.com/leochiu-a/5276029a65017c660bb91dcba6bab53c
一個很常見的起手式,我們使用 create-next-app
以 typescript 啟動一個 Next.js 的專案:
$ npx create-next-app --typescript
創建完後,你可以在 pages/
這個資料夾中看到類似以下的資料夾結構,原本預設的檔案沒有 products.tsx
,這是後來加上去的,我們將使用 /
與 /products
兩個頁面來做實驗:
我們將 pages/index.tsx
的內容改成以下這個模樣,首頁中的內容只有包含一個 <Link>
,可以點擊路由到 /products
頁面。在前面的章節介紹過 <Link>
裡面的 children 只能是 string
或是 HTML 的 <a>
,如果使用其他的 tag 則會報錯:
import type { NextPage } from "next";
import Link from "next/link";
const Home: NextPage = () => {
return (
<Link href="/products">
<a>link to products</a>
</Link>
);
};
export default Home;
接著看到 pages/products.tsx
的內容,它是一個 SSR 的頁面,透過 getServerSideProps
傳遞一個 products
的字串,並在 component 中渲染這個字串,非常地單純:
interface Props {
products: string;
}
const Products = ({ products }: Props) => {
return <div>{products}</div>;
};
export const getServerSideProps = async () => {
return {
props: { products: "products" },
};
};
export default Products;
各位讀者要先知道「 prefetch
只在 production 環境有用」,亦即使用 yarn dev
啟動開發用伺服器什麼事情都不會發生,我們要使用 yarn build
與 yarn start
將 Next.js 應用打包後,再啟動伺服器:
$ yarn build
$ yarn start
接著你可以在瀏覽器中看到 [http://localhost:3000](http://localhost:3000)
中的首頁內容如下:
使用 Chrome 的 devtool 看看 /prodcuts
的 chunk 是不是已經被預先載入了:
cmd + option + i
)你可以看到 /products
的 chunk 已經被預先下載,而且 Size 的欄位是 prefetch cache,意思是會儲存在 Chrome 的 prefetch cache 中:
/products
的 chunk 可以預先載入?Next.js 之所以可以做到這件事要歸功於 HTML 的 <link>
,在 <head>
加上以下這種寫法,可以「預先載入未來可能被用到的資源」:
<link rel="prefetch" href="/images/big.jpeg" />
在 pages/index.tsx
這個頁面中可以看到加入了 <Link href='/products'>
的內容,而 Next.js 知道使用者可能會點擊連結切換到 /products
這個頁面,所以便讓這個頁面成為會被「prefetch」的資源。
/products
頁面被 prefetch在 Next.js 9 以後,使用 <Link>
都會被預設加入到 prefetch 的資源中,如同上面看到的例子,雖然我們沒有在 <Link>
傳入 prefetch={true}
,但是 /products
這個頁面仍然會被加入到 prefetch 的資源中。
但是有時候並不是所有使用 <Link>
的頁面都需要被 prefetch,所以很直覺地在 <Link>
上傳入 prefetch={false}
,將會使得該頁面不會被 prefetch。
我們以 pages/index.tsx
這個頁面中的 <Link>
為例,修改頁面中的內容:
<Link href="/products" prefetch={false}>
<a>link to products</a>
</Link>
同樣執行 yarn build
與 yarn start
後,瀏覽該頁面的 Elements tab,會發現在 <head>
中找不到 /products
被預先載入的訊息。
當然,如果再切換到 Network tab,就看不到 /products
的 chunk 被載入。
<Link>
會觸發 prefetch 嗎?在頁面上總是會有一些 <Link>
並非是一開始就渲染在頁面上,而是使用者跟頁面互動過後,達成某些條件才渲染在畫面上,讀者們也許就會有這樣的問題「條件式渲染的 <Link>
也可以讓頁面的 chunk 被 prefetch 嗎?」
我們來實驗看看,修改 pages/index.tsx
中的內容,以條件式渲染的方式顯示 <Link>
,在使用者點擊按鈕後, <Link>
才會出現在畫面上:
const Home: NextPage = () => {
const [visible, setVisible] = useState(false);
return (
<div>
{visible && (
<Link href="/products">
<a>link to products</a>
</Link>
)}
<button onClick={() => setVisible(true)}>show link</button>
</div>
);
};
同樣執行 yarn build
與 yarn start
後,瀏覽該頁面的 Elements tab,會發現在 <head>
中找不到 /products
被預先載入的訊息。
但是,在點擊「show link」的按鈕之後,你會發現 <head>
中動態載入了以下內容,讓 /prodcuts
的 chunk 成為會被預先載入的資源:
此時再打開 Network tab,原本沒有預先載入的 products chunk 資源,也在 <link>
被設定後被載入儲存到 prefetch cache 中。
router.push
的自定義路由也能夠 prefetch 嗎?在有些情況 <Link>
也許不能夠滿足需求,必須使用 next/router
的 useRouter
在切換頁面之前執行一些操作,例如驗證表單、 GA 事件等等,在這種情況要怎麼 prefetch 頁面呢?
可以使用 router.prefetch
將指定的頁面作為 prefetch 的頁面,這種做法跟 conditional rendering 的狀況有些相似,從「檢視原始碼」中會發現原本的頁面中是不包含 <link rel="prefetch">
的資源,但是在頁面載入之後, router.prefetch
再動態地指定 prefetch 的資源,讓瀏覽器自動預先抓取資源。
const router = useRouter();
useEffect(() => {
router.prefetch("/products");
}, [router]);
要特別注意的是,
router.prefetch
在yarn dev
下不會起作用。
以下為 pages/index.tsx
的範例程式,在頁面載入時動態地 prefetch /products
的頁面 chunk,在點擊按鈕後觸發 handleClick()
後,此時 /products
頁面已經被預先載入,所以就可以有效縮減 /products
的頁面載入時間
const Home: NextPage = () => {
const router = useRouter();
useEffect(() => {
router.prefetch("/products");
}, [router]);
const handleClick = () => {
router.push("/products");
};
return (
<div>
<button onClick={handleClick}>to /products</button>
</div>
);
};
在這篇文章中,我們了解了 Next.js 的 prefetch 機制,並且知道 <Link>
在預設的情況下都會 prefetch 頁面。如果是在 conditional rendering 的頁面中,只要 <Link>
被渲染後,也可以動態地 prefetch 指定的頁面。
而有時候 <Link>
無法滿足我們的需求,會使用到 router.push
這種自定義路由的方式,此時可以使用 router.prefetch
達到 prefetch 頁面的效果。
最後,再次提醒不論是 <Link>
或是 router.prefetch
,prefetch 這個特性只有在 production 的環境下才能使用, yarn dev
是無法觸發 prefetch 資源的 。