跳到內容

從 Vite 遷移

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

為何切換?

有幾個理由可能會讓你想要從 Vite 切換到 Next.js

初始頁面載入時間緩慢

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

  1. 瀏覽器需要等待 React 程式碼和你整個應用程式的 bundle 下載並執行,你的程式碼才能發送請求來載入一些資料。
  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

要知道的好處: 你可以使用 .js.mjs 作為你的 Next.js 設定檔。

步驟 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,只要你將它們放置在 app 目錄的頂層,就會自動新增到應用程式的 <head> 標籤中。將所有支援的檔案移動到 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 支援 .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://127.0.0.1:3000。您應該看到您的應用程式現在在 Next.js 上運行。

範例: 查看 這個 pull request 以獲得將 Vite 應用程式遷移到 Next.js 的工作範例。

步驟 9:清理

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

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

後續步驟

如果一切按計劃進行,您現在應該有一個作為單頁應用程式運行的 Next.js 應用程式。但是,您尚未利用 Next.js 的大多數優勢,但您現在可以開始進行增量更改以獲得所有好處。以下是您接下來可能想做的事情