進階路由

404 頁面處理

任何使用者都有可能造訪您網站上不存在的網址。舉例來說,如果使用者造訪 https://example.com/does-not-exist,則伺服器應回應 404 HTTP 狀態碼,並且網頁應該至少要有一些說明,而不是只顯示空白頁面。

對於任何指定的路由,Qwik City 都可以選擇如何以最佳方式處理使用者的 404 回應,無論是使用預設的 404 頁面、自訂的 404 頁面,還是動態產生的 404 頁面。

預設 404 頁面

Qwik City 預設會為任何未處理的路由提供一個通用的 404 頁面,而不是顯示空白頁面。當找不到自訂的 404 頁面時,就會轉而顯示預設的 404 頁面。建議您提供自訂的 404 頁面,以提供更好的使用者體驗。在自訂的 404 頁面中加入常見的頁首和導覽列,可以幫助使用者找到他們要找的頁面。

自訂 404 頁面

您可以使用與網站其他部分相同的熟悉版面配置,提供自訂的 404 頁面,而不是顯示通用的(無聊的)404 回應。

若要建立自訂的 404 頁面,請在根目錄 src/routes 下新增一個 404.tsx 檔案。

src/
└── routes/
    ├── 404.tsx            # Custom 404
    ├── layout.tsx         # Default layout
    └── index.tsx          # https://example.com/

在上面的範例中,404.tsx 頁面也會使用 layout.tsx 版面配置,因為它是相同目錄中版面配置的同層級檔案。

此外,使用 Qwik City 的目錄式路由,可以在不同的路徑建立自訂的 404 頁面。例如,如果在結構中也加入了 src/routes/account/404.tsx,則自訂的帳戶 404 頁面只會套用到 /account/* 路由,而所有其他 404 回應都會使用根目錄下的 404.tsx 頁面。

注意:在開發期間和預覽模式下,不會顯示自訂的 404 頁面,而是會顯示預設的 Qwik City 404 頁面。但是,在建置應用程式的正式版時,自訂的 404 頁面會以靜態的 404.html 檔案的形式產生。

src/
└── routes/
    ├── account/
       └── 404.tsx        # Custom Account 404
       └── index.tsx      # https://example.com/account/
    ├── 404.tsx            # Custom 404
    ├── layout.tsx         # Default layout
    └── index.tsx          # https://example.com/

值得注意的是,自訂的 404 頁面會在建置時靜態產生,產生一個靜態的 404.html 檔案,而不是個別進行伺服器端轉譯的頁面。這種策略可以減少 HTTP 伺服器的負擔,避免伺服器端轉譯 404 頁面,從而節省資源。

動態 404 頁面

轉譯頁面時,預設會一律回應 200 HTTP 狀態碼,這表示一切正常,路由也存在。但是,也可以在處理頁面轉譯的同時,手動將回應狀態碼設定為 200 以外的其他狀態碼,例如 404。

舉例來說,假設我們有一個產品頁面,其網址為 https://example.com/product/abc。產品頁面會使用 src/routes/product/[id]/index.tsx 目錄式路由來處理,而 [id] 是網址中的動態參數。

在此範例中,id 會用作從資料庫載入產品資料的索引鍵。如果找到了產品資料,那就太好了,我們會正確地顯示資料。但是,如果在資料庫中找不到產品資料,我們仍然可以處理頁面轉譯,但會改為回應 404 HTTP 狀態碼。

import { component$ } from '@builder.io/qwik';
import { routeLoader$ } from '@builder.io/qwik-city';
 
export const useProductLoader = routeLoader$(async ({ params, status }) => {
  // Example database call using the id param
  // The database could return null if the product is not found
  const data = await productDatabase.get(params.id);
 
  if (!data) {
    // Product data was not found
    // Set the status code to 404
    status(404);
  }
 
  // return the data (which may be null)
  return data;
});
 
export default component$(() => {
  // get the product data from the loader
  const product = useProductLoader();
 
  if (!product.value) {
    // no product data found
    // so render our own custom product 404
    return <p>Sorry, looks like we don't have this product.</p>;
  }
 
  // product data was found, so let's render it
  return (
    <div>
      <h1>{product.value.name}</h1>
      <p>{product.value.price}</p>
      <p>{product.value.description}</p>
    </div>
  );
});

群組版面配置

常見用途的路由通常會放在目錄中,以便共用版面配置,並將相關的原始程式碼檔案邏輯地分組在一起。但是,您可能會希望從公開的網址中排除用於分組類似檔案和共用版面配置的目錄。這就是「群組」版面配置(也稱為「無路徑」版面配置路由)的用途。

將任何目錄名稱放在括號中,例如 (name),則目錄名稱本身將不會包含在 URL 路徑名中。

舉例來說,假設一個應用程式將所有帳戶 路由放在一個目錄中。可以從 URL 中刪除 /account/,以獲得更簡潔、更短的 URL。在下面的範例中,請注意路徑位於 src/routes/(account)/ 目錄中,但 URL 路徑不包含 (account)/

src/
└── routes/
    └── (account)/             # Notice the parentheses
        ├── layout.tsx         # Shared account layout
        └── profile/
            └── index.tsx      # https://example.com/profile
        └── settings/
            └── index.tsx      # https://example.com/settings

命名版面配置

有時,相關路由需要與其同級路由具有截然不同的版面配置。可以透過使用單一預設版面配置和任意數量的命名版面配置,為不同的同級路由定義多個版面配置。子路由可以請求特定的命名版面配置。

Qwik City 定義了版面配置位於 src/routes 中且檔案名稱以 layout 開頭的慣例。這就是預設版面配置命名為 layout.tsx 的原因。命名版面配置也以 layout 開頭,後跟破折號 - 和唯一名稱,例如 layout-narrow.tsx

若要參考命名版面配置,路由的 index.tsx 檔案必須加上 @<name> 後綴。例如, 將使用 layout-narrow.tsx 版面配置。

src/
└── routes/
    ├── contact/
       └── [email protected]      # https://example.com/contact (Layout: layout-narrow.tsx)
    ├── layout.tsx                # Default layout
    ├── layout-narrow.tsx         # Named layout
    └── index.tsx                 # https://example.com/ (Layout: layout.tsx)
  • https://example.com/
    ┌──────────────────────────────────────────────────┐
    │       src/routes/layout.tsx                      │
    │  ┌────────────────────────────────────────────┐  │
    │  │    src/routes/index.tsx                    │  │
    │  │                                            │  │
    │  └────────────────────────────────────────────┘  │
    │                                                  │
    └──────────────────────────────────────────────────┘
  • https://example.com/contact
    ┌──────────────────────────────────────────────────┐
    │       src/routes/layout-narrow.tsx               │
    │  ┌────────────────────────────────────────────┐  │
    │  │    src/routes/contact/[email protected]     │  │
    │  │                                            │  │
    │  └────────────────────────────────────────────┘  │
    │                                                  │
    └──────────────────────────────────────────────────┘

巢狀版面配置

大多數情況下,希望將版面配置彼此巢狀。頁面的內容可以巢狀在多個包裝版面配置中,這由目錄結構決定。

src/
└── routes/
    ├── layout.tsx           # Parent layout
    └── about/
        ├── layout.tsx       # Child layout
        └── index.tsx        # https://example.com/about

在上面的範例中,有兩個版面配置應用於 /about 頁面元件周圍。

  1. src/routes/layout.tsx
  2. src/routes/about/layout.tsx

在這種情況下,版面配置將在頁面上彼此巢狀。

┌────────────────────────────────────────────────┐
│       src/routes/layout.tsx                    │
│  ┌──────────────────────────────────────────┐  │
│  │    src/routes/about/layout.tsx           │  │
│  │  ┌────────────────────────────────────┐  │  │
│  │  │ src/routes/about/index.tsx         │  │  │
│  │  │                                    │  │  │
│  │  └────────────────────────────────────┘  │  │
│  │                                          │  │
│  └──────────────────────────────────────────┘  │
│                                                │
└────────────────────────────────────────────────┘
src/routes/layout.tsx
import { component$, Slot } from '@builder.io/qwik';
 
export default component$(() => {
  return (
    <main>
      <Slot /> {/* <== Child layout/route inserted here */}
    </main>
  );
});
src/routes/about/layout.tsx
import { component$, Slot } from '@builder.io/qwik';
 
export default component$(() => {
  return (
    <section>
      <Slot /> {/* <== Child layout/route inserted here */}
    </section>
  );
});
src/routes/about/index.tsx
import { component$ } from '@builder.io/qwik';
 
export default component$(() => {
  return <h1>About</h1>;
});

上面的範例將呈現 html

<main>
  <section>
    <h1>About</h1>
  </section>
</main>

帶有 plugin@<name>.ts 的外掛

可以在 src/routes 目錄的根目錄中建立 plugin.tsplugin@<name>.ts 檔案,以便在根版面配置執行之前處理任何傳入請求。

您可以有多個 plugin.ts 檔案,每個檔案都有不同的名稱。例如,@<name> 是可選的,它僅供開發人員用於識別外掛。

請求處理程式,例如 onRequestonGetonPost 等,會在執行 server$ 函式之前被呼叫。

plugin.ts 檔案的執行順序

如果 plugin.ts 檔案存在並且它已匯出請求處理程式,則會先執行它們。

然後,從 plugin@<name>.ts 檔案匯出的請求處理程式會按照檔案名稱的字母順序執行。例如,來自 onGet 會在來自 onGet 之前執行,因為 auth 在字母順序上排在 security 之前。

最後,如果存在 server$ 函式,則會最後執行它。

貢獻者

感謝所有幫助改進此文件的貢獻者!

  • manucorporat
  • adamdbradley
  • cunzaizhuyi
  • the-r3aper7
  • mhevery
  • jakovljevic-mladen
  • vfshera
  • thejackshelton
  • wtlin1228
  • hamatoyogi
  • jemsco
  • patrickjs