本指南將引導您完成在您的 Remix 應用程式中採用未來標誌的過程。透過遵循此策略,您將能夠以最少的變更升級到 Remix 的下一個主要版本。若要深入瞭解未來標誌,請參閱開發策略。
我們強烈建議您在每個步驟之後進行提交並發布,而不是一次完成所有事情。大多數標誌可以按任何順序採用,但以下註明的例外情況除外。
首先更新至最新的 v2.x 次要版本,以取得最新的未來標誌。在您升級時,您可能會看到一些棄用警告,我們將在下面介紹。
👉 更新至最新的 v2
npm install @remix-run/{dev,react,node,etc.}@2
installGlobals
背景
先前 Remix 需要安裝 fetch
polyfill。這是透過呼叫 installGlobals()
來完成的。
下一個主要版本需要至少 Node 20 才能利用內建的 fetch
支援。
注意:如果您在您的 remix 專案中使用 miniflare/cloudflare worker,請確保您的相容性標誌也設定為 2023-03-01
或更高版本。
👉 更新至 Node 20+
建議您升級至最新的偶數版本 Node LTS。
👉 移除 installGlobals
import { vitePlugin as remix } from "@remix-run/dev";
import { defineConfig } from "vite";
-installGlobals();
export default defineConfig({
plugins: [remix()],
});
背景
Remix 不再使用其自己的封閉編譯器(現在稱為「Classic Compiler」),而是使用 Vite。Vite 是 JavaScript 專案強大、高效且可擴充的開發環境。檢視 Vite 文件以取得效能、疑難排解等方面的更多資訊。
雖然這不是一個未來標誌,但新功能和某些功能標誌僅在 Vite 外掛中可用,而 Classic Compiler 將在下一個版本的 Remix 中移除。
👉 安裝 Vite
npm install -D vite
更新您的程式碼
👉 將您 Remix 應用程式根目錄中的 remix.config.js
替換為 vite.config.ts
import { vitePlugin as remix } from "@remix-run/dev";
import { defineConfig } from "vite";
export default defineConfig({
plugins: [remix()],
});
支援的 Remix 設定選項的子集應直接傳遞至外掛
export default defineConfig({
plugins: [
remix({
ignoredRouteFiles: ["**/*.css"],
}),
],
});
👉 新增 unstable_optimizeDeps
(可選)
許多使用者發現自動最佳化相依性可協助他們更輕鬆地採用 Vite 外掛。因此,我們在 Vite 外掛中新增了 unstable_optimizeDeps
標誌。
此標誌將保持在「不穩定」狀態,直到 React Router v7,因此在升級至 React Router v7 之前,您不需要在 Remix v2 應用程式中採用此標誌。
export default defineConfig({
plugins: [
remix({
future: {
unstable_optimizeDeps: true,
},
}),
],
});
👉 移除 <LiveReload/>
,保留 <Scripts />
import {
- LiveReload,
Outlet,
Scripts,
}
export default function App() {
return (
<html>
<head>
</head>
<body>
<Outlet />
- <LiveReload />
<Scripts />
</body>
</html>
)
}
👉 更新 tsconfig.json
更新 tsconfig.json
中的 types
欄位,並確保 skipLibCheck
、module
和 moduleResolution
都已正確設定。
{
"compilerOptions": {
"types": ["@remix-run/node", "vite/client"],
"skipLibCheck": true,
"module": "ESNext",
"moduleResolution": "Bundler"
}
}
👉 更新/移除 remix.env.d.ts
移除 remix.env.d.ts
中的下列類型宣告
- /// <reference types="@remix-run/dev" />
- /// <reference types="@remix-run/node" />
如果 remix.env.d.ts
現在為空,請將其刪除
rm remix.env.d.ts
設定路徑別名
Vite 預設不提供任何路徑別名。如果您依賴此功能,例如將 ~
定義為 app
目錄的別名,您可以安裝 vite-tsconfig-paths 外掛程式,以自動從 Vite 中的 tsconfig.json
解析路徑別名,與 Remix 編譯器的行為相符
👉 安裝 vite-tsconfig-paths
npm install -D vite-tsconfig-paths
👉 將 vite-tsconfig-paths
新增至您的 Vite 設定
import { vitePlugin as remix } from "@remix-run/dev";
import { defineConfig } from "vite";
import tsconfigPaths from "vite-tsconfig-paths";
export default defineConfig({
plugins: [remix(), tsconfigPaths()],
});
移除 @remix-run/css-bundle
Vite 內建支援 CSS 側效應引入、PostCSS 和 CSS 模組,以及其他 CSS 打包功能。Remix Vite 外掛會自動將打包的 CSS 附加到相關路由。
當@remix-run/css-bundle
cssBundleHref
匯出將始終為 undefined
。
👉 解除安裝 @remix-run/css-bundle
npm uninstall @remix-run/css-bundle
👉 移除對 cssBundleHref
的參照
- import { cssBundleHref } from "@remix-run/css-bundle";
import type { LinksFunction } from "@remix-run/node"; // or cloudflare/deno
export const links: LinksFunction = () => [
- ...(cssBundleHref
- ? [{ rel: "stylesheet", href: cssBundleHref }]
- : []),
// ...
];
修正 links
中參照的 CSS 引入
如果您在 links
函式中參照 CSS,您需要更新對應的 CSS 引入,以使用 Vite 的明確 ?url
引入語法。
👉 將 ?url
新增至 links
中使用的 CSS 引入
-import styles from "~/styles/dashboard.css";
+import styles from "~/styles/dashboard.css?url";
export const links = () => {
return [
{ rel: "stylesheet", href: styles }
];
}
遷移 Tailwind CSS 或 Vanilla Extract
如果您使用 Tailwind CSS 或 Vanilla Extract,請參閱完整遷移指南。
從 Remix 應用程式伺服器遷移
👉 更新您的 dev
、build
和 start
指令碼
{
"scripts": {
"dev": "remix vite:dev",
"build": "remix vite:build",
"start": "remix-serve ./build/server/index.js"
}
}
👉 設定您的 Vite 開發伺服器連接埠(可選)
export default defineConfig({
server: {
port: 3000,
},
plugins: [remix()],
});
遷移自訂伺服器
如果您要遷移自訂伺服器或 Cloudflare Functions,請參閱完整遷移指南。
遷移 MDX 路由
如果您使用 MDX,您應該使用官方的 MDX Rollup 外掛程式。如需逐步演練,請參閱完整遷移指南。
背景
fetcher 生命週期現在基於其返回閒置狀態的時間,而不是其擁有者元件解除掛載的時間:如需更多資訊,請參閱檢視 RFC。
👉 啟用標誌
remix({
future: {
v3_fetcherPersist: true,
},
});
更新您的程式碼
它不太可能影響您的應用程式。您可能需要檢查 useFetchers
的任何用法,因為它們可能會比以前持續更長的時間。根據您正在執行的操作,您可能會比以前呈現更長的時間。
背景
變更多區段 splat 路徑(例如 dashboard/*
(相對於僅 *
))的相對路徑匹配和連結。如需更多資訊,請參閱檢視 CHANGELOG。
👉 啟用標誌
remix({
future: {
v3_relativeSplatPath: true,
},
});
更新您的程式碼
如果您有任何具有路徑 + splat 的路由,例如 dashboard.$.tsx
或 route("dashboard/*")
,且其下方具有相對連結,例如 <Link to="relative">
或 <Link to="../relative">
,您將需要更新您的程式碼。
👉 將路由分割為兩個
對於任何 splat 路由,請將其分割為版面配置路由和具有 splat 的子路由
└── routes
├── _index.tsx
+ ├── dashboard.tsx
└── dashboard.$.tsx
// or
routes(defineRoutes) {
return defineRoutes((route) => {
route("/", "home/route.tsx", { index: true });
- route("dashboard/*", "dashboard/route.tsx")
+ route("dashboard", "dashboard/layout.tsx", () => {
+ route("*", "dashboard/route.tsx");
});
});
},
👉 更新相對連結
更新該路由樹中任何具有相對連結的 <Link>
元素,以包含額外的 ..
相對區段,以繼續連結至相同位置
// dashboard.$.tsx or dashboard/route.tsx
function Dashboard() {
return (
<div>
<h2>Dashboard</h2>
<nav>
- <Link to="">Dashboard Home</Link>
- <Link to="team">Team</Link>
- <Link to="projects">Projects</Link>
+ <Link to="../">Dashboard Home</Link>
+ <Link to="../team">Team</Link>
+ <Link to="../projects">Projects</Link>
</nav>
</div>
);
}
背景
當伺服器端請求中止時,例如當使用者在載入器完成之前離開頁面時,Remix 將會擲回 request.signal.reason
,而不是錯誤,例如 new Error("query() call aborted...")
。
👉 啟用標誌
remix({
future: {
v3_throwAbortReason: true,
},
});
更新您的程式碼
您可能不需要調整任何程式碼,除非您的 handleError
內部有自訂邏輯,會比對先前的錯誤訊息,以區分它和其他錯誤。
背景
啟用此標記後,Remix 不再在初始載入時將完整的路由清單傳送到客戶端。相反地,Remix 只會在清單中傳送伺服器渲染的路由,然後在使用者瀏覽應用程式時擷取其餘的路由。其他詳細資訊請參閱文件和部落格文章。
👉 啟用標誌
remix({
future: {
v3_lazyRouteDiscovery: true,
},
});
更新您的程式碼
您應該不需要為了讓此功能運作而對應用程式程式碼進行任何變更。
如果您想要在某些連結上停用預先路由探索,您可能會發現新的 <Link discover>
API 有些用途。
此標記需要 Vite 外掛程式。
背景
啟用此標記後,Remix 會在客戶端導覽期間使用單次擷取來處理資料請求。這透過將資料請求視為文件請求相同的方式,簡化了資料載入,消除了處理標頭和快取的差異。對於進階的使用案例,您仍然可以選擇進行細微的重新驗證。如需更多資訊,請參閱「單次擷取」文件。
👉 啟用標記(和類型)
import { vitePlugin as remix } from "@remix-run/dev";
import { defineConfig } from "vite";
import tsconfigPaths from "vite-tsconfig-paths";
declare module "@remix-run/node" {
// or cloudflare, deno, etc.
interface Future {
v3_singleFetch: true;
}
}
export default defineConfig({
plugins: [
remix({
future: {
v3_singleFetch: true,
},
}),
tsconfigPaths(),
],
});
更新您的程式碼
您應該能夠在啟用標記的情況下,大部分照常使用程式碼,但應隨著時間推移進行以下變更,且在下一個主要版本之前必須完成。
👉 移除 json()
/defer()
,改用原始物件
單次擷取支援 JSON 物件和 Promise 開箱即用,因此您可以從 loader
/action
函式傳回原始資料
-import { json } from "@remix-run/node";
export async function loader({}: LoaderFunctionArgs) {
let tasks = await fetchTasks();
- return json(tasks);
+ return tasks;
}
-import { defer } from "@remix-run/node";
export async function loader({}: LoaderFunctionArgs) {
let lazyStuff = fetchLazyStuff();
let tasks = await fetchTasks();
- return defer({ tasks, lazyStuff });
+ return { tasks, lazyStuff };
}
如果您使用 json
/defer
的第二個參數,來設定回應的自訂狀態或標頭,您可以透過新的 data
API 繼續執行此操作
-import { json } from "@remix-run/node";
+import { data } from "@remix-run/node";
export async function loader({}: LoaderFunctionArgs) {
let tasks = await fetchTasks();
- return json(tasks, {
+ return data(tasks, {
headers: {
"Cache-Control": "public, max-age=604800"
}
});
}
👉 調整您的伺服器中止延遲
如果您在 entry.server.tsx
檔案中使用自訂的 ABORT_DELAY
,您應該將其變更為使用單次擷取利用的新 streamTimeout
API
-const ABORT_DELAY = 5000;
+// Reject/cancel all pending promises after 5 seconds
+export const streamTimeout = 5000;
// ...
function handleBrowserRequest(/* ... */) {
return new Promise((resolve, reject) => {
const { pipe, abort } = renderToPipeableStream(
<RemixServer
context={remixContext}
url={request.url}
- abortDelay={ABORT_DELAY}
/>,
{
onShellReady() {
/* ... */
},
onShellError(error: unknown) {
/* ... */
},
onError(error: unknown) {
/* ... */
},
}
);
- setTimeout(abort, ABORT_DELAY);
+ // Automatically timeout the React renderer after 6 seconds, which ensures
+ // React has enough time to flush down the rejected boundary contents
+ setTimeout(abort, streamTimeout + 1000);
});
}
此標記需要 Vite 外掛程式。
基於設定的路由是 React Router v7 中的新預設設定,透過應用程式目錄中的 routes.ts
檔案進行設定。Remix 中對 routes.ts
及其相關 API 的支援,旨在作為遷移路徑,以盡可能減少將 Remix 專案遷移到 React Router v7 時所需的變更數量。雖然在 @remix-run
範圍內引入了一些新的套件,但這些新的套件僅為了讓 routes.ts
中的程式碼,與 React Router v7 的等效程式碼盡可能相似。
啟用 v3_routeConfig
未來標記後,Remix 的內建檔案系統路由將會停用,而您的專案將選擇使用 React Router v7 的基於設定的路由。如果您偏好繼續使用 Remix 的基於檔案的路由,我們將在下方的 routes.ts
中說明如何啟用它。
更新您的程式碼
若要將 Remix 的檔案系統路由和路由設定遷移到 React Router v7 中的等效設定,您可以按照下列步驟操作
👉 啟用標誌
remix({
future: {
v3_routeConfig: true,
},
});
👉 安裝 @remix-run/route-config
此套件符合 React Router v7 的 @react-router/dev/routes
的 API,盡可能簡化 React Router v7 的遷移。
npm install -D @remix-run/route-config
這提供了核心的 RouteConfig
類型,以及一組用於在程式碼中設定路由的輔助工具。
👉 新增一個不含任何已設定路由的 app/routes.ts
檔案
touch app/routes.ts
import type { RouteConfig } from "@remix-run/route-config";
export default [] satisfies RouteConfig;
這是一個很好的方法,可以檢查您的新 routes.ts
檔案是否已成功載入。由於尚未定義任何路由,因此您的應用程式現在應呈現空白頁面。
👉 安裝 @remix-run/fs-routes
並在 routes.ts
中使用它
npm install -D @remix-run/fs-routes
此套件符合 React Router v7 的 @react-router/fs-routes
的 API,盡可能簡化 React Router v7 的遷移。
如果您將
ignoredRouteFiles
設定為["**/*"]
,則應略過此步驟,因為您已選擇退出 Remix 的檔案系統路由。
import { flatRoutes } from "@remix-run/fs-routes";
export default flatRoutes();
👉 如果您使用了 routes
設定選項,請新增 @remix-run/routes-option-adapter
並在 routes.ts
中使用它
Remix 提供了一種在程式碼中定義路由,並插入替代檔案系統路由慣例的機制,可透過 Vite 外掛程式上的 routes
選項取得。
為了簡化遷移,提供了一個轉換器套件,可將 Remix 的 routes
選項轉換為 React Router 的 RouteConfig
陣列。
若要開始,請先安裝轉換器
npm install -D @remix-run/routes-option-adapter
此套件符合 React Router v7 的 @react-router/remix-routes-option-adapter
的 API,盡可能簡化 React Router v7 的遷移。
然後,更新您的 routes.ts
檔案以使用轉換器,將您的 routes
選項的值傳遞給 remixRoutesOptionAdapter
函式,該函式將傳回已設定路由的陣列。
例如,如果您使用 routes
選項來使用替代的檔案系統路由實作,例如 remix-flat-routes
import { type RouteConfig } from "@remix-run/route-config";
import { remixRoutesOptionAdapter } from "@remix-run/routes-option-adapter";
import { flatRoutes } from "remix-flat-routes";
export default remixRoutesOptionAdapter((defineRoutes) =>
flatRoutes("routes", defineRoutes)
) satisfies RouteConfig;
或者,如果您使用 routes
選項來定義基於設定的路由
import { flatRoutes } from "@remix-run/fs-routes";
import { type RouteConfig } from "@remix-run/route-config";
import { remixRoutesOptionAdapter } from "@remix-run/routes-option-adapter";
export default remixRoutesOptionAdapter((defineRoutes) => {
return defineRoutes((route) => {
route("/", "home/route.tsx", { index: true });
route("about", "about/route.tsx");
route("", "concerts/layout.tsx", () => {
route("trending", "concerts/trending.tsx");
route(":city", "concerts/city.tsx");
});
});
}) satisfies RouteConfig;
如果您以這種方式定義基於設定的路由,您可能會想要考慮遷移到新的路由設定 API,因為它更精簡,同時仍然與舊 API 非常相似。例如,上述路由看起來會像這樣
import {
type RouteConfig,
route,
layout,
index,
} from "@remix-run/route-config";
export default [
index("home/route.tsx"),
route("about", "about/route.tsx"),
layout("concerts/layout.tsx", [
route("trending", "concerts/trending.tsx"),
route(":city", "concerts/city.tsx"),
]),
] satisfies RouteConfig;
請注意,如果您需要混合和比對不同的路由設定方法,它們可以合併成單個路由陣列。RouteConfig
類型可確保所有內容仍然有效。
import { flatRoutes } from "@remix-run/fs-routes";
import type { RouteConfig } from "@remix-run/route-config";
import { route } from "@remix-run/route-config";
import { remixRoutesOptionAdapter } from "@remix-run/routes-option-adapter";
export default [
...(await flatRoutes({ rootDirectory: "fs-routes" })),
...(await remixRoutesOptionAdapter(/* ... */)),
route("/hello", "routes/hello.tsx"),
] satisfies RouteConfig;
@remix-run/eslint-config
套件已棄用,且不會包含在 React Router v7 中。我們建議您轉向簡化的 ESLint 設定,例如 Remix 範本中包含的設定。
此公用程式已棄用,並將在 React Router v7 中移除,以支援 單次擷取原始物件傳回。
json
來序列化您的資料(例如字串化 Date
物件),您可以安全地移除它。json
傳回 headers
或 status
,您可以使用新的 data 公用程式作為直接替代來設定這些值。如需更多資訊,請參閱 單次擷取 文件。
此公用程式已棄用,並將在 React Router v7 中移除,以支援 單次擷取原始物件傳回。
defer
傳回 headers
或 status
,您可以使用新的 data 公用程式作為直接替代來設定這些值。如需更多資訊,請參閱 單次擷取 文件。
此類型已棄用,並將在 React Router v7 中移除,因為 單次擷取不再將資料序列化為 JSON。
如果您依賴 SerializeFrom
來解開 loader
/action
資料,您可以使用像這樣的自訂類型
type SerializeFrom<T> = ReturnType<typeof useLoaderData<T>>;
在大多數情況下,您應該能夠直接移除 SerializeFrom
,並使用從 useLoaderData
/useActionData
傳回的類型,或 loader
/action
函式中資料的類型。
以下公用程式已棄用,並將在 React Router v7 中移除
unstable_parseMultipartFormData
unstable_composeUploadHandlers
unstable_createFileUploadHandler
unstable_createMemoryUploadHandler
我們建議使用 @mjackson/form-data-parser
和 @mjackson/file-storage
來處理多部分表單資料和檔案上傳。
您也可以查看 React Router「檔案上傳」文件或 「使用 Remix 進行檔案上傳」部落格文章,以取得使用這些程式庫的指南。