跳到內容

如何從 Vite 遷移到 Next.js

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

為什麼要切換?

您可能希望從 Vite 切換到 Next.js 的原因有幾個

初始頁面載入時間慢

如果您使用 Vite 預設的 React 外掛 構建了應用程式,那麼您的應用程式是一個純客戶端應用程式。純客戶端應用程式,也稱為單頁應用程式 (SPA),通常會遇到初始頁面載入時間慢的問題。這有幾個原因

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

無自動程式碼分割

透過程式碼分割可以一定程度上解決載入時間慢的問題。然而,如果您嘗試手動進行程式碼分割,通常會使效能變差。手動進行程式碼分割時很容易無意中引入網路瀑布。Next.js 的路由器內建了自動程式碼分割功能。

網路瀑布

效能不佳的常見原因之一是應用程式連續發出客戶端-伺服器請求來獲取資料。SPA 中常見的資料獲取模式是先渲染一個佔位符,然後在元件掛載後獲取資料。不幸的是,這意味著一個子元件在父元件完成自身資料載入之前無法開始獲取資料。

雖然 Next.js 支援在客戶端獲取資料,但它也提供了將資料獲取轉移到伺服器的選項,這可以消除客戶端-伺服器瀑布效應。

快速且有意的載入狀態

藉助內建的 透過 React Suspense 進行流式傳輸 支援,您可以更有意地控制 UI 的哪些部分先載入,以及以何種順序載入,而不會引入網路瀑布效應。

這使您能夠構建載入速度更快的頁面並消除佈局偏移

選擇資料獲取策略

根據您的需求,Next.js 允許您根據頁面和元件選擇資料獲取策略。您可以選擇在構建時、在伺服器請求時或在客戶端獲取資料。例如,您可以從 CMS 獲取資料並在構建時渲染您的部落格文章,然後這些文章可以在 CDN 上高效快取。

代理

Next.js Proxy 允許您在請求完成之前在伺服器上執行程式碼。這對於避免使用者訪問僅限認證的頁面時出現未經認證內容閃爍的問題特別有用,透過將使用者重定向到登入頁面。代理對於實驗和 國際化 也很有用。

內建最佳化

圖片字型第三方指令碼 通常對應用程式的效能有顯著影響。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. compilerOptions 中的 plugins 陣列 中新增 { "name": "next" }"plugins": [{ "name": "next" }]
  5. esModuleInterop 設定為 true"esModuleInterop": true
  6. jsx 設定為 react-jsx"jsx": "react-jsx"
  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": "react-jsx",
    "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. 任何元資料檔案,例如 `favicon.ico`、`icon.png`、`robots.txt`,只要您將它們放置在 `app` 目錄的頂層,就會自動新增到應用程式的 `` 標籤中。將所有支援的檔案移動到 `app` 目錄後,您可以安全地刪除它們的 `` 標籤。
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 管理您最終的 `` 標籤。將您最終的元資料資訊移動到一個匯出的 `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 和 Web 可共享性。

步驟 5:建立入口頁面

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

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

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

此目錄被稱為可選的捕獲所有路由段。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>` 的 `width` 和 `height` 屬性。這可以防止圖片載入時出現佈局偏移。但是,如果您的應用程式中包含只有其一個尺寸被樣式化而另一個沒有樣式化為 `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 支援 .env 環境變數,類似於 Vite。主要區別在於客戶端暴露環境變數所使用的字首。

  • 將所有帶 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://:3000。您應該會看到您的應用程式現在正在 Next.js 上執行。

示例: 檢視 此拉取請求,瞭解一個已遷移到 Next.js 的 Vite 應用程式的執行示例。

步驟 9:清理

您現在可以從程式碼庫中清理與 Vite 相關的檔案

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

後續步驟

如果一切按計劃進行,您現在擁有一個作為單頁應用程式執行的 Next.js 應用程式。然而,您尚未充分利用 Next.js 的大部分優勢,但您現在可以開始進行增量更改以獲得所有優勢。以下是您接下來可能希望執行的操作