特殊檔案
本頁面

特殊檔案

React Router 會在您的專案中尋找一些特殊檔案。並非所有這些檔案都是必需的

react-router.config.ts

此檔案為選用

設定檔用於配置應用程式的某些方面,例如您是否正在使用伺服器端渲染、某些目錄的位置等等。

import type { Config } from "@react-router/dev/config";

export default {
  // Config options...
} satisfies Config;

有關更多資訊,請參閱 react-router config API 的詳細資訊。

root.tsx

此檔案為必要

「根」路由 (app/root.tsx) 是您的 React Router 應用程式中唯一必要的路由,因為它是 routes/ 目錄中所有路由的父項,並且負責渲染根 <html> 文件。

由於根路由管理您的文件,因此它是渲染 React Router 提供的一些「文件層級」元件的適當位置。這些元件應在您的根路由內使用一次,並且它們包含 React Router 為了使您的頁面正確渲染而計算或建構的所有內容。

import type { LinksFunction } from "react-router";
import {
  Links,
  Meta,
  Outlet,
  Scripts,
  ScrollRestoration,
} from "react-router";

import "./global-styles.css";

export default function App() {
  return (
    <html lang="en">
      <head>
        <meta charSet="utf-8" />
        <meta
          name="viewport"
          content="width=device-width, initial-scale=1"
        />

        {/* All `meta` exports on all routes will render here */}
        <Meta />

        {/* All `link` exports on all routes will render here */}
        <Links />
      </head>
      <body>
        {/* Child routes render here */}
        <Outlet />

        {/* Manages scroll position for client-side transitions */}
        {/* If you use a nonce-based content security policy for scripts, you must provide the `nonce` prop. Otherwise, omit the nonce prop as shown here. */}
        <ScrollRestoration />

        {/* Script tags go here */}
        {/* If you use a nonce-based content security policy for scripts, you must provide the `nonce` prop. Otherwise, omit the nonce prop as shown here. */}
        <Scripts />
      </body>
    </html>
  );
}

Layout 導出

根路由支援所有路由模組導出

根路由也支援額外的選用 Layout 導出。Layout 元件有 2 個用途

  1. 避免在您的根元件、HydrateFallbackErrorBoundary 中重複您的文件的「應用程式外殼」
  2. 防止 React 在根元件/HydrateFallback/ErrorBoundary 之間切換時重新掛載您的應用程式外殼元素,如果 React 從您的 <Links> 元件中移除並重新新增 <link rel="stylesheet"> 標籤,則可能會導致 FOUC。
export function Layout({ children }) {
  return (
    <html lang="en">
      <head>
        <meta charSet="utf-8" />
        <meta
          name="viewport"
          content="width=device-width, initial-scale=1"
        />
        <Meta />
        <Links />
      </head>
      <body>
        {/* children will be the root Component, ErrorBoundary, or HydrateFallback */}
        {children}
        <Scripts />
        <ScrollRestoration />
      </body>
    </html>
  );
}

export default function App() {
  return <Outlet />;
}

export function ErrorBoundary() {}

關於 Layout 元件中的 useLoaderData 的注意事項

useLoaderData 不允許在 ErrorBoundary 元件中使用,因為它旨在用於 happy-path 路由渲染,並且其類型定義內建假設 loader 成功執行並傳回了某些內容。該假設在 ErrorBoundary 中不成立,因為可能是 loader 拋出並觸發了邊界!為了在 ErrorBoundary 中存取 loader 資料,您可以使用 useRouteLoaderData,它會考慮 loader 資料可能為 undefined 的情況。

由於您的 Layout 元件用於成功和錯誤流程,因此此限制同樣適用。如果您需要在 Layout 中根據請求是否成功來分支邏輯,則可以使用 useRouteLoaderData("root")useRouteError()

由於您的 <Layout> 元件用於渲染 ErrorBoundary,因此您應該非常謹慎,以確保您可以渲染 ErrorBoundary 而不會遇到任何渲染錯誤。如果您的 Layout 在嘗試渲染邊界時拋出另一個錯誤,則無法使用它,並且您的 UI 將回退到非常簡化的內建預設 ErrorBoundary

export function Layout({
  children,
}: {
  children: React.ReactNode;
}) {
  const data = useRouteLoaderData("root");
  const error = useRouteError();

  return (
    <html lang="en">
      <head>
        <meta charSet="utf-8" />
        <meta
          name="viewport"
          content="width=device-width, initial-scale=1"
        />
        <Meta />
        <Links />
        <style
          dangerouslySetInnerHTML={{
            __html: `
              :root {
                --themeVar: ${
                  data?.themeVar || defaultThemeVar
                }
              }
            `,
          }}
        />
      </head>
      <body>
        {data ? (
          <Analytics token={data.analyticsToken} />
        ) : null}
        {children}
        <ScrollRestoration />
        <Scripts />
      </body>
    </html>
  );
}

routes.ts

此檔案為必要

routes.ts 檔案用於配置哪些 URL 模式與哪些路由模組匹配。

import {
  type RouteConfig,
  route,
} from "@react-router/dev/routes";

export default [
  route("some/path", "./some/file.tsx"),
  // pattern ^           ^ module file
] satisfies RouteConfig;

有關更多資訊,請參閱路由指南

entry.client.tsx

此檔案為選用

預設情況下,React Router 將為您處理在客戶端上水合您的應用程式。您可以使用以下方式顯示預設的 entry client 檔案

react-router reveal

此檔案是瀏覽器的進入點,負責水合由伺服器在您的伺服器 entry 模組中產生的標記,但是您也可以在此處初始化任何其他客戶端程式碼。

import { startTransition, StrictMode } from "react";
import { hydrateRoot } from "react-dom/client";
import { HydratedRouter } from "react-router/dom";

startTransition(() => {
  hydrateRoot(
    document,
    <StrictMode>
      <HydratedRouter />
    </StrictMode>
  );
});

這是瀏覽器中執行的第一段程式碼。您可以初始化客戶端函式庫、新增僅限客戶端的提供者等等。

entry.server.tsx

此檔案為選用

預設情況下,React Router 將為您處理產生 HTTP Response。您可以使用以下方式顯示預設的 entry server 檔案

react-router reveal

此模組的 default 導出是一個函式,可讓您建立回應,包括 HTTP 狀態、標頭和 HTML,讓您可以完全控制標記的產生方式和傳送到客戶端的方式。

此模組應使用 <ServerRouter> 元素以及目前請求的 contexturl 渲染目前頁面的標記。一旦 JavaScript 在瀏覽器中使用client entry 模組載入後,此標記將(可選地)重新水合。

streamTimeout

如果您正在串流回應,您可以導出一個選用的 streamTimeout 值(以毫秒為單位),該值將控制伺服器等待串流承諾解決的時間量,然後拒絕未完成的承諾並關閉串流。

建議將此值與中止 React 渲染器的超時時間解耦。您應始終將 React 渲染超時時間設定為較高的值,以便它有時間從您的 streamTimeout 中串流傳輸底層的拒絕。

// Reject all pending promises from handler functions after 10 seconds
export const streamTimeout = 10000;

export default function handleRequest(...) {
  return new Promise((resolve, reject) => {
    // ...

    const { pipe, abort } = renderToPipeableStream(
      <ServerRouter context={routerContext} url={request.url} />,
      { /* ... */ }
    );

    // Abort the streaming render pass after 11 seconds to allow the rejected
    // boundaries to be flushed
    setTimeout(abort, streamTimeout + 1000);
  });
}

handleDataRequest

您可以導出一個選用的 handleDataRequest 函式,該函式將允許您修改資料請求的回應。這些是不渲染 HTML 的請求,而是在客戶端水合發生後將 loader 和 action 資料傳回瀏覽器。

export function handleDataRequest(
  response: Response,
  {
    request,
    params,
    context,
  }: LoaderFunctionArgs | ActionFunctionArgs
) {
  response.headers.set("X-Custom-Header", "value");
  return response;
}

handleError

預設情況下,React Router 會將遇到的伺服器端錯誤記錄到控制台。如果您想要更精細地控制記錄,或者也想將這些錯誤報告給外部服務,那麼您可以導出一個選用的 handleError 函式,該函式將讓您進行控制(並將停用內建的錯誤記錄)。

export function handleError(
  error: unknown,
  {
    request,
    params,
    context,
  }: LoaderFunctionArgs | ActionFunctionArgs
) {
  if (!request.signal.aborted) {
    sendErrorToErrorReportingService(error);
    console.error(formatErrorForJsonLogging(error));
  }
}

請注意,您通常希望避免在請求中止時記錄,因為 React Router 的取消和競爭條件處理可能會導致大量請求被中止。

串流渲染錯誤

當您透過 renderToPipeableStreamrenderToReadableStream 串流您的 HTML 回應時,您自己的 handleError 實作將僅處理初始外殼渲染期間遇到的錯誤。如果您在後續的串流渲染期間遇到渲染錯誤,您將需要手動處理這些錯誤,因為 React Router 伺服器在那時已發送回應。

對於 renderToPipeableStream,您可以在 onError 回呼函式中處理這些錯誤。您需要在 onShellReady 中切換一個布林值,以便您知道錯誤是外殼渲染錯誤(可以忽略)還是非同步錯誤

有關範例,請參閱 Node 的預設 entry.server.tsx

拋出的回應

請注意,這不處理從您的 loader/action 函式拋出的 Response 實例。此處理常式的目的是尋找程式碼中導致意外拋出錯誤的錯誤。如果您正在偵測一種情況並在您的 loader/action 中拋出 401/404 等 Response,那麼這是您的程式碼處理的預期流程。如果您也希望記錄或將它們發送到外部服務,則應在拋出回應時完成。

.server 模組

雖然不是絕對必要,但 .server 模組是將整個模組明確標記為僅限伺服器端的好方法。如果 .server 檔案或 .server 目錄中的任何程式碼意外地最終出現在客戶端模組圖中,則建置將失敗。

app
├── .server 👈 marks all files in this directory as server-only
│   ├── auth.ts
│   └── db.ts
├── cms.server.ts 👈 marks this file as server-only
├── root.tsx
└── routes.ts

.server 模組必須位於您的應用程式目錄中。

有關更多資訊,請參閱側邊欄中的「路由模組」章節。

.client 模組

雖然不常見,但您可能有一個檔案或依賴項在瀏覽器中使用模組副作用。您可以在檔案名稱上使用 *.client.ts 或將檔案巢狀在 .client 目錄中,以強制它們退出伺服器捆綁包。

// this would break the server
export const supportsVibrationAPI =
  "vibrate" in window.navigator;

請注意,從此模組導出的值在伺服器上都將是 undefined,因此使用它們的唯一位置是在 useEffect 和使用者事件(如點擊處理常式)中。

import { supportsVibrationAPI } from "./feature-check.client.ts";

console.log(supportsVibrationAPI);
// server: undefined
// client: true | false
文件和範例 CC 4.0