跳到內容

從 Vite 遷移

本指南將協助您將現有的 Vite 應用程式遷移到 Next.js。

為何切換?

您可能想要從 Vite 切換到 Next.js 有幾個原因

初始頁面載入時間緩慢

如果您使用 React 的預設 Vite 外掛程式 建置您的應用程式,則您的應用程式純粹是用戶端應用程式。純用戶端應用程式,也稱為單頁應用程式 (SPA),通常會遇到初始頁面載入時間緩慢的問題。發生這種情況有幾個原因

  1. 瀏覽器需要等待 React 程式碼和您的整個應用程式套件下載並執行,您的程式碼才能發送請求來載入一些資料。
  2. 您的應用程式程式碼會隨著您新增的每個新功能和額外依賴項而增長。

沒有自動程式碼分割

先前的載入時間緩慢問題可以透過程式碼分割在某種程度上解決。但是,如果您嘗試手動進行程式碼分割,通常會使效能更差。當手動進行程式碼分割時,很容易不經意地引入網路瀑布流。Next.js 提供內建在其路由器中的自動程式碼分割。

網路瀑布流

當應用程式進行連續的用戶端-伺服器請求以抓取資料時,就會發生常見的效能不佳情況。SPA 中資料抓取的一種常見模式是初始渲染佔位符,然後在元件掛載後抓取資料。不幸的是,這表示抓取資料的子元件在父元件完成載入其自身資料之前,無法開始抓取。

雖然 Next.js 支援在用戶端抓取資料,但它也讓您可以選擇將資料抓取轉移到伺服器,這可以消除用戶端-伺服器瀑布流。

快速且有意的載入狀態

透過內建對 透過 React Suspense 進行串流 的支援,您可以更有意地決定要先載入 UI 的哪些部分以及以什麼順序載入,而不會引入網路瀑布流。

這使您可以建置載入速度更快的頁面,並消除 版面配置偏移

選擇資料抓取策略

根據您的需求,Next.js 允許您在頁面和元件的基礎上選擇資料抓取策略。您可以決定在建置時、在伺服器上的請求時或在用戶端抓取。例如,您可以從 CMS 抓取資料,並在建置時渲染您的部落格文章,然後可以有效地快取在 CDN 上。

中介軟體

Next.js 中介軟體 允許您在請求完成之前在伺服器上執行程式碼。這對於避免使用者造訪僅限已驗證使用者的頁面時出現未驗證內容的閃爍特別有用,方法是將使用者重新導向到登入頁面。中介軟體也適用於實驗和 國際化

內建最佳化

圖片字型第三方指令碼 通常對應用程式的效能有重大影響。Next.js 配備了內建元件,可自動為您最佳化這些項目。

遷移步驟

我們這次遷移的目標是盡快獲得一個可運作的 Next.js 應用程式,以便您可以逐步採用 Next.js 功能。首先,我們將其保持為純用戶端應用程式 (SPA),而無需遷移您現有的路由器。這有助於最大限度地減少在遷移過程中遇到問題的機會,並減少合併衝突。

步驟 1:安裝 Next.js 依賴項

您需要做的第一件事是安裝 next 作為依賴項

終端機
npm install next@latest

步驟 2:建立 Next.js 設定檔

在專案根目錄建立 next.config.mjs。此檔案將保存您的 Next.js 設定選項

next.config.mjs
/** @type {import('next').NextConfig} */
const nextConfig = {
  output: 'export', // Outputs a Single-Page Application (SPA).
  distDir: './dist', // Changes the build output directory to `./dist/`.
}
 
export default nextConfig

小知識: 您的 Next.js 設定檔可以使用 .js.mjs

步驟 3:更新 TypeScript 設定

如果您使用 TypeScript,則需要使用以下變更更新您的 tsconfig.json 檔案,使其與 Next.js 相容。如果您未使用 TypeScript,則可以跳過此步驟。

  1. 移除對 tsconfig.node.json專案參考
  2. ./dist/types/**/*.ts./next-env.d.ts 新增到 include 陣列
  3. ./node_modules 新增到 exclude 陣列
  4. { "name": "next" } 新增到 compilerOptions 中的 plugins 陣列"plugins": [{ "name": "next" }]
  5. esModuleInterop 設定為 true"esModuleInterop": true
  6. jsx 設定為 preserve"jsx": "preserve"
  7. allowJs 設定為 true"allowJs": true
  8. forceConsistentCasingInFileNames 設定為 true"forceConsistentCasingInFileNames": true
  9. incremental 設定為 true"incremental": true

以下是一個包含這些變更的有效 tsconfig.json 範例

tsconfig.json
{
  "compilerOptions": {
    "target": "ES2020",
    "useDefineForClassFields": true,
    "lib": ["ES2020", "DOM", "DOM.Iterable"],
    "module": "ESNext",
    "esModuleInterop": true,
    "skipLibCheck": true,
    "moduleResolution": "bundler",
    "allowImportingTsExtensions": true,
    "resolveJsonModule": true,
    "isolatedModules": true,
    "noEmit": true,
    "jsx": "preserve",
    "strict": true,
    "noUnusedLocals": true,
    "noUnusedParameters": true,
    "noFallthroughCasesInSwitch": true,
    "allowJs": true,
    "forceConsistentCasingInFileNames": true,
    "incremental": true,
    "plugins": [{ "name": "next" }]
  },
  "include": ["./src", "./dist/types/**/*.ts", "./next-env.d.ts"],
  "exclude": ["./node_modules"]
}

您可以在 Next.js 文件中找到有關設定 TypeScript 的更多資訊。

步驟 4:建立根版面配置

Next.js App Router 應用程式必須包含一個 根版面配置 檔案,這是一個 React 伺服器元件,它將包裝您應用程式中的所有頁面。此檔案定義在 app 目錄的最上層。

Vite 應用程式中與根版面配置檔案最接近的對等檔案是 index.html 檔案,其中包含您的 <html><head><body> 標籤。

在此步驟中,您將把您的 index.html 檔案轉換為根版面配置檔案

  1. 在您的 src 目錄中建立一個新的 app 目錄。
  2. 在該 app 目錄內建立一個新的 layout.tsx 檔案
app/layout.tsx
export default function RootLayout({
  children,
}: {
  children: React.ReactNode
}) {
  return '...'
}

小知識:版面配置檔案可以使用 .js.jsx.tsx 副檔名。

  1. 將您的 index.html 檔案的內容複製到先前建立的 <RootLayout> 元件中,同時將 body.div#rootbody.script 標籤替換為 <div id="root">{children}</div>
app/layout.tsx
export default function RootLayout({
  children,
}: {
  children: React.ReactNode
}) {
  return (
    <html lang="en">
      <head>
        <meta charset="UTF-8" />
        <link rel="icon" type="image/svg+xml" href="/icon.svg" />
        <meta name="viewport" content="width=device-width, initial-scale=1.0" />
        <title>My App</title>
        <meta name="description" content="My App is a..." />
      </head>
      <body>
        <div id="root">{children}</div>
      </body>
    </html>
  )
}
  1. Next.js 預設已包含 meta charsetmeta viewport 標籤,因此您可以安全地從 <head> 中移除這些標籤
app/layout.tsx
export default function RootLayout({
  children,
}: {
  children: React.ReactNode
}) {
  return (
    <html lang="en">
      <head>
        <link rel="icon" type="image/svg+xml" href="/icon.svg" />
        <title>My App</title>
        <meta name="description" content="My App is a..." />
      </head>
      <body>
        <div id="root">{children}</div>
      </body>
    </html>
  )
}
  1. 任何 metadata 檔案 (例如 favicon.icoicon.pngrobots.txt) 都會自動新增到應用程式 <head> 標籤,只要您將它們放置在 app 目錄的最上層即可。將 所有支援的檔案 移動到 app 目錄後,您可以安全地刪除其 <link> 標籤
app/layout.tsx
export default function RootLayout({
  children,
}: {
  children: React.ReactNode
}) {
  return (
    <html lang="en">
      <head>
        <title>My App</title>
        <meta name="description" content="My App is a..." />
      </head>
      <body>
        <div id="root">{children}</div>
      </body>
    </html>
  )
}
  1. 最後,Next.js 可以使用 Metadata API 管理您最後的 <head> 標籤。將您最後的 metadata 資訊移動到匯出的 metadata 物件
app/layout.tsx
import type { Metadata } from 'next'
 
export const metadata: Metadata = {
  title: 'My App',
  description: 'My App is a...',
}
 
export default function RootLayout({
  children,
}: {
  children: React.ReactNode
}) {
  return (
    <html lang="en">
      <body>
        <div id="root">{children}</div>
      </body>
    </html>
  )
}

透過上述變更,您從在 index.html 中宣告所有內容,轉變為使用 Next.js 內建在框架中的基於慣例的方法 (Metadata API)。這種方法使您可以更輕鬆地改善頁面的 SEO 和網路分享性。

步驟 5:建立進入點頁面

在 Next.js 上,您可以透過建立 page.tsx 檔案來宣告應用程式的進入點。Vite 上與此檔案最接近的對等檔案是您的 main.tsx 檔案。在此步驟中,您將設定應用程式的進入點。

  1. 在您的 app 目錄中建立一個 [[...slug]] 目錄。

由於在本指南中,我們的首要目標是將 Next.js 設定為 SPA (單頁應用程式),因此您需要您的頁面進入點來捕獲應用程式的所有可能路由。為此,請在您的 app 目錄中建立一個新的 [[...slug]] 目錄。

此目錄稱為 可選的 catch-all 路由區段。Next.js 使用基於檔案系統的路由器,其中資料夾用於定義路由。這個特殊的目錄將確保您應用程式的所有路由都將導向到其包含的 page.tsx 檔案。

  1. app/[[...slug]] 目錄內建立一個新的 page.tsx 檔案,其中包含以下內容
app/[[...slug]]/page.tsx
import '../../index.css'
 
export function generateStaticParams() {
  return [{ slug: [''] }]
}
 
export default function Page() {
  return '...' // We'll update this
}

小知識:頁面檔案可以使用 .js.jsx.tsx 副檔名。

此檔案是一個 伺服器元件。當您執行 next build 時,該檔案會預先渲染為靜態資源。它需要任何動態程式碼。

此檔案匯入我們的全域 CSS,並告知 generateStaticParams 我們只會產生一個路由,即 / 的索引路由。

現在,讓我們移動我們其餘的 Vite 應用程式,它將僅在用戶端執行。

app/[[...slug]]/client.tsx
'use client'
 
import React from 'react'
import dynamic from 'next/dynamic'
 
const App = dynamic(() => import('../../App'), { ssr: false })
 
export function ClientOnly() {
  return <App />
}

此檔案是一個 用戶端元件,由 'use client' 指令定義。用戶端元件仍然會在傳送到用戶端之前,在伺服器上 預先渲染為 HTML

由於我們希望應用程式以純客戶端方式啟動,我們可以設定 Next.js 停用從 App 元件開始的預先渲染。

const App = dynamic(() => import('../../App'), { ssr: false })

現在,更新您的進入點頁面以使用新的元件

app/[[...slug]]/page.tsx
import '../../index.css'
import { ClientOnly } from './client'
 
export function generateStaticParams() {
  return [{ slug: [''] }]
}
 
export default function Page() {
  return <ClientOnly />
}

步驟 6:更新靜態圖片匯入

Next.js 處理靜態圖片匯入的方式與 Vite 略有不同。使用 Vite 時,匯入圖片檔案會將其公開 URL 作為字串傳回

App.tsx
import image from './img.png' // `image` will be '/assets/img.2d8efhg.png' in production
 
export default function App() {
  return <img src={image} />
}

使用 Next.js 時,靜態圖片匯入會傳回一個物件。然後可以直接將該物件與 Next.js 的 <Image> 元件 一起使用,或者您可以將物件的 src 屬性與現有的 <img> 標籤一起使用。

<Image> 元件還具有 自動圖片最佳化 的額外優點。 <Image> 元件會根據圖片的尺寸自動設定產生的 <img>widthheight 屬性。這可以防止圖片載入時發生版面配置偏移。但是,如果您的應用程式包含僅對其中一個尺寸進行樣式設定,而另一個尺寸未設定為 auto 的圖片,則可能會導致問題。當未設定為 auto 時,尺寸將預設為 <img> 尺寸屬性的值,這可能會導致圖片顯示失真。

保留 <img> 標籤將減少應用程式中的變更量,並防止上述問題。然後,您可以選擇稍後遷移到 <Image> 元件,以透過設定載入器,或移至具有自動圖片最佳化的預設 Next.js 伺服器,來利用圖片最佳化。

  1. 轉換從 /public 匯入的圖片的絕對匯入路徑為相對匯入路徑
// Before
import logo from '/logo.png'
 
// After
import logo from '../public/logo.png'
  1. 將圖片的 src 屬性而不是整個圖片物件傳遞到您的 <img> 標籤
// Before
<img src={logo} />
 
// After
<img src={logo.src} />

或者,您可以根據檔案名稱參考圖片資產的公開 URL。例如,public/logo.png 將在 /logo.png 為您的應用程式提供圖片,這將是 src 值。

警告: 如果您使用 TypeScript,則在存取 src 屬性時可能會遇到類型錯誤。您可以暫時忽略這些錯誤。它們將在本指南的結尾修正。

步驟 7:遷移環境變數

Next.js 支援類似 Vite 的 .env 環境變數。主要區別在於用於在客戶端公開環境變數的前綴。

  • 將所有帶有 VITE_ 前綴的環境變數更改為 NEXT_PUBLIC_

Vite 在特殊的 import.meta.env 物件上公開了一些內建環境變數,這些變數 Next.js 不支援。您需要按如下方式更新它們的用法

  • import.meta.env.MODEprocess.env.NODE_ENV
  • import.meta.env.PRODprocess.env.NODE_ENV === 'production'
  • import.meta.env.DEVprocess.env.NODE_ENV !== 'production'
  • import.meta.env.SSRtypeof window !== 'undefined'

Next.js 也沒有提供內建的 BASE_URL 環境變數。但是,如果您需要,仍然可以設定一個

  1. 將以下內容新增到您的 .env 檔案
.env
# ...
NEXT_PUBLIC_BASE_PATH="/some-base-path"
  1. 在您的 next.config.mjs 檔案中將 basePath 設定為 process.env.NEXT_PUBLIC_BASE_PATH
next.config.mjs
/** @type {import('next').NextConfig} */
const nextConfig = {
  output: 'export', // Outputs a Single-Page Application (SPA).
  distDir: './dist', // Changes the build output directory to `./dist/`.
  basePath: process.env.NEXT_PUBLIC_BASE_PATH, // Sets the base path to `/some-base-path`.
}
 
export default nextConfig
  1. import.meta.env.BASE_URL 的用法更新為 process.env.NEXT_PUBLIC_BASE_PATH

步驟 8:更新 package.json 中的腳本

您現在應該能夠執行您的應用程式,以測試您是否已成功遷移到 Next.js。但在這之前,您需要使用 Next.js 相關命令更新 package.json 中的 scripts,並將 .nextnext-env.d.ts 新增到您的 .gitignore

package.json
{
  "scripts": {
    "dev": "next dev",
    "build": "next build",
    "start": "next start"
  }
}
.gitignore
# ...
.next
next-env.d.ts
dist

現在執行 npm run dev,並開啟 https://127.0.0.1:3000。您應該會看到您的應用程式現在在 Next.js 上執行。

範例: 查看 此提取請求,以取得將 Vite 應用程式遷移到 Next.js 的工作範例。

步驟 9:清理

您現在可以從程式碼庫中清除與 Vite 相關的產物

  • 刪除 main.tsx
  • 刪除 index.html
  • 刪除 vite-env.d.ts
  • 刪除 tsconfig.node.json
  • 刪除 vite.config.ts
  • 解除安裝 Vite 依賴項

後續步驟

如果一切按計劃進行,您現在應該有一個作為單頁應用程式運作的 Next.js 應用程式。但是,您尚未充分利用 Next.js 的大多數優勢,但您現在可以開始進行漸進式變更以獲得所有優勢。以下是您接下來可能想要執行的操作