繼續介紹一些除了 page 以外 App Router 會識別的特殊文件名稱。
現在 Next.js 預設 page 都是 Server Component,而當 Server Component 中在做資料請求或處理導致沒有即時送回元件的話,就能用 loading 暫時顯示一個載入狀態。
例如一個叫 console 的頁面結構如下。
console
├── loading.tsx
└── page.tsx
// page.tsx
export default async function Console() {
await new Promise(resolve => setTimeout(resolve, 3000));
return (
<div>
<h1>Welcome to Console!</h1>
</div>
);
}
page 中夾了一個測試用的載入 Promise ,讓元件延遲送回。
// loading.tsx
import { CircularProgress } from '@mui/material';
export default async function Loading() {
return <CircularProgress />;
}
這樣在元件送回來前就會先顯示一個載入圈圈。
loading 裡實作的是 React 的 Suspense 功能。
如果頁面在請求資料時拋出了錯誤且沒被處理的話,就會使用 error 的元件來取代。
console
├── error.tsx
├── loading.tsx
└── page.tsx
// page.tsx
export default async function Console() {
await new Promise((resolve, reject) =>
setTimeout(() => reject(new Error('Console Error!')), 3000),
);
return (
<div>
<h1>Welcome to Console!</h1>
</div>
);
}
給剛剛的範例做點修改,改成載入結束後拋出錯誤。
// error.tsx
'use client';
import { useEffect, useState } from 'react';
export default function Error({
error,
reset,
}: {
error: Error;
reset: () => void;
}) {
const [message, setMessage] = useState(error.message);
useEffect(() => {
setMessage(error.message);
}, [error]);
return (
<div>
<h2>{message}</h2>
</div>
);
}
error 元件會被傳入錯誤資訊的 prop ,可以從中提取資料。
error 實作的是 React 的 ErrorBoundary 功能。
另 error 只能用 Client Component,不能是 Server Component。
又 error 產生的 ErrorBoundary 只會包著 page 元件,然後 layout 包在外面,所以 layout 的錯誤並不回被處理。但 layout 發生的錯誤會被更上層的 error 元件捕捉。
<Layout>
<ErrorBoundary fallback={<Error/>}>
<Page/>
</ErrorBoundary>
</Layout>
因為 error 不能處理 layout 的錯誤,那最上層的 layout 假如發生了錯誤,該怎麼辦呢?
這裡就得定義一個特殊的 error handler。
app
├── global-error.tsx
├── layout.tsx
└── page.tsx
global-error
是最外圍的防線,基本上構造跟一般 error 相同,只是 global-error
為了能夠取代 layout,必須要有 html 跟 body 的標籤。
'use client';
export default function GlobalError({
error,
reset,
}: {
error: Error;
reset: () => void;
}) {
return (
<html lang="en">
<body id="__next">
<h2>Something went wrong!</h2>
<button onClick={() => reset()}>Try again</button>
</body>
</html>
);
}
當沒有對應的路由時的替換頁面,放在 app 底下的話就是取代預設的 404 頁面。
app
├── page.tsx
└── not-found.tsx
動態路由中也能用,只是只有當動態路由的頁面使用 notFound
函式時才會導向 not-found 頁面。
user
└── [id]
├── not-found.tsx
└── page.tsx
// page.tsx
import { notFound } from 'next/navigation';
export interface IdProps {
params: {
id: string;
};
}
export default function User({ params: { id } }: IdProps) {
if (id !== '1') {
notFound();
}
return (
<div>
<h1>Welcome User {id}!</h1>
</div>
);
}