跳至內容

從 Vite 遷移

本指南將協助您將現有的 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 內建了可自動為您優化這些項目的元件。

遷移步驟 步驟 1:安裝 Next.js 依賴項
終端機
npm install next@latest

步驟 2:建立 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 設定 專案參考
  • 將 `./dist/types/**/*.ts` 和 `./next-env.d.ts` 加入到`include` 陣列中。
  • 將 `./node_modules` 加入到`exclude` 陣列中。
  • 將 `{ "name": "next" }` 加入到 `compilerOptions` 中的`plugins` 陣列:`“plugins”: [{ “name”: “next” }]`
  • `esModuleInterop`設定為 `true`:`“esModuleInterop”: true`
  • `jsx`設定為 `preserve`:`“jsx”: “preserve”`
  • `allowJs`設定為 `true`:`“allowJs”: true`
  • `forceConsistentCasingInFileNames`設定為 `true`:`“forceConsistentCasingInFileNames”: true`
  • 設定 incrementaltrue"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 應用程式路由器 應用程式必須包含一個 根佈局 檔案,它是一個 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.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 物件 中。
    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]] 目錄。

    這個目錄就是所謂的可選的全部捕捉路由區段。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 在特殊的 import.meta.env 物件上公開了一些內建的環境變數,這些變數不受 Next.js 支援。您需要如下更新它們的用法:

    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 以取得已遷移到 Next.js 的 Vite 應用程式範例。

    步驟 9:清理

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

    後續步驟

    如果一切按計劃進行,您現在擁有一個以單頁應用程式形式運行的 Next.js 應用程式。然而,您尚未充分利用 Next.js 的大部分優點,但您現在可以開始進行漸進式更改以獲得所有優點。以下是您接下來可能想要做的事情: